字符串 - C++引用 (cplusplus.com)这里给出标准官方的string实现,可以看到设计还是较为复杂的,有成员函数,迭代器,修饰符,容量,元素访问,字符串操作等,将字符尽可能的需求都设计出来,我们这里实现string中比较常用且重要的。
成员变量
private: char* _str; size_t _size; size_t _capacity; public: const static size_t npos;//声明一个npos 表示返回找不到的下标 或从-1的下一个开始寻找
1.迭代器实现
这里我们实现了常量迭代器与正常的迭代器,反向迭代器并未实现。对于string这里的迭代器我们可以看到本质上是一个char *类型的指针,在某些地方可能不是指针,且它的用法也是与指针如出一撤。这里对于反向迭代器后面再实现。
typedef char* iterator;//迭代器 const typedef 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; };
在这里, 我们将char*定义为一个新的类型,并且给出它的成员函数,也就是迭代器的各个位置,从而实现。利用迭代器我们可以对他进行遍历:
Mystring::iterator it = str1.begin(); while (it != str1.end()) { cout << *it ; ++it; } //范围for for (auto tmp : str1) { cout << tmp ; }
同时其实本质上范围for的底层就是利用迭代器实现的,编译器在遇到范围for这样的写法时就会将这一段代码替换成下面迭代器实现的这样的遍历。
2.成员函数
空构造
实际上并不需要实现,我们这里给出它的实现,是了解对于空字符串的构造函数,虽说是空字符串,但不可直接赋值nullptr,否则后面实例化对象调成员函数时就是解引用空指针,空字符串,故这里会报错,实际上实现会给几个字节空间,放\0
Mystring():_str(new char[1]),_capacity(0),_size(0) { }
有参构造
//有参构造 Mystring(const char* str="") : _size(strlen(str)), _capacity(_size)//缺省值直接给"",不给"\0",原先就有\0 { //注意这里的字符串利用深拷贝,且初始化顺序遵循声明顺序 _str = new char[_capacity + 1];//多开一个给\0 strcpy(_str, str); }
拷贝构造
还是老问题,直接的赋值会造成两次析构,故这里需要进行深拷贝。
//拷贝构造 //之前讲过,浅拷贝会析构两次,深拷贝解决 Mystring(const Mystring& s) { _str = new char[s._capacity + 1]; memset(_str, 0, s._capacity); strcpy(_str, s._str); this->_size = s._size; this->_capacity = s._capacity; }
析构
释放及初始化
//析构 ~Mystring() { delete [] _str; _str = nullptr; _capacity = _size = 0; }
重载=
// 赋值重载 Mystring& operator=(const Mystring& s) { if (*this != s) { char* temp = new char[s._capacity+1]; strcpy(temp, s._str); delete []_str; _str = temp; this->_size = s._size; this->_capacity = s._capacity; } return *this; }
这里的赋值重载区分拷贝构造:
用现有对象初始化定义的对象---------拷贝构造
用现有对象赋值给现有对象------------赋值
3.字符串操作
这里实现几个比较重要的,常用的。
c_str
const char* c_str()const { return _str; }
find
这里给出了两种实现,字符寻找与字符串寻找
//寻找某个字符,返回其下标位置 size_t find(char ch,size_t pos=0) { for (size_t i = pos; i < _size; i++) { if (ch == _str[i]) { return i; } } return npos;//找不到,返回-1 } //寻找某个字符串,返回其相同的个数 size_t find(const char* ch, size_t pos = 0) { //暴力匹配 const char* tmp = strstr(_str+pos, ch); if (tmp!= nullptr) { return tmp - _str;//指针相减,找到个数 } else { return npos; } }
substr
//从pos位置处取n个字符,或n到 npos Mystring substr(size_t pos, size_t len=npos) { assert(pos < _size); Mystring s; size_t end = pos + len; if (len == pos || len + pos >= _size) { len = _size - len; end = _size; } s.reserve(len); for (size_t i = pos; i <pos+len; i++) { s += _str[i];//这里利用重载后的+=,本质上就是尾插 } return s; }
5.修饰符
push_back
//尾插字符 void push_back(char ch) { //尾插的操作很简单,但是要考虑到扩容的问题 if (_size == _capacity) { reserve(_capacity=0?4:_capacity * 2);//为了避免空间是0,扩容失败 } _str[_size] = ch; ++_size; _str[_size] = '\0'; }
append
//尾插字符串 void append(const char*ch) { size_t num = strlen(ch); if (num + _size > _capacity) { reserve(num + _size); } strcpy(_str + _size, ch); _size += num; //注意这里直接运用strcpy拷贝过来,连同\0一起,之后变为新大小 }
operator+=
这里重载的+=是利用了尾插的实现,故也作为字符串修饰
也是两种实现,+=字符或字符串
Mystring& operator+=(const char ch) { push_back(ch); return *this; } Mystring& operator+=(const char* ch) { append(ch); return *this; }
insert
也是有两种实现方式
注意注释的提示:若这里想要进行头插,此时end>=0。只有当end--到负数才停止,而size_t 无法去判断正负数,程序崩溃,解决方法,将类型转换为int ,或者修改后移顺序,_str[end] = _str[end-1];去掉条件=。
其次就是对于扩容需要考虑空间为0的情况。
//某个位置插入字符 void insert(size_t pos,char ch) { assert(pos < _size); if (_size == _capacity) { reserve(_capacity = 0 ? 4 : _capacity * 2);//为了避免空间是0,扩容失败 } int end = _size; //pos位置处后移 while (end>=(int)pos) { //后移 _str[end + 1] = _str[end]; --end; } //插入 _str[pos] = ch; } //某个位置插入字符串 void insert(size_t pos,char *ch) { size_t len = strlen(ch); if (_size +len>_capacity) { reserve(_capacity = 0 ? 4 : _capacity * 2);//为了避免空间是0,扩容失败 } size_t end = _size; //pos位置处后移 while (end >= pos) { //后移 _str[end + len] = _str[end]; --end; } //插入 strncpy(_str+pos, ch,len); _size += len; }
erase
删除pos位置后的npos个字符,这里的npos给的是缺省值,再没参数的情况下为-1,也就是pos后的全部删除。
//删除 void erase(size_t pos,size_t len= npos) { assert(pos <= _size); if (len == npos || pos + len >= _size) { _str[pos] = '\0'; _size = pos; } else { size_t begin = pos + len; while (begin <= _size) { _str[begin- len] = _str[begin ]; ++begin; } _size -= len; } }
6.容量
reserve
//扩容 void reserve(size_t n) { if (n > _size) { char* tmp = new char[n + 1];//这里还是留一个\0位置 strcpy(tmp, _str); delete[]_str; _str = tmp; _capacity = n; } }
size
//返回大小 size_t size()const { return _size; }
capacity
//返回容量 size_t capacity()const { return _capacity; }
resize
//初始化大小为n,且可以赋值之后的 void resize(size_t n,char c='\0') { if (n <= _size) { //删除 _str[n] = '\0'; _size = n; } else { //扩容 reserve(n); //初始化值 while (_size < n) { _str[_size] = c; _size++; } //末尾给\0 _str[_size] = '\0'; } }
7.元素访问
重载下标引用操作符
const char& operator[](int pos)const { assert(pos < _size); return _str[pos]; }
8.运算符重载
对于这里的比较运算符,通过实现<与==,再取反实现> ,>=,!=.
重载<
bool operator<(const Mystring &s1) { return strcmp(this->_str, s1._str) <0; }
重载==
bool operator==(const Mystring& s) { return strcmp(this->_str, s._str) == 0; }
重载<=
bool operator<=(const Mystring& s) { return *this < s || *this == s;//由于<与=已实现,直接用 }
重载>
bool operator>(const Mystring& s) { return !( *this <=s) ;//取反 }
重载>=
bool operator>=(const Mystring& s) { return !(*this < s);//取反 }
重载!=
bool operator!=(const Mystring& s) { return!(*this==s); }
重载输入流
//流插入 istream& operator>>(istream& in, Mystring& s) { char ch; ch = in.get();//一个个拿字符,包括空格与\n while (ch != ' ' && ch != '\n') { s += ch; ch = in.get(); } return in; }
重载输出流
//流提取 ostream& operator<<(ostream& out, const Mystring& s) { for (size_t i = 0; i < s.size(); i++) { out << s[i]; } return out; }