目录
- 前言
- 类的属性
- 构造函数
- 构造函数
- 析构函数
- 拷贝构造函数
- 操作符重载
- 赋值操作符重载
- 比较操作符重载
- 获取字符串长度
- 下标访问
- 迭代器
- 尾插一个字符
- 追加字符串
- 字符串清空
- 字符串交换
- 以C的方式返回字符
- 判断字符串是否为空
- 修改长度
- 指定大小扩容
- 查找字符
- 查找子串
- 指定位置插入字符
- 指定位置插入字符串
- 删除指定区间
- 总结:
前言
string类,想必大家都不陌生,这是一个用来管理字符串的类。让我们使用字符串时更方便,更遍历。所以今天我们就来简单的实现一下string类。
类的属性
我们要管理一个字符串,那么首先得有一个字符串。所以我们用char* 类型的指针,来指向一个块存储字符串的地址。_size则记录字符串的长度,_cacpcity 为字符串的空间容量
private:char* _str;size_t _size;size_t _cacpcity;
构造函数
构造函数
我们在实例化一个string对象的时候,可以string s("hello world");
直接创建,也可以string s();
使它初始时为空,所以我们可以用缺省的构造函数。如果不传参数,那么默认初始化为空。
//缺省的构造函数 string(const char* str = ""): _size(strlen(str)),_cacpcity(_size){//开辟一块内存_str = new char[_cacpcity + 1];//有效容量是cacpcity,要多一个用来存放\0strcpy(_str, str);}
析构函数
析构函数我们只需要释放 _str指向的空间即可。
//析构函数~string(){delete[] _str;_str = nullptr;_size = _cacpcity = 0;}
拷贝构造函数
拷贝构造,就是拷贝一个字符串 例如:string s1("hello world"); string s2(s1);
。所以我们开辟一块与要拷贝字符串一样大小的空间,再把它一一复制给新字符串即可。
//拷贝构造string(const string& s):_size(s._size), _cacpcity(s._cacpcity){//开辟一块和s一样的空间_str = new char[_cacpcity+1];strcpy(_str, s._str);}
操作符重载
赋值操作符重载
如果我们想用 = 操作符来给字符串赋值。那我们可以重载 = 操作符。
//赋值操作符重载string& operator=(const string& s){//如果不是自己给自己赋值if (this != &s){//创建一块新空间char* tmp = new char[s._cacpcity+1];//拷贝strcpy(tmp, s._str);//销毁旧空间delete[] _str;_str = tmp;_size = s._size;_cacpcity = s._cacpcity;}return *this;}
当然也可以复用拷贝构造函数。
比较操作符重载
<重载
//字符串比较函数重载bool operator<(const string& s){return strcmp(_str, s._str) < 0;}
== 重载
bool operator==(const string& s){return strcmp(_str, s._str) == 0;}
<= 重载
bool operator<=(const string& s){return (*this < s) || (*this == s);}
>重载
bool operator>(const string& s){return !((*this) <= s);}
>=重载
bool operator>=(const string& s){return !(*this < s);}
!=重载
bool operator!=(const string& s){return !(*this == s);}
获取字符串长度
直接返回_size 即可
//获取长度size_t size(){return _size;}
下标访问
字符串也可以进行下标访问,所以我们重载[]即可
//下标访问char& operator[](size_t pos){return _str[pos];}
当然,如果访问的是const修饰的字符串,那我们只能读,不能写。
//只读const char& operator[](size_t pos) const{return _str[pos];}
迭代器
//定义2个迭代器,一个是可读可写,一个是只读typedef char* iterator;typedef const char* const_iterator;
//迭代器开始位置iterator begin(){return _str;}const_iterator begin() const{return _str;}//迭代器末尾位置iterator end(){return _str + _size;}const_iterator end()const{return _str + _size;}
尾插一个字符
就是在字符串末端插入一个字符,我们只需要在_size的位置插入,随后_size++即可。不过要保证容量必须充足。
//尾插一个字符void push_back(char c){//检查容量if (_size == _cacpcity){AddCacpcity(_cacpcity == 0 ? 15 : _cacpcity * 2);}_str[_size] = c;_size++;_str[_size] = '\0';}
扩容函数
void AddCacpcity(size_t newCacpcity){//末尾位置留一个\0,所以+1char* str = new char[newCacpcity + 1];//旧字符串的内容拷贝到新字符串strcpy(str, _str);//销毁旧字符串delete[] _str;_str = str;_cacpcity = newCacpcity;}
追加字符串
如果想在尾部追加字符串的话,我们可以实现一个 append()函数。
//追加一个字符串void append(const char* str){//检查容量if (_cacpcity < (_size)+strlen(str)){AddCacpcity(_size + strlen(str));}//直接在末尾的位置加上strstrcpy(_str + _size, str);_size += strlen(str);}
然后我们可以重载 +=运算符,拿来复用 append()函数。
string& operator+=(const char* str){append(str);return *this;}
然后我们重载 +运算符,但需要注意的是,+运算符不改变当前字符串,所以我们要值返回。
//+重载string operator+(const char* str){string s(*this);s += str;return s;}
字符串清空
直接把第一个字符改成\0即可
//清空void clear(){_str[0] = '\0';_size = 0;}
字符串交换
我们用std命名空间里面的swap函数,对每个成员进行交换。
//字符串交换void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_cacpcity, s._cacpcity);}
以C的方式返回字符
c语言的字符串其实就是一个char*指针,遇到\0结束,所以我们直接返回str就可以了。
//以c的方式返回字符串const char* c_str()const{return _str;}
判断字符串是否为空
直接判断第一个元素是否是 \0
//判断字符串是否为空bool empty()const{return _str[0] == '\0';}
修改长度
修改长度我们要分多种情况,一般长度减少时,我们不改变现有容量。容量不够时,我们才增容。 如果 长度减少,或者不变,我们只需要把 减少的新长度给_size,然后把当前位置变成\0。如果是增加长度,我们要考虑容量,不够的话需要增容,然后memset函数把增容的部分改成你指定的字符(默认\0)。
//修改长度void resize(size_t n, char c = '\0'){//如果修改的值比当前长度小if (n <= _size){//截断_size = n;_str[_size] = '\0';}//如果修改的值比当前长度大else{//扩容if(n > _cacpcity){AddCacpcity(n);}//从size位置到n的位置设置为cmemset(_str + _size, c, n - _size);//最后位置填上\0_size = n;_str[_size] = '\0';}}
指定大小扩容
之前写过一个扩容函数,直接把指定的大小传过去即可。还是老规矩,减少不处理。
//指定容量。只增加,减少不处理void reserve(size_t n){if (n > _cacpcity){AddCacpcity(n);}}
查找字符
该查找只会找到从指定位置开始,第一个出现的字符。如果要查找第二个,那么就在第一个字符的后面开始查找。
// 返回c在string中第一次出现的位置size_t find(char c, size_t pos = 0) const{for (int i = pos; i < _size; i++){if (_str[i] == c)return i;}return nops;}
nops是一个无符号的数,代表找不到返回的值。
static const size_t nops = -1;
查找子串
C语言中有strstr函数,我们可以复用。
// 返回子串s在string中第一次出现的位置size_t find(const char* s, size_t pos = 0) const{char* tmp = strstr(_str, s);if (tmp == NULL)return nops;return tmp - _str;}
指定位置插入字符
只需要把pos位置后面的字符都往后移动一格,随后把字符放进pos位置。
// 在pos位置上插入字符c/字符串str,并返回该字符的位置string& insert(size_t pos, char c){//判断容量if (_size == _cacpcity){AddCacpcity(_cacpcity == 0 ? 15 : _cacpcity * 2);}//pos位置后往后移size_t end = _size + 1;while (pos < end){_str[end] = _str[end - 1];end--;}_str[pos] = c;_size++;return *this;}
指定位置插入字符串
插入字符是都往后移动一格,插入字符串那就是把pos位置后面的字符都像后 移动字符串的长度格。然后把字符串从pos位置开始写入。
//插入字符串string& insert(size_t pos, const char* str){size_t len = strlen(str);//判断容量if (_cacpcity < (len + _size)){AddCacpcity(len + _size);}//移动len格size_t end1 = _size+1;size_t end2 = _size + len ;while (pos < end1 ){_str[end2] = _str[end1-1] ;end1--;end2--;}int i = pos;while (*str){_str[i++] = *str++;}_size += len;return *this;}
删除指定区间
从pos位置开始,删除len个空间。那么我们需要先判断 len是否大于pos后面的长度,如果大于那就是后面全部删除,那么我们只需要把pos位置置空成\0即可。如果不大于就说明在中间删除,那么就从pos的第len个位置开始往pos后面的位置覆盖,覆盖到\0结束。
// 删除string& erase(size_t pos, size_t len){//如果要删除的长度大于后面的剩余长度if (len >= _size - pos){len = _size - pos;_size -= len;_str[pos] = '\0';return *this;}//把后面的往前移,覆盖式删除size_t begin = pos+len;while (_str[begin]){_str[begin-len] = _str[begin];begin++;}_size -= len;return *this;}
全部代码:
namespace wyl
{class string{public:typedef char* iterator;typedef const char* const_iterator;//缺省的构造函数 string(const char* str = ""): _size(strlen(str)),_cacpcity(_size){//开辟一块内存_str = new char[_cacpcity + 1];strcpy(_str, str);}//析构函数~string(){delete[] _str;_str = nullptr;_size = _cacpcity = 0;}//拷贝构造string(const string& s):_size(s._size), _cacpcity(s._cacpcity){//开辟一块和s一样的空间_str = new char[_cacpcity+1];strcpy(_str, s._str);}//赋值操作符重载string& operator=(const string& s){//如果不是自己给自己赋值if (this != &s){//创建一块新空间char* tmp = new char[s._cacpcity+1];//拷贝strcpy(tmp, s._str);//销毁旧空间delete[] _str;_str = tmp;_size = s._size;_cacpcity = s._cacpcity;}return *this;}//获取长度size_t size(){return _size;}//下标访问char& operator[](size_t pos){return _str[pos];}//只读const char& operator[](size_t pos) const{return _str[pos];}//迭代器开始位置iterator begin(){return _str;}const_iterator begin() const{return _str;}//迭代器末尾位置iterator end(){return _str + _size;}const_iterator end()const{return _str + _size;}// 扩容void AddCacpcity(size_t newCacpcity){char* str = new char[newCacpcity + 1];strcpy(str, _str);delete[] _str;_str = str;_cacpcity = newCacpcity;}//尾插一个字符void push_back(char c){//检查容量if (_size == _cacpcity){AddCacpcity(_cacpcity == 0 ? 15 : _cacpcity * 2);}_str[_size] = c;_size++;_str[_size] = '\0';}string& operator+=(char c){push_back(c);return *this;}//追加一个字符串void append(const char* str){if (_cacpcity < (_size)+strlen(str)){AddCacpcity(_size + strlen(str));}strcpy(_str + _size, str);_size += strlen(str);}string& operator+=(const char* str){append(str);return *this;}//+重载string operator+(const char* str){string s(*this);s += str;return s;}string operator+(const string& str){string s(*this);s += str._str;return s;}//清空void clear(){_str[0] = '\0';_size = 0;}//字符串交换void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_cacpcity, s._cacpcity);}//以c的方式返回字符串const char* c_str()const{return _str;}//判断字符串是否为空bool empty()const{return _str[0] == '\0';}//修改长度void resize(size_t n, char c = '\0'){//如果修改的值比当前长度小if (n <= _size){//截断_size = n;_str[_size] = '\0';}//如果修改的值比当前长度大else{//扩容if(n > _cacpcity){AddCacpcity(n);}//从size位置到n的位置设置为cmemset(_str + _size, c, n - _size);//最后位置填上\0_size = n;_str[_size] = '\0';}}//指定容量。只增加,减少不处理void reserve(size_t n){if (n > _cacpcity){AddCacpcity(n);}}//字符串比较函数重载bool operator<(const string& s){return strcmp(_str, s._str) < 0;}bool operator<=(const string& s){return (*this < s) || (*this == s);}bool operator>(const string& s){return !((*this) <= s);}bool operator>=(const string& s){return !(*this < s);}bool operator==(const string& s){return strcmp(_str, s._str) == 0;}bool operator!=(const string& s){return !(*this == s);}// 返回c在string中第一次出现的位置size_t find(char c, size_t pos = 0) const{for (int i = pos; i < _size; i++){if (_str[i] == c)return i;}return nops;}// 返回子串s在string中第一次出现的位置size_t find(const char* s, size_t pos = 0) const{char* tmp = strstr(_str, s);if (tmp == NULL)return nops;return tmp - _str;}// 在pos位置上插入字符c/字符串str,并返回该字符的位置string& insert(size_t pos, char c){//判断容量if (_size == _cacpcity){AddCacpcity(_cacpcity == 0 ? 15 : _cacpcity * 2);}//pos位置后往后移size_t end = _size + 1;while (pos < end){_str[end] = _str[end - 1];end--;}_str[pos] = c;_size++;return *this;}//插入字符串string& insert(size_t pos, const char* str){size_t len = strlen(str);//判断容量if (_cacpcity < (len + _size)){AddCacpcity(len + _size);}//移动len格size_t end1 = _size+1;size_t end2 = _size + len ;while (pos < end1 ){_str[end2] = _str[end1-1] ;end1--;end2--;}int i = pos;while (*str){_str[i++] = *str++;}_size += len;return *this;}// 删除string& erase(size_t pos, size_t len){//如果要删除的长度大于后面的剩余长度if (len >= _size - pos){len = _size - pos;_size -= len;_str[pos] = '\0';return *this;}//把后面的往前移,覆盖式删除size_t begin = pos+len;while (_str[begin]){_str[begin-len] = _str[begin];begin++;}_size -= len;return *this;}private:char* _str;size_t _size;size_t _cacpcity;static const size_t nops = -1;};
}
总结:
string类的细节还有很多,在这里只能简单实现一下。在实现的过程中需要注意的几点。
1.避免内存越界,否则析构时销毁会出错。
2.释放new出来的内存,避免内存泄漏。
3.需要深拷贝,否则会出现析构多次的情况。