目录
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)的模拟实现
8.string::erase(size_t pos = 0,size_t len=npos)的模拟实现
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类这个部分也是比较考验理解的。下一讲的内容快得话明天就可以发出来,喜欢的可以一键三连哦。下讲再见。