字符串 - C++引用 (cplusplus.com)这里给出标准官方的string实现,可以看到设计还是较为复杂的,有成员函数,迭代器,修饰符,容量,元素访问,字符串操作等,将字符尽可能的需求都设计出来,我们这里实现string中比较常用且重要的。
成员变量
private:char* _str;size_t _size;size_t _capacity;
public:const static size_t npos;//声明一个npos 表示返回找不到的下标 或从-1的下一个开始寻找
目录
1.迭代器实现
2.成员函数
空构造
有参构造
拷贝构造
析构
重载=
3.字符串操作
c_str
find
substr
5.修饰符
push_back
append
operator+=
insert
erase
6.容量
reserve
size
capacity
resize
7.元素访问
重载下标引用操作符
8.运算符重载
重载<
重载==
重载<=
重载>
重载>=
重载!=
重载输入流
重载输出流
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;}//范围forfor (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];//多开一个给\0strcpy(_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到 nposMystring 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();//一个个拿字符,包括空格与\nwhile (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;
}