目录
1. string成员变量
2. 构造和析构
(1)构造函数
(2)拷贝构造
(3)析构函数
3. reserve和resize
4. find
5. insert和erase
6. push_back和append
7. 重载
(1)方括号重载
(2)比较大小
(3)流提取和流插入
(4)尾插
(5)赋值运算符
8. clear
【代码汇总】
string是C++里面对于字符串处理非常方便的容器,不仅支持方括号加下标的访问,还内置各种接口,用起来嘎嘎爽。
前置注意事项:
在模拟实现的时候,我们需要自己开一块命名空间,避免跟库里的string发生冲突
需要对类和对象的相关知识有一定的了解,可翻阅我之前的文章
1. string成员变量
有点类似顺序表的实现(可看这篇文章),有一个size(数组大小)和capacity(数组容量),而string的本质是字符数组,有char*来记录数据
namespace myString{class string{public: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; }const_iterator begin() const{return _str;}const_iterator end() const{return _str + _size;} // 获取大小和容量的接口 // 这里要用const修饰成员函数,保证const对象也可以调用size_t size() const{return _size;}size_t capacity() const{return _capacity;} bool empty() const { return _size == 0; } //获取C类型的字符串 const char* c_str() const { return _str; }private:char* _str = nullptr;size_t _size = 0;size_t _capacity = 0; public: static const size_t npos; };}
【补充】这里的npos一般赋值为-1,用来表示字符串的末尾,因为-1在无符号整型中为2^32-1,大小约为4G,所以一般不会达到,所以用来表示末尾,通常作缺省值使用。或者在查找不到需要的字符(字符串)时作为返回值表示查找失败。
2. 构造和析构
(1)构造函数
string(const char* str = "") :_size = strlen(str){_capacity = _size; _str = new char[_capacity + 1]; strcpy(_str, str); }
(2)拷贝构造
注意要新开一块空间再将数据拷贝过来,否则两个指针指向同一块空间时,当其中一个发生改变另一个也会变,而且在析构时同一块空间析构两次会引发程序的崩溃,而这也就是我们常说的浅拷贝。
而深拷贝就是先开一块新的空间,再将数据依次拷过去,可以有效地避免上述情况的发生。
// 版本一string(const string& s){ _str = new char[s.capacity() + 1]; strcpy(_str, s._str); _size = s._size;_capacity = s._capacity;}
版本二复用了(1)构造函数,同时写了一个swap函数
Q:有小伙伴就问了,为什么不直接用库里的swap呢?
可以看到这里要调用一次拷贝构造,两次赋值,还得调用析构把c析构掉
代价太大,所以我们不如自己实现一个
这里要用std限定住范围是因为swap会先从就近的swap开始找,但是由于参数个数不匹配,所以就会报错。
// 版本二void swap(string& s){ std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity);}string(const string& s){ string tmp(s._str); swap(tmp);}
(3)析构函数
~string(){ delete[] _str; _str = nullptr; _size = _capacity = 0;}
3. reserve和resize
【区分reserve和resize】
reserve:只是开空间,而且如果请求的空间大小小于已有大小,不会缩容
resize:若扩大,并赋值给对应的插入值,默认为'\0';若给的大小小于size则缩容到对应大小
void reserve(size_t n){ if(n > _capacity) { char* tmp = new char[n + 1]; strcpy(tmp, _str); delete[] _str; _str = tmp; _capacity = n; }}void resize(size_t n, char c = '\0'){ if(n <= _size) { _str[n] = '\0'; _size = n; } else { reserve(n); for(size_t i = _size; i < n; i++) _str[i] = c; _str[n] = '\0'; _size = n; }}
4. find
查找不需要修改对应的成员函数,所以使用const成员函数
strstr返回的就是在某个位置之后找某个对应的子串,返回首元素的地址
// 返回c在string中第一次出现的位置size_t find (char c, size_t pos = 0) const{ assert(pos < _size); for(size_t i = pos; i < _size; i++) { if(_str[i] == c) return i; } return npos; }// 返回子串s在string中第一次出现的位置size_t find (const char* s, size_t pos = 0) const{ assert(pos < _size); const char* p = strstr(_str + pos, s); if(p) return p - _str; // 指针减指针得两者之间的元素个数 else return npos;}
5. insert和erase
void insert(size_t pos, char c){ if(_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); } int end = _size + 1; while(end > pos) { _str[end] = _str[end - 1]; end--; } _str[pos] = c; _size++;}void insert(size_t pos, const char* str){ assert(pos <= _size); size_t len = strlen(str); if(_size + len > _capacity) reserve(_size + len); size_t end = _size + len; while(end > pos + len - 1) { _str[end] = _str[end - len]; end--; } strncpy(_str + pos, str, len); _size += len;}// 删除pos位置上的元素,并返回该元素的下一个位置void erase(size_t pos, size_t len = npos){ aasert(pos < _size); if(len == npos || pos + len >= _size) { _str[pos] = '\0'; _size = pos; } else { strcpy(_str + pos, _str + pos + len); _size -= len; }}
6. push_back和append
老思路,扩容扩二倍;记得加回 '\0'
void push_back(char c){ if (_size == _capacity) reserve(_capacity == 0 ? 4 : _capacity * 2); _str[_size] = c; _str[++_size] = '\0';}
【版本二】复用insert
void push_back(char c){ insert(_size, c);}
append:不要忘记扩容!!因为传进来的是c类型字符串,所以直接使用strlen
void append(const char* str){ size_t len = strlen(str); if (_size + len > _capacity) reserve(_size + len); strcpy(_str + _size, str); _size += len;}
7. 重载
(1)方括号重载
char& operator[](size_t index){ assert(index < _size); return _str[index];}const char& operator[](size_t index) const{ assert(index < _size); return _str[index];}
(2)比较大小
注意strcmp函数是c语言的接口,需要转成const char*才能传参
bool operator==(const string& s1, const string& s2){ int ret = strcmp(s1.c_str(), s2.c_str()); return ret == 0;}bool operator<(const string& s1, const string& s2){ int ret = strcmp(s1.c_str(), s2.c_str()); return ret < 0;}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);}bool operator!=(const string& s1, const string& s2){ return !(s1 == s2);}
(3)流提取和流插入
【注意点】
a. 范围for的底层是迭代器,所以需要先定义迭代器 const begin() 和 const end() 之后才能使用
b. 这里的流提取采用了“缓冲数组”的思路,避免了开辟空间不够用的问题
ostream& operator<<(ostream& _cout, const string& s){ for (auto e : s) { _cout << e; } return _cout;}istream& operator>>(istream& _cin, bit::string& s){ s.clear(); char buff[128]; char ch = _cin.get(); size_t i = 0; while (ch != ' ' && ch != '\n') { buff[i++] = ch; if (i == 127) { buff[127] = '\0'; s += buff; i = 0; } ch = _cin.get(); } if (i > 0) { buff[i] = '\0'; s += buff; } return _cin;}// 获取一行信息(可包含空格)istream& getline(istream& _cin, string& s){ s.clear(); char ch = _cin.get(); while (ch != '\n') { s += ch; ch = _cin.get(); } return _cin;}
(4)尾插
直接复用push_back和append
string& operator+=(char c){ push_back(c); return *this;}string& operator+=(const char* str){ append(str); return *this;}
(5)赋值运算符
【版本一】
string& operator=(const string& s){ char* tmp = new char[s._capacity + 1]; strcpy(tmp, s._str); delete[] _str; _str = tmp; _size = s._size; _capacity = s._capacity; return *this;}
【版本二】
传参时,发生拷贝构造,所以交换之后不对原来的对象产生影响
string& operator=(string s){swap(s); return *this; //返回左值(支持连续赋值)}
8. clear
void clear(){ _str[0] = '\0'; _size = 0;}
【代码汇总】
namespace myString{ class string { friend ostream& operator<<(ostream& _cout, const string& s); friend istream& operator>>(istream& _cin, string& s); public: typedef char* iterator; typedef const char* const_iterator; public: string(const char* str = "") :_size(strlen(str)) { _capacity = _size; _str = new char[_capacity + 1]; strcpy(_str, str); } string(const string& s) { _str = new char[s.capacity() + 1]; strcpy(_str, s._str); _size = s._size; _capacity = s._capacity; } string& operator=(const string& s) { char* tmp = new char[s._capacity + 1]; strcpy(tmp, s._str); delete[] _str; _str = tmp; _size = s._size; _capacity = s._capacity; return *this; } ~string() { delete[] _str; _str = nullptr; _size = _capacity = 0; } // // iterator iterator begin() { return _str; } const_iterator begin() const { return _str; } iterator end() { return _str + _size; } const_iterator end() const { return _str + _size; } / // modify void push_back(char c) { if (_size == _capacity) reserve(_capacity == 0 ? 4 : _capacity * 2); _str[_size] = c; _str[++_size] = '\0'; } string& operator+=(char c) { push_back(c); return *this; } void append(const char* str) { size_t len = strlen(str); if (_size + len > _capacity) reserve(_size + len); strcpy(_str + _size, str); _size += len; } string& operator+=(const char* str) { append(str); return *this; } void clear() { _str[0] = '\0'; _size = 0; } void swap(string& s) { std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity); } const char* c_str() const { return _str; } / // capacity size_t size()const { return _size; } size_t capacity()const { return _capacity; } bool empty()const { return _size == 0; } void resize(size_t n, char c = '\0') { if (n <= _size) { _str[n] = '\0'; _size = n; } else { reserve(n); for (size_t i = _size; i < n; i++) _str[i] = c; _str[n] = '\0'; _size = n; } } void reserve(size_t n) { if (n > _capacity) { char* tmp = new char[n + 1]; strcpy(tmp, _str); delete[] _str; _str = tmp; _capacity = n; } } / // access char& operator[](size_t index) { assert(index < _size); return _str[index]; } const char& operator[](size_t index) const { assert(index < _size); return _str[index]; } / //relational operators // 返回c在string中第一次出现的位置 size_t find(char c, size_t pos = 0) const { assert(pos < _size); for (size_t i = pos; i < _size; i++) { if (_str[i] == c) return i; } return npos; } // 返回子串s在string中第一次出现的位置 size_t find(const char* s, size_t pos = 0) const { assert(pos < _size); const char* p = strstr(_str + pos, s); if (p) return p - _str; else return npos; } // 在pos位置上插入字符c/字符串str,并返回该字符的位置 void insert(size_t pos, char c) { if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); } int end = _size + 1; while (end > pos) { _str[end] = _str[end - 1]; end--; } _str[pos] = c; _size++; } void insert(size_t pos, const char* str) { assert(pos <= _size); size_t len = strlen(str); if (_size + len > _capacity) reserve(_size + len); size_t end = _size + len; while (end > pos + len - 1) { _str[end] = _str[end - len]; end--; } strncpy(_str + pos, str, len); _size += len; } // 删除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 { strcpy(_str + pos, _str + pos + len); _size -= len; } } private: char* _str; size_t _capacity; size_t _size; public: static const size_t npos; }; const size_t string::npos = -1; void swap(string& x, string& y) { x.swap(y); } bool operator<(const string& s1, const string& s2) { int ret = strcmp(s1.c_str(), s2.c_str()); return ret < 0; } bool operator==(const string& s1, const string& s2) { int ret = strcmp(s1.c_str(), s2.c_str()); return ret == 0; } 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); } bool operator!=(const string& s1, const string& s2) { return !(s1 == s2); } ostream& operator<<(ostream& _cout, const string& s) { for (auto e : s) { _cout << e; } return _cout; } istream& operator>>(istream& _cin, string& s) { s.clear(); char buff[128]; char ch = _cin.get(); size_t i = 0; while (ch != ' ' && ch != '\n') { buff[i++] = ch; if (i == 127) { buff[127] = '\0'; s += buff; i = 0; } ch = _cin.get(); } if (i > 0) { buff[i] = '\0'; s += buff; } return _cin; } istream& getline(istream& _cin, string& s) { s.clear(); char ch = _cin.get(); while (ch != '\n') { s += ch; ch = _cin.get(); } return _cin; }};
如果你能看到这里,给你点个赞!
如果觉得这篇文章不错的话,不妨点个赞支持一下,你的支持是我持续更新的动力~