在学习 string 类时我们认识了很多关于 string 类的接口,这些接口可以很好的帮助我们解决问题并简化代码,所以接下来我们要自行实现一个 string 类来加深对 string 类的理解
关于 string 类的详细讲解参考博文:面向对象程序设计(C++)之 String 类
1. 构造与析构函数
string 类中的构造函数通常有无参与带参构造,这里我们都进行实现并且整合优化
using namespace std;
namespace bit
{
class string
{
public:
//无参构造函数
string()
:_str(new char[1] {'\0'})
,_size(0)
,_capacity(0)
{}
//带参构造
string(const char* str)
{
//capacity 中不包含'/0',所以在开辟_str所需空间时才会多开一个存储'\0'
_size = strlen(str);
_capacity = _size;
str = new char[_capacity + 1];
strcpy(_str, str);//将str拷贝给_str
}
//全缺省构造函数,上面二者合并
string(const char* str = "")
{
//capacity 中不包含'/0',所以在开辟_str所需空间时才会多开一个存储'\0'
_size = strlen(str);
_capacity = _size;
str = new char[_capacity + 1];
strcpy(_str, str);//将str拷贝给_str
}
//拷贝构造函数
string(const string& s)//深拷贝
{
_str = new char[s._capacity + 1];//多开一个空间存储'/0'
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
//析构函数
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
private:
char* _str;//字符串数组
size_t _size;//长度
size_t _capacity;//容量
};
}
2. 遍历的实现
string 遍历有下标遍历、迭代器遍历与范围for遍历,这里我们一一实现
范围for的底层逻辑实际就是迭代器,所以模拟实现一个迭代器的操作就可以使用二者,下标遍历需要模拟实现 [] 符号的重载,然后使用 for 循环遍历即可
using namespace std;
namespace bit
{
class string
{
public:
//迭代器
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
无参构造函数
//string()
// :_str(new char[1] {'\0'})
// ,_size(0)
// ,_capacity(0)
//{}
带参构造
//string(const char* str)
//{
// //capacity 中不包含'/0',所以在开辟_str所需空间时才会多开一个存储'\0'
// _size = strlen(str);
// _capacity = _size;
// str = new char[_capacity + 1];
// strcpy(_str, str);//将str拷贝给_str
//}
//全缺省构造函数,上面二者合并
string(const char* str = "")
{
//capacity 中不包含'/0',所以在开辟_str所需空间时才会多开一个存储'\0'
_size = strlen(str);
_capacity = _size;
str = new char[_capacity + 1];
strcpy(_str, str);//将str拷贝给_str
}
//拷贝构造函数
string(const string& s)//深拷贝
{
_str = new char[s._capacity + 1];//多开一个空间存储'/0'
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
//析构函数
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
const char* c_str() const
{
return _str;
}
//返回长度
size_t size()
{
return _size;
}
//返回容量
size_t capacity()
{
return _capacity;
}
//下标实现[] 返回引用可读可写
char& operator[](size_t pos)
{
assert(pos < _size);//防止越界
return _str[pos];
}
//只读类型
char& operator[](size_t pos) const
{
assert(pos < _size);//防止越界
return _str[pos];
}
private:
char* _str;//字符串数组
size_t _size;//长度
size_t _capacity;//容量
static const size_t npos;
};
}
void test_string_1()
{
string s1;
string s2("manbo");
cout << s1.c_str() << endl;
cout << s2.c_str() << endl;
//模拟实现遍历
for (auto e : s2)
{
cout << e << endl;
}
cout << endl;
string::iterator it = s2.begin();
while (it != s2.end())
{
cout << *it << " ";
}
cout << endl;
}
3. 基础函数的实现
实现在字符串一些基础函数函数,如push_back、insert函数等
关于string类的所以函数详情见 string类 ,这里我们实现的有在指定位置插入字符/字符串,在指定位置删除字符/字符串,查找指定字符/字符串的位置,模拟实现比较两字符串(ASCII码),重载流插入流提取,在容量不足时扩容
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include
#include
#include
#include
#include
using namespace std;
namespace bit
{
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
无参构造函数
//string()
// :_str(new char[1] {'\0'})
// ,_size(0)
// ,_capacity(0)
//{}
带参构造
//string(const char* str)
//{
// //capacity 中不包含'/0',所以在开辟_str所需空间时才会多开一个存储'\0'
// _size = strlen(str);
// _capacity = _size;
// str = new char[_capacity + 1];
// strcpy(_str, str);//将str拷贝给_str
//}
//全缺省构造函数,上面二者合并
string(const char* str = "")
{
//capacity 中不包含'/0',所以在开辟_str所需空间时才会多开一个存储'\0'
_size = strlen(str);
_capacity = _size;
str = new char[_capacity + 1];
strcpy(_str, str);//将str拷贝给_str
}
//拷贝构造函数
string(const string& s)//深拷贝
{
_str = new char[s._capacity + 1];//多开一个空间存储'/0'
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
//析构函数
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
const char* c_str() const
{
return _str;
}
//返回长度
size_t size()
{
return _size;
}
//返回容量
size_t capacity()
{
return _capacity;
}
//清除数据不缩小容量
void clear()
{
_str[0] = '\0';
_size = 0;
}
//下标实现[] 返回引用可读可写
char& operator[](size_t pos)
{
assert(pos < _size);//防止越界
return _str[pos];
}
//只读类型
char& operator[](size_t pos) const
{
assert(pos < _size);//防止越界
return _str[pos];
}
private:
char* _str;//字符串数组
size_t _size;//长度
size_t _capacity;//容量
static const size_t npos;
};
}
3.1 增删查改
插入:insert、append、push_back、operator+=
删除:erase
扩容:reserve
查找:find
取出指定长度指定位置字符串:substr
插入:
1. insert
insert 函数是在指定位置插入字符/字符串,模拟实现的思路是讲指定位置之后的字符均向后移动所需插入字符/字符串长度,使其预留出来的长度刚好满足插入指定字符/字符串,最终完成插入
需要注意的是:在容量不够时需要扩容,并且需要根据实际大小来扩容,还要注意移动数据时不能出现下标越界,例如代码中注释的情况,C语言中的隐式类型转换可能导致下标越界
void string::insert(size_t pos, char ch)
{
assert(pos <= _size);
//空间不够就会扩容
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
//依次挪动数据
//size_t end = _size;
//while ((int)pos <= end)//防止隐式类型转换将无符号转变为有符号,导致end为-1时仍然在比较
//{
// _str[end + 1] = _str[end];
// end--;
//}
size_t end = _size + 1;
while (pos < end)//解决方法二
{
_str[end] = _str[end - 1];
end--;
}
_str[pos] = ch;
_size++;
}
void string::insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
//扩容,小于两倍按二倍扩容,大于二倍需要多少扩容多少
if (len + _size > _capacity)
{
reserve((len + _size) > 2 * _capacity ? (len + _size) : 2 * _capacity);
}
size_t end = _size + len;
while (end > pos + len - 1)
{
_str[end] = _str[end - len];
end--;
}
for (size_t i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}
_size += len;
}
2. append
append 函数功能是在字符串末尾添加字符串,所以直接扩容后拷贝所要添加的字符串即可
void string::append(const char* str)
{
size_t len = strlen(str);
if ((len + _size) > _capacity)
{
//如果所需空间比原空间容量两倍还大那么需要多少开多少,否则按照原空间容量的二倍扩容
reserve((len + _size) > _capacity * 2 ? (len + _size) : _capacity * 2);
}
strcpy(_str + _size, str);//直接在结尾拷贝输入的字符串即可
_size += len;
}
3. operator+=
operator+=就是直接在原有字符串后添加字符/字符串,可以复用push_back 函数实现
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
4. push_back
push_back函数是尾插函数,需要注意的是,在插入完成之后需要在末尾添加'\0',以保证判断字符串结束,还要注意扩容
void string::push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
删除
erase 函数是在指定位置删除指定长度字符/字符串,需要注意的是,当给定长度超过原字符串长度,那么就直接删除剩余字符即可;如果不超出那么就直接覆盖即可
void string::erase(size_t pos, size_t len)
{
assert(pos < _size);
if (len >= _size + pos)
{
_str[pos] = '\0';
_size = pos;
}
else
{
for (size_t i = pos + len; i < _size; i++)
{
_str[i - len] = _str[i];
}
_size -= len;
}
}
扩容
reserve 函数的目的是保证时刻的容量都是充足的,多开一个空间是为了存储'\0'
const size_t npos = -1;
void string::reserve(size_t n)
{
if (n > _capacity)
{
//创建新数组,拷贝原数组数据,释放原数组,将原数组指向新数组
char* tmp = new char[_capacity + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
查找
find 函数的目的是查找指定字符/字符串,如果匹配则返回其下标/起始位置下标,不匹配返回npos(默认值-1)
size_t string::find(char ch, size_t pos)
{
assert(pos < _size);
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
return npos;
}
}
size_t string::find(const char* str, size_t pos)
{
assert(pos < _size);
const char* ptr = strstr(_str + pos, str);
if (ptr == nullptr)
{
return npos;
}
else
{
//指针相减返回下标
return ptr - _str;
}
}
取出指定长度指定位置字符串
substr 函数是在函数内部创建一个名为 sub 的 string 类,然后拷贝需要取出的元素即可,需要注意的是,不能使用默认构造函数,因为默认构造函数是浅拷贝,要自己写一个构造函数以达到深拷贝的目的
//注意此时为浅拷贝,需要显式定义拷贝构造函数使其成为深拷贝
string string::substr(size_t pos, size_t len)
{
assert(pos < _size);
//如果超出字符串范围则更新len的值
if (len > _size - pos)
{
len = _size - pos;
}
string sub;
sub.reserve(len);
for (size_t i = 0; i < len; i++)
{
sub += _str[pos + i];
}
return sub;
}
3.2 比较字符串
通过重载 >、<、>=、<=、==、!= 来实现比较两字符串
这里使用了 strcmp 函数首先实现两个基本函数,之后的函数复用即可
//先实现小于和等于,之后的函数可以不断复用即可
bool operator<(const string& s1, const string& s2)
{
//strcmp 直接比较两字符串,根据ASCII码来比较
return strcmp(s1.c_str(), s2.c_str()) < 0;
}
bool operator==(const string& s1, const string& s2)
{
return strcmp(s1.c_str(), s2.c_str()) == 0;
}
bool operator>(const string& s1, const string& s2)
{
return !(s1 <= s2);
}
bool operator<=(const string& s1, const string& s2)
{
return s1 < s2 || s1 == s2;
}
bool operator>=(const string& s1, const string& s2)
{
return !(s1 < s2);
}
bool operator!=(const string& s1, const string& s2)
{
return !(s1 == s2);
}
3.3 流提取与流插入
通过重载 >> 与 << 来实现 string 类的流提取与流插入
流插入直接使用范围 for 来逐次插入即可,要注意的是流提取,cin 不能识别空格与换行,需要使用 get 函数逐个插入,并且要注意扩容次数不宜太多,所以这里模拟了一个缓冲区,创建了一个 buff 数组,直接先插入 buff 数组,最后将 buff 数组插入到原字符串即可
ostream& operator<<(ostream& out, const string& str)
{
for (auto ch : str)
{
out << ch;
}
return out;
}
//istream& operator>>(istream& in, string& str)
//{
// str.clear();
// char ch;
// //in>>ch;cin默认提取不到换行与空格,所以使用 get 函数逐个字符提取
// ch = in.get();
// while (ch != ' ' && ch != '\n')
// {
// str += ch;
// ch = in.get();
// }
// return in;
//}
//减少扩容次数
istream& operator>>(istream& in, string& str)
{
str.clear();
const int N = 256;
char buff[N];
int i = 0;
char ch;
//in>>ch;cin默认提取不到换行与空格,所以使用 get 函数逐个字符提取
ch = in.get();
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == N - 1)
{
buff[i] = '\0';
str += buff;
i = 0;
}
ch = in.get();
}
if (i > 0)//当buff没有放满时
{
buff[i] = '\0';
str += buff;
}
return in;
}