C++初阶-string类的增删的模拟实现
目录
1.string::reserve(size_t n)的模拟实现
2.string::push_back(char ch)的模拟实现
3.string::append(const char* str)的模拟实现
4.string& operator+=(char ch)和string& operator+=(const char* str)的模拟实现
5.string::insert(size_t pos,size_t n,char ch)的模拟实现
问题分析和最终版本
6.string::insert(size_t pos,const char* str)的模拟实现
7.string::npos的模拟实现
8.string::erase(size_t pos = 0,size_t len=npos)的模拟实现
问题分析和代码改进
9.总结
1.string::reserve(size_t n)的模拟实现
这个函数我们只实现扩容的版本,缩容的我们用得不多,所以这里就不实现了。
如何实现?
我们通过比较n与capacity来判断是否要扩容,若n>_capacity则我们先开辟一块新的空间(n+1)个char类型的空间(存储n+1个数据(因为不包含\0所以要+1)),然后再把内容拷贝到新空间上,最后释放原空间,并把_str指向新空间,_capacity置为n。则最终代码如下:
//.cpp
void string::reserve(size_t n)
{if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}
}
我们通过该测试代码:
//.cpp
void test1()
{string s("Hello world");s.reserve(20);
}
调试发现结果:
地址是已经改变了的,并且把_capacity变化的代码是正确的。
2.string::push_back(char ch)的模拟实现
这个函数实现比较简单,先看一下_size是否等于_capacity或者判断是否_size+1>_capacity来判断是否需要库容,然后用二倍扩容的知识来进行扩容(之后的那些insert、append、+=都差不多),因为如果用1.5倍扩容还要涉及到其他的如四舍五入的知识,很麻烦。则代码如下:
//.cpp
void string::push_back(char ch)
{if (_size == _capacity){//扩容reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = ch;++_size;//注意!!!_str[_size] = '\0';
}
用以下的函数来测试:
//.cpp
void test1()
{string s("Hello world");s.push_back('a');
}
则调试发现:
则代码无误。
3.string::append(const char* str)的模拟实现
这个函数实现起来比之前复杂一些:
首先我们需要计算出str的长度,用strlen来计算;
其次,我们需要用_size+len是否大于_capacity来判断是否需要扩容;
如果需要扩容,我们需要把newCapacity置为2*_capacity;
但是如果_size+len还是大于newCapacity呢么我们就需要把newCapacity置为len+_size了,最后再扩容至newCapacity。
而且我们还不能在插入字符串时用strcat(_str,str)来插入,因为效率太低了,要从头开始找\0。我们用新的方式:strcpy(_str+_size,str);我们就从这个_str的_size位置即之后的字符串替换为str,这个方式比之前的方式难想到,故也需要很强的思维性!
最后不要忘记把_size+=len了!
那么我们最终代码如下:
//.cpp
void string::append(const char* str)
{size_t len = strlen(str);if (_size + len > _capacity){//扩容size_t newCapacity = 2 * _capacity;//不确定二倍扩容后的空间是否足够if (_size + len > 2 * _capacity){newCapacity = _size + len;}reserve(newCapacity);}strcpy(_str + _size, str);_size += len;
}
用以下代码进行测试:
//.cpp
void test1()
{string s("Hello world");s.append(" Hello CSDN");
}
那么结果:
则代码无误。
4.string& operator+=(char ch)和string& operator+=(const char* str)的模拟实现
这两个函数实现都是前面两个函数实现的复用,所以就只展示代码,就不测试了。
//.cpp
string& string::operator+=(char ch)
{push_back(ch);return *this;
}
string& string::operator+=(const char* str)
{append(str);return *this;
}
5.string::insert(size_t pos,size_t n,char ch)的模拟实现
这个函数实现起来基本和之前一样,这里讲一下思路:
先判断pos是否小于等于_size,这个可以用assert来判断,也可以手动判断,因为不能越界,所以我们要加上一个判断;
然后判断是否要扩容,这个和之前一样,就不做多解释了;
然后我们要把第pos位置后的字符移动n个位置,方法是从后往前移动;
最后插入n个字符。
代码如下:
//.cpp
void string::insert(size_t pos, size_t n, char ch)
{assert(pos <= _size);if (_size + n > _capacity){size_t newCapacity = _capacity * 2;if (_size + n > newCapacity){newCapacity = _size + n;}reserve(newCapacity);}size_t end = _size;while (end >= pos){_str[end + n] = _str[end];--end;}//插入n个字符for (size_t i = 0; i < n; i++){_str[pos + i] = ch;}_size += n;
}
用以下代码进行测试:
//.cpp
void test1()
{string s("Hello");//尾部插入s.insert(5, 5, 'x');cout << s.c_str() << endl;//中间插入s.insert(2, 3, 'y');cout << s.c_str() << endl;//头部插入s.insert(0, 3, 'z');cout << s.c_str() << endl;
}
但是运行结果为:
也就是说头部插入有问题,原因是什么?
问题分析和最终版本
因为size_t end=_size;虽然end会--,但end是无符号整数,不会小于0所以end>=pos这个条件是一直都成立的,所以我们不能用这个来判断。
那我们如果把end改为int类型呢?
结果没有改变,为什么?
因为在C语言中两个操作数不一样的时候会把类型进行转换,通常是范围小转换成范围大的数。但是如果是无符号数与有符号数进行比较时,就会把有符号转换为无符号去比较。那我们把pos也改为int类型吗?
不行,我们要与string类的这个函数的参数保持一致,我们不能改变它的定义。
我们可以通过强制类型转换,如:end<=(int)pos,这个pos仍然为size_t类型。或者我们把end初始化为_size+n,循环改为while(end>(pos+n-1)){_str[end]=_str[end-n];--end;}我们也可以把循环的条件改为end>=(pos+n)这个需要自己画图去理解了,要么就因为开始的时候加了n,这个时候也要加n。
最终代码如下:
void string::insert(size_t pos, size_t n, char ch)
{assert(pos <= _size);if (_size + n > _capacity){size_t newCapacity = _capacity * 2;if (_size + n > newCapacity){newCapacity = _size + n;}reserve(newCapacity);}size_t end = _size + n;while (end >= (pos + n) ){_str[end] = _str[end - n];--end;}//插入n个字符for (size_t i = 0; i < n; i++){_str[pos + i] = ch;}_size += n;
}
那么最终运行结果如下:
代码没有问题!
6.string::insert(size_t pos,const char* str)的模拟实现
这个函数前面和刚刚那个函数一样,后面的只要把字符串拷贝至pos位置即可,则代码如下:
//.cpp
void string::insert(size_t pos, const char* str)
{assert(pos <= _size);size_t n = strlen(str);if (n == 0){return;}if (_size + n > _capacity){size_t newCapacity = _capacity * 2;if (_size + n > newCapacity){newCapacity = _size + n;}reserve(newCapacity);}size_t end = _size + n;while (end >= (pos + n)){_str[end] = _str[end - n];--end;}//插入n个字符for (size_t i = 0; i < n; i++){_str[pos + i] = str[i];}_size += n;
}
测试函数如下:
void test1()
{string s("Hello");//尾部插入s.insert(5, " World");cout << s.c_str() << endl;//中间插入s.insert(5, " Beautiful");cout << s.c_str() << endl;//头部插入s.insert(0, "I Love ");cout << s.c_str() << endl;
}
运行结果如下:
7.string::npos的模拟实现
这个npos是一个静态的且不可以被修改的成员变量,而且它是size_t类型的,但是它的值为-1,而且我们是在.cpp里面定义npos的值,而不是在.h文件中定义,这个你可以试一下在.h文件中定义会有问题的!
那么我分为两个文件来讲解:
//.h
#define _CRT_SECURE_NO_WARNINGS 1
#include<string.h>
#include<assert.h>
#include<iostream>
using namespace std;
namespace td
{class string{public:string(const char* str = " ");const char* c_str() const{return _str;}~string();void reserve(size_t n);void push_back(char ch);void append(const char* str);string& operator+=(char ch);string& operator+=(const char* str);void insert(size_t pos, size_t n, char ch);void insert(size_t pos, const char* str);private:char* _str;size_t _size;size_t _capacity;const static size_t npos;};void test1();
}
//.cpp
#include"test.h"
namespace td
{const size_t string::npos = -1;string::string(const char* str):_size(strlen(str)){_capacity = _size;_str = new char[_size + 1];strcpy(_str, str);}string::~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}void string::reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}void string::push_back(char ch){if (_size == _capacity){//扩容reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = ch;++_size;//注意!!!_str[_size] = '\0';}void string::append(const char* str){size_t len = strlen(str);if (_size + len > _capacity){//扩容size_t newCapacity = 2 * _capacity;//不确定二倍扩容后的空间是否足够if (_size + len > 2 * _capacity){newCapacity = _size + len;}reserve(newCapacity);}strcpy(_str + _size, str);_size += len;}string& string::operator+=(char ch){push_back(ch);return *this;}string& string::operator+=(const char* str){append(str);return *this;}void string::insert(size_t pos, size_t n, char ch){assert(pos <= _size);if (_size + n > _capacity){size_t newCapacity = _capacity * 2;if (_size + n > newCapacity){newCapacity = _size + n;}reserve(newCapacity);}size_t end = _size + n;while (end >= (pos + n) ){_str[end] = _str[end - n];--end;}//插入n个字符for (size_t i = 0; i < n; i++){_str[pos + i] = ch;}_size += n;}void string::insert(size_t pos, const char* str){assert(pos <= _size);size_t n = strlen(str);if (n == 0){return;}if (_size + n > _capacity){size_t newCapacity = _capacity * 2;if (_size + n > newCapacity){newCapacity = _size + n;}reserve(newCapacity);}size_t end = _size + n;while (end >= (pos + n)){_str[end] = _str[end - n];--end;}//插入n个字符for (size_t i = 0; i < n; i++){_str[pos + i] = str[i];}_size += n;}void test1(){string s("Hello");//尾部插入s.insert(5, " World");cout << s.c_str() << endl;//中间插入s.insert(5, " Beautiful");cout << s.c_str() << endl;//头部插入s.insert(0, "I Love ");cout << s.c_str() << endl;}
}
这个主要是为了你们了解一下每个函数的定义和声明,之后有些函数是要复用的,所以先总结一下。
const在最前面,static在中间,最后再是size_t类型,这个我们是一定要记得顺序的!
8.string::erase(size_t pos = 0,size_t len=npos)的模拟实现
首先,我们要设置缺省值在声明时,即在.h文件中要设置缺省值,而在.cpp文件中也就是在定义中我们不能加缺省值;
然后,如果pos+len大于_size,所以我们需要先把_str[pos]='\0'然后把_size=pos,最后return;
否则,我们就要设置一个end初始化为pos+len,然后再加上一个循环,循环条件是end<=_size,循环体内是_str[end-len]=_str[end];++end我们是从前往后赋值的,所以要这样写。
最后再把_size-=len即可。
所以最终代码如下:
//.cpp
void string::erase(size_t pos, size_t len)
{assert(pos < _size);if (pos + len >= _size){//删完了_str[pos] = '\0';_size = pos;return;}size_t end = pos + len;while (end < _size){_str[end - len] = _str[end];++end;}_size -= len;_str[_size] = '\0';
}
测试函数如下:
void test1()
{string s1("Hello world");string s2("Hello world");string s3("Hello world");string s4("Hello world");cout << s1.c_str() << endl;//删除从中间到最后的元素s1.erase(5);cout << s1.c_str() << endl;//删除从头到中间的元素s2.erase(0, 5);cout << s2.c_str() << endl;//删除从中间一个位置到另外一个中间的位置s3.erase(3, 5);cout << s3.c_str() << endl;//直接删完s4.erase();cout << s4.c_str() << endl;
}
那么这个代码运行结果就会报错,为什么?
问题分析和代码改进
如果len==npos 或 pos + len
远超 _size
会直接截断字符串到 pos
位置,这个思路是不行的,所以我们要么就把前面的if语句改为如下形式:
len = std::min(len, _size - pos);
这样保证了我们不会越界了,也简化了我们的代码长度,或者我们把前面的if语句改为:
if ( len >= _size - pos)
{//删完了_str[pos] = '\0';_size = pos;return;
}
因为这样就不会有len为npos时再加pos而报错了,但是我们建议还是第一种方式来进行修改,因为第二种方式最后还可能会报错的,所以最终改变后的代码为:
void string::erase(size_t pos, size_t len)
{if (pos >= _size) return; // 检查 pos 是否合法len = std::min(len, _size - pos); // 限制 len 的范围size_t end = pos + len;while (end < _size) {_str[end - len] = _str[end];++end;}_size -= len;_str[_size] = '\0'; // 确保字符串以 \0 结尾
}
至于这个std::min函数就是查找两者之间的最小值,我们也可以手动判断。
则运行结果如下:
当然,我们也可以把循环改变成如下形式(和之前的insert方式一样):
void string::erase(size_t pos, size_t len)
{if (pos >= _size) return; // 检查 pos 是否合法len = std::min(len, _size - pos); // 限制 len 的范围size_t end = pos + len;strcpy(_str + pos, _str + pos + len);//while (end < _size) //{// _str[end - len] = _str[end];// ++end;//}_size -= len;_str[_size] = '\0'; // 确保字符串以 \0 结尾
}
这样运行起来还是可以的!
9.总结
这一节内容还是比较多的,而且理解起来比之前的提升了不只一个档次,下一讲的内容难度可能还会有过之而不及,所以说string类这个部分也是比较考验理解的。下一讲的内容快得话明天就可以发出来,喜欢的可以一键三连哦。下讲再见。