朋友们、伙计们,我们又见面了,本期来给大家解读一下有关string的模拟实现,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成!
C 语 言 专 栏:C语言:从入门到精通
数据结构专栏:数据结构
个 人 主 页 :stackY、
C + + 专 栏 :C++
Linux 专 栏 :Linux
目录
1. 基本构造
2. 深拷贝
2.1 传统写法
2.2 现代写法
2.3 写时拷贝(了解)
2. 容量相关接口
2.1 size、capacity、clear、empty
2.2 reserve
2.3 resize
3. 迭代器
3.1 operator[ ]
3.2 begin、end
4. 修改相关接口
4.1 push_back、c_str、npos
4.2 operator+=、append
4.3 insert
4.4 erase
5. 运算符重载
6. 流插入和流提取
6.1 流提取
6.2 流插入
7. 完整代码
为了区别库中本来的string,我们可以将模拟实现的string封装在我们自己的命名空间中,string的基本构造构造函数、拷贝构造、析构函数。
头文件:string.h
#pragma once #include
using namespace std; // //模拟实现string,将实现的string封装在自己的命名空间中 namespace ywh { class string { public: //构造函数 string(const char* str = "") :_size(strlen(str)) ,_capacity(_size) { //根据空间大小为_str开辟空间 _str = new char[_capacity + 1]; //多开辟一个存放'\0' //将str拷贝至_str strcpy(_str, str); } //析构 ~string() { //匹配使用 delete[] _str; _str = nullptr; _size = _capacity = 0; } private: char* _str; size_t _size; //有效元素个数 size_t _capacity; //空间大小 }; }
在之前的文章中提到过关于浅拷贝的问题,我们不写编译器默认生成的拷贝构造就是浅拷贝,但是string存在资源的创建的,所以我们得自己实现一个深拷贝的拷贝构造。
如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显示给出。一般情况都是按照深拷贝方式提供。
#pragma once #include
using namespace std; // //模拟实现string,将实现的string封装在自己的命名空间中 namespace ywh { class string { public: //构造函数 string(const char* str = "") :_size(strlen(str)) ,_capacity(_size) { //根据空间大小为_str开辟空间 _str = new char[_capacity + 1]; //多开辟一个存放'\0' //将str拷贝至_str strcpy(_str, str); } //析构 ~string() { //匹配使用 delete[] _str; _str = nullptr; _size = _capacity = 0; } //传统写法 //拷贝构造 string(const string& s) { _str = new char[s._capacity + 1]; _size = s._size; _capacity = s._capacity; strcpy(_str, s._str); } //赋值运算符重载 string& operator=(const string& s) { if (this != &s) { //使用一块新的空间来保存s的数据 char* tmp = new char[s._capacity + 1]; strcpy(tmp, s._str); delete[] _str; _str = tmp; //直接将_str重新指向tmp _size = s._size; _capacity = s._capacity; } return *this; } private: char* _str; size_t _size; //有效元素个数 size_t _capacity; //空间大小 }; } 传统写法的代码不容易看懂而且还比较麻烦,我们可以尝试新的方法。
#pragma once #include
using namespace std; // //模拟实现string,将实现的string封装在自己的命名空间中 namespace ywh { class string { public: //构造函数 string(const char* str = "") :_size(strlen(str)) ,_capacity(_size) { //根据空间大小为_str开辟空间 _str = new char[_capacity + 1]; //多开辟一个存放'\0' //将str拷贝至_str strcpy(_str, str); } //析构 ~string() { //匹配使用 delete[] _str; _str = nullptr; _size = _capacity = 0; } /* //传统写法 //拷贝构造 string(const string& s) { _str = new char[s._capacity + 1]; _size = s._size; _capacity = s._capacity; strcpy(_str, s._str); } //赋值运算符重载 string& operator=(const string& s) { if (this != &s) { //使用一块新的空间来保存s的数据 char* tmp = new char[s._capacity + 1]; strcpy(tmp, s._str); delete[] _str; _str = tmp; //直接将_str重新指向tmp _size = s._size; _capacity = s._capacity; } return *this; } */ //现代写法 void swap(string& s) { std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity); } //拷贝构造 string(const string& s) :_str(nullptr) ,_size(0) ,_capacity(0) { //复用构造函数 string tmp(s._str); swap(tmp); } //赋值运算符重载 string& operator=(string tmp) { //直接交换 swap(tmp); return *this; } private: char* _str; size_t _size; //有效元素个数 size_t _capacity; //空间大小 }; } 上述方法是比较简单的,但是还是一种深拷贝的情况,那么可以通过写时拷贝来进行浅拷贝。
写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。
引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。
以下接口都是在string类中实现:
2.1 size、capacity、clear、empty
//清理 void clear() { _size = 0; _str[_size] = '\0'; } //有效个数 size_t size() const { return _size; } //容量 size_t capacity() const { return _capacity; } //判空 bool empty()const { return 0 == _size; }
2.2 reserve
当要扩容的空间比原来的空间大时才可以进行扩容,重新开辟一块新的空间,先将_str拷贝至新空间,然后再将_str指向的旧空间进行释放,再将_str重新指向新的空间,然后将新空间的_capacity置为新的容量即可:
//预留空间 void reserve(size_t n) { //当传递的n大于本来的空间才可以扩容 if (n > _capacity) { char* tmp = new char[n + 1]; strcpy(tmp, _str); delete[] _str; _str = tmp; _capacity = n; } }
2.3 resize
当传递的n小于原本的_size,只需要将_str中下标为n的地方置为'\0'即可,再将_size改为n,如果大于_size,那么可以复用reserve预留空间,然后将剩余的空间用指定的字符来进行填充,如果没有指定字符,默认使用'\0'来填充,然后在最后面再添加上'\0'来表示末尾。
//修改有效个数 void resize(size_t n, char ch = '\0') { //小于直接添加'\0'截止 if (n <= _size) { _str[n] = '\0'; _size = n; } else { //大于先预留空间 reserve(n); //再将指定的字符依次插入,末尾在添加'\0' while(_size < n) { _str[_size] = ch; _size++; } _str[_size] = '\0'; } }
3.1 operator[ ]
//非const operator[] char& operator[](size_t pos) { assert(pos < _size); return _str[pos]; } //const operator[] const char& operator[](size_t pos) const { assert(pos < _size); return _str[pos]; }
3.2 begin、end
typedef char* iterator; typedef const char* const_iterator; //非const iterator begin() { return _str; } iterator end() { return _str + _size; } //const const_iterator begin() const { return _str; } const_iterator end() const { return _str + _size; }
4.1 push_back、c_str、npos
#pragma once #include
#include using namespace std; // //模拟实现string,将实现的string封装在自己的命名空间中 namespace ywh { class string { public: 基本构造// //... 容量接口// //... 迭代器 //... 修改接口// //尾插 void push_back(char ch) { if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); } _str[_size] = ch; _size++; _str[_size] = '\0'; } //c格式的字符串 const char* c_str() const { return _str; } private: char* _str; size_t _size; //有效元素个数 size_t _capacity; //空间大小 public: const static size_t npos; //静态对象需要在全局定义 }; } const size_t string::npos = -1; 4.2 operator+=、append
//追加字符串 void append(const char* str) { size_t len = strlen(str); if (len + _size > _capacity) { reserve(len + _size); } strcpy(_str+_size, str); _size += len; } //+=运算符重载 string& operator+=(const char ch) { //复用尾插 push_back(ch); return *this; } string& operator+=(const char* str) { //复用append append(str); return *this; }
4.3 insert
在pos位置插入字符有两种写法:
1. end表示size位置,end类型为int(有符号整形)
当进行头部插入(pos == 0),如果end类型为size_t(无符号整型),那么end就永远不可能小于pos,如果end类型为int,那么在进行end与pos的比较时,end会进行类型提升,从有符号整型提升为无符号整型,所以为了防止类型提升,需要将pos强制转化为int
//在pos位置插入字符 void insert(size_t pos, char ch) { assert(pos <= _size); if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); } //挪动数据 int end = _size; //end类型只能为int while (end >= (int)pos) //防止类型提升,所以进行强转 { _str[end + 1] = _str[end]; end--; } _str[pos] = ch; _size++; }
2. end表示size的后一个位置,end的类型为size_t(无符号整型)
//在pos位置插入字符 void insert(size_t pos, char ch) { assert(pos <= _size); if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); } //挪动数据 size_t end = _size + 1; while (end > pos) { _str[end] = _str[end-1]; end--; } _str[pos] = ch; _size++; }
在pos位置插入字符串同样的也有两种写法,这里就不做演示了。
//在pos位置插入字符串 void insert(size_t pos, const char* str) { assert(pos <= _size); size_t len = strlen(str); if (_size + len > _capacity) { reserve(len + _size); } //挪动数据 int end = _size; //end类型只能为int while (end >= (int)pos) //防止越界进行强转,防止类型提升 { _str[end + len] = _str[end - 1]; end--; } strncpy(_str + pos, str, len); _size += len; }
4.4 erase
//删除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; } }
bool operator<(const string& s) const { return strcmp(_str, s._str) < 0; } bool operator==(const string& s) const { return strcmp(_str, s._str) == 0; } bool operator<=(const string& s) const { return *this < s || *this == s; } bool operator>(const string& s) const { return !(*this <= s); } bool operator>=(const string& s) const { return !(*this < s); } bool operator!=(const string& s) const { return !(*this == s); }
6.1 流提取
//全局operator<< //流插入 ostream& operator<<(ostream& out, const string& s) { for (size_t i = 0; i < s.size(); i++) { out << s[i]; } /*for (auto ch : s) out << ch;*/ return out; }
6.2 流插入
重载流提取时要注意:库里面的cin与scanf是不读取空格和换行的,即便是我们输入了空格和换行它也不会读取,所以在重载流插入时要使用库里面的get
进行流插入之前先要清理一下字符串,避免上一次的字符串进行混淆,这里还要注意一下,当我们输入的东西很多的时候,它就面临了要不断的进行扩容,如果我们刚开始就进行扩容一点空间,那么还可能存在空间不够用,或者是浪费空间的问题,那么该怎么办呢?
我们可以使用一个字符数组buff当作一个缓存区,将这个buff大小设为129,将输入的字符先储存在这个数组里面,当这个数组满了之后再将这个数组的内容尾插到string中,如果没有满,就将剩下的内容继续尾插,这样就避免了string自己进行多次扩容的问题:
//流提取 istream& operator>>(istream& in, string& s) { s.clear(); //清理 char ch; char buff[129]; //设置缓存区 ch = in.get(); //输入 int i = 0; while (ch != ' ' && ch != '\n') { buff[i++] = ch; if (i == 128) //满了就往string中尾插 { buff[++i] = '\0'; s += buff; i = 0; } ch = in.get(); } //将剩下的内容插入string if (i != 0) { buff[i] = '\0'; s += buff; } return in; }
头文件:string.h
#pragma once #include
// //模拟实现string,将实现的string封装在自己的命名空间中 namespace ywh { class string { public: //构造函数 string(const char* str = "") :_size(strlen(str)) ,_capacity(_size) { //根据空间大小为_str开辟空间 _str = new char[_capacity + 1]; //多开辟一个存放'\0' //将str拷贝至_str strcpy(_str, str); } //析构 ~string() { //匹配使用 delete[] _str; _str = nullptr; _size = _capacity = 0; } /* //传统写法 //拷贝构造 string(const string& s) { _str = new char[s._capacity + 1]; _size = s._size; _capacity = s._capacity; strcpy(_str, s._str); } //赋值运算符重载 string& operator=(const string& s) { if (this != &s) { //使用一块新的空间来保存s的数据 char* tmp = new char[s._capacity + 1]; strcpy(tmp, s._str); delete[] _str; _str = tmp; //直接将_str重新指向tmp _size = s._size; _capacity = s._capacity; } return *this; } */ //现代写法 void swap(string& s) { std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity); } //拷贝构造 string(const string& s) :_str(nullptr) ,_size(0) ,_capacity(0) { //复用构造函数 string tmp(s._str); swap(tmp); } //赋值运算符重载 string& operator=(string tmp) { //直接交换 swap(tmp); return *this; } 容量接口// //清理 void clear() { _size = 0; _str[_size] = '\0'; } //有效个数 size_t size() const { return _size; } //容量 size_t capacity() const { return _capacity; } //判空 bool empty()const { return 0 == _size; } //预留空间 void reserve(size_t n) { //当传递的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 ch = '\0') { //小于直接添加'\0'截止 if (n <= _size) { _str[n] = '\0'; _size = n; } else { //大于先预留空间 reserve(n); //再将指定的字符依次插入,末尾在添加'\0' while(_size < n) { _str[_size] = ch; _size++; } _str[_size] = '\0'; } } ///迭代器// //非const operator[] char& operator[](size_t pos) { assert(pos < _size); return _str[pos]; } //const operator[] const char& operator[](size_t pos) const { assert(pos < _size); return _str[pos]; } public: typedef char* iterator; typedef const char* const_iterator; //非const iterator begin() { return _str; } iterator end() { return _str + _size; } //const const_iterator begin() const { return _str; } const_iterator end() const { return _str + _size; } /修改接口/// //尾插 void push_back(char ch) { if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); } _str[_size] = ch; _size++; _str[_size] = '\0'; } //c格式的字符串 const char* c_str() const { return _str; } //追加字符串 void append(const char* str) { size_t len = strlen(str); if (len + _size > _capacity) { reserve(len + _size); } strcpy(_str+_size, str); _size += len; } //+=运算符重载 string& operator+=(const char ch) { //复用尾插 push_back(ch); return *this; } string& operator+=(const char* str) { //复用append append(str); return *this; } //查找字符 size_t find(const char ch, size_t pos = 0) { for (size_t i = pos; i < _size; i++) { if (_str[i] == ch) { return i; } } return npos; } //查找字符串 size_t find(const char* str, size_t pos = 0) { const char* tmp = strstr(_str, str); if (tmp) { return tmp - _str; //返回第一次出现的下标 } else { return npos; } } //截取字符串 string substr(size_t pos, size_t len = npos) { string s; //将取到的字符串存放在s中 size_t end = pos + len; if (len == npos || end > _size) { end = _size; len = _size - pos; //有多少取多少 } s.reserve(len); for (size_t i = pos; i < end; i++) { s += _str[i]; //依次插入 } return s; } //在pos位置插入字符 //1. end为size位置 void insert(size_t pos, char ch) { assert(pos <= _size); if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); } //挪动数据 int end = _size; //end类型只能为int while (end >= (int)pos) //防止类型提升,所以进行强转 { _str[end + 1] = _str[end]; end--; } _str[pos] = ch; _size++; } //在pos位置插入字符 //2. end为size后一个位置 //void insert(size_t pos, char ch) //{ // assert(pos <= _size); // if (_size == _capacity) // { // reserve(_capacity == 0 ? 4 : _capacity * 2); // } // //挪动数据 // size_t end = _size + 1; // while (end > pos) // { // _str[end] = _str[end-1]; // end--; // } // _str[pos] = ch; // _size++; //} //在pos位置插入字符串 void insert(size_t pos, const char* str) { assert(pos <= _size); size_t len = strlen(str); if (_size + len > _capacity) { reserve(len + _size); } //挪动数据 int end = _size; while (end >= (int)pos) { _str[end + len] = _str[end - 1]; 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 { size_t begin = pos + len; while (begin <= _size) { _str[begin - len] = _str[begin]; begin++; } _size -= len; } } 运算符重载/// bool operator<(const string& s) const { return strcmp(_str, s._str) < 0; } bool operator==(const string& s) const { return strcmp(_str, s._str) == 0; } bool operator<=(const string& s) const { return *this < s || *this == s; } bool operator>(const string& s) const { return !(*this <= s); } bool operator>=(const string& s) const { return !(*this < s); } bool operator!=(const string& s) const { return !(*this == s); } private: char* _str; size_t _size; //有效元素个数 size_t _capacity; //空间大小 const static size_t npos; }; const size_t string::npos = -1; //流插入 ostream& operator<<(ostream& out, const string& s) { for (size_t i = 0; i < s.size(); i++) { out << s[i]; } /*for (auto ch : s) out << ch;*/ return out; } //流提取 istream& operator>>(istream& in, string& s) { s.clear(); //清理 char ch; char buff[129]; //设置缓存区 ch = in.get(); //输入 int i = 0; while (ch != ' ' && ch != '\n') { buff[i++] = ch; if (i == 128) //满了就往string中尾插 { buff[++i] = '\0'; s += buff; i = 0; } ch = in.get(); } //将剩下的内容插入string if (i != 0) { buff[i] = '\0'; s += buff; } return in; } }
朋友们、伙计们,美好的时光总是短暂的,我们本期的的分享就到此结束,欲知后事如何,请听下回分解~,最后看完别忘了留下你们弥足珍贵的三连喔,感谢大家的支持!