容器和大小相关函数
size
因为我们这里的Size和Capacity是私有成员
所以说我们要写出函数来返回它们的大小
int size() { return _size; }
实现效果如下
capacity
代码表示如下
int capacity() { return _capacity; }
运行结果如下 这里也没有什么好讲的
reserve
开空间
reserve的执行规则如下
如果要开的空间大于capacity的时候 将capacity扩大
如果要开的空间小于capacity的时候 什么都不做
代码表示如下
void reserve(int n) //n 表示要开的空间大小 { // 首先判断大小 if (n > _capacity) { char* tmp = new char[n + 1]; // 这里多开一个空间是为了存放 /0 strncpy(tmp, _str, _size + 1); // 这里之所以使用strncpy是因为防止str里面有/0存在 delete[] _str; _str = tmp; _capacity = n; // 容量代表的是可以存放字符的数量 不包括/0 所以说要比真实大小少1 } }
resize
resize的执行规则如下
如果重置的大小大于当前的大小并且小于容量 那就增加差值个‘/0’
如果重置的大小大于容量 那么久扩容后 后面全部赋值‘/0’
如果重置的大小小于当前的大小 那么就缩小大小 并且后面的内容截断
代码表示如下
void resize(int n) // n 表示要重置的大小 { // 小于的话改变下size就好 if (n<=_size) { _size = n; _str[_size] = '\0'; } // 大于的话 要考虑是否要扩容 if (n > _size) { if (n>_capacity) { reserve(n); // 扩容 // 后面全部赋值 /0 int i = _size; while (i<=_capacity) { _str[i] = '\0'; i++; } _size = n; } else { int i = _size; while (i<=n) { _str[i] = '\0'; i++; } _size = n; } } }
运行结果如下
empty
判断是否为空 这个很简单
判断下字符串和空串是否相等就好了
bool empty() { return strcmp(_str, "") == 0; }
、
修改字符串相关函数
凡是修改字符串的函数 都需要考虑是否要扩容
push_back
这里往后插入一个字符 一定记得后面要加上一个‘\0’
代码表示如下
void push_back(char ch) { // 首先判断是否需要扩容 if (_size == _capacity) { reserve(_capacity == 0 ? 4 : 2 * _capacity); } _str[_size] = ch; _str[_size + 1] = '\0'; _size++; }
运行结果如下
append
这个函数的作用就是在字符串后面尾插一个字符串 尾插前需要判断字符串的容量是否可以容纳这么多的
字符
代码表示如下
void append(const char* str) { // 判断是否需要扩容 if (_size+strlen(str)>_capacity) { reserve(_capacity + strlen(str)); } // 开始拼接字符串 strcpy(_str + _size, str); // 将_str尾插到字符串末尾 // 重置字符串长度 _size = _size + strlen(str); }
我们来看看最后的效果是什么样子的
这里符合预期
operator +=
这里我们直接复用上面两个实现的函数就好啦
为了符合String内置函数的写法 我们这里写成两个重载函数
代码分别如下
String& operator+=(char ch) { // 复用就可以 push_back(ch); return *this; }
String& operator+=(const char* str) { // 复用就可以 append(str); return *this; }
显示效果如下
insert
insert函数的作用是选择一个位置插入字符或者是字符串
所以说这里有两个参数 我们将一个设置为pos 一个设置为字符/字符串
首先我们要考虑pos的合法性 它一定是大于等于0的
之后我们来看后面的字符 就要考虑扩容问题啦 移位问题啦
首先我们按照这个思路来设计插入字符的代码
代码表示如下
void append(const char* str) { // 判断是否需要扩容 if (_size+strlen(str)>_capacity) { reserve(_capacity + strlen(str)); } // 开始拼接字符串 strcpy(_str + _size, str); // 将_str尾插到字符串末尾 // 重置字符串长度 _size = _size + strlen(str); } void insert(int pos, char ch) { // 首先考虑pos是否合法 assert(pos >= 0); assert(pos <= _size); // 其次考虑是否需要扩容 if (_size == _capacity) { reserve(_capacity == 0 ? 4 : 2* _capacity ); } // 之后开始考虑移位 为插入数据腾出位置 for (int i = _size; i >= pos; i--) { _str[i + 1] = _str[i]; } _str[pos] = ch; // 调整下size的大小 _size++; }
接下来我们来看看字符串应该怎么插入
代码表示如下
void insert(int pos, const char* str) { // assert assert(pos >= 0); assert(pos <= _size); // 判断是否需要扩容 int len = _size + strlen(str); if (len > _capacity) { reserve(len + _capacity); } // 之后开始考虑位移 for (int i = _size; i >= pos; i--) { _str[i + strlen(str)] = _str[i]; } // 开始插入 注意这个时候要用strncpy才可以 // 如果这个时候你用了strcpy 后面会被/0覆盖 strncpy(_str + pos, str, strlen(str)); }
运行结果如下
这里尤其要注意的一点就是最后必须要使用strncpy 很多人都是忽略这个细节导致出错
erase
这里还是我们给出两个参数
一个位置还有要删除字符的个数
void earse(int pos, size_t n = -1) { // assert assert(pos >= 0); assert(pos <= _size); // 判断要删除的是不是后面所有的值 if (n >= _size - pos) { _str[pos] = '\0'; // 改变size的大小 _size = pos; } // 否则就不是要删除所有的值 那么我们这个时候来看看被删除的值有多少 n else // n < _size - pos { strcpy(_str + pos, _str + pos + n); _size+=n; } }
运行结果如下
clear
这个很简单啦 直接将字符串大小变成0 然后赋值空字符串就可以
void clear() { _size = 0; _str[_size] = '\0'; }
很简单 这样子两行代码搞定
c_str 这里也很简单 返回一个字符串就可以 const char* c_str() const { return _str; }
访问字符串相关函数
operator[]
重载这个运算符只是为了符合我们使用喜欢
想想看 当我们要使用这个运算符的时候是为了什么?
访问数组中的某个元素是吧 那么怎么写这个函数是不是很清晰了
char& operator[](size_t i) { assert(i < _size); //检测下标的合法性 return _str[i]; //返回对应字符 } const char& operator[](size_t i ) const { assert(i < _size); //检测下标的合法性 return _str[i]; //返回对应字符 }
使用效果如下
find
这个函数的意思很明确 找到第一个出现的字符 如果找到 返回下标 如果找不到 返回一个-1就可以
我们这里再加一个功能 就是用户可以设置从默认的第n个位置开始查找 默认为0
那么代码表示如下
int find(char ch , int pos = 0) { // assert assert(pos < _size); for (size_t i = 0+pos; i < _size; i++) { if (_str[i] == ch) { return i; } } return -1; }
这里我们可以发现结果没问题
如果我们想要找字符串呢?
很简单 我们使用strstr查找就可以
//正向查找第一个匹配的字符串 int find(const char* str, int pos = 0) { assert(pos < _size); //检测下标的合法性 const char* ret = strstr(_str + pos, str); //调用strstr进行查找 if (ret) //ret不为空指针,说明找到了 { return ret - _str; //返回字符串第一个字符的下标 } else //没有找到 { return -1; //返回-1 } }
rfind
顾名思义 反向查找
那么我们这里转换下思路就好了 指针从右边开始找
代码表示如下
int rfind(const char* str, int pos = 0) { assert(pos < _size); //检测下标的合法性 pos = _size - 1 - pos; // 一个个开始往后面找 while (pos) { const char* ret = strstr(_str + pos, str); if (ret==nullptr) { pos--; } else { return ret - _str; } } return -1; }
代码运行结果如下
我们发现可以完美运行
关系运算符重载
关系运算符有 >、>=、<、<=、==、!= 这六个,但是对于C++中任意一个类的关系运算符重载,我们均
只需重载其中的两个,剩下的四个关系运算符可以通过复用已经重载好了的两个关系运算符来实现。
我们这里实现 == 和 >
//>运算符重载 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); }
输入输出运算符重载
重载>>运算符是为了让string对象能够像内置类型一样使用>>运算符直接输入。输入前我们需要先将对象的C字符串置空,然后从标准输入流读取字符,直到读取到’ ‘或是’\n’便停止读取
代码表示如下
//>>运算符的重载 istream& operator>>(istream& in, string& s) { s.clear(); //清空字符串 char ch = in.get(); //读取一个字符 while (ch != ' '&&ch != '\n') //当读取到的字符不是空格或'\n'的时候继续读取 { s += ch; //将读取到的字符尾插到字符串后面 ch = in.get(); //继续读取字符 } return in; //支持连续输入 }
同样的 流插入运算符表示如下
//<<运算符的重载 ostream& operator<<(ostream& out, const string& s) { //使用范围for遍历字符串并输出 for (auto e : s) { cout << e; } return out; //支持连续输出 }
还记不记得我们之前有一个getline运算符
它其实和我们的流提取差不多 唯一的区别就是它遇到换行符才会停
//读取一行含有空格的字符串 istream& getline(istream& in, String& s) { s.clear(); //清空字符串 char ch = in.get(); //读取一个字符 while (ch != '\n') //当读取到的字符不是'\n'的时候继续读取 { s += ch; //将读取到的字符尾插到字符串后面 ch = in.get(); //继续读取字符 } return in; }
总结
本篇博客主要模拟了String类的实现
由于博主水平有限 错误在所难免 希望大佬看到能够及时指正
如果本篇博客帮助了你 别忘记一键三连啊
阿尼亚 哇酷哇酷!