前言:因为模拟实现string中途修修改改了很多代码,所以建议大家先复制总代码在自己的vs下,然后再来一步一步看
目录
第一步:将"hello world"赋值给自定义的string
第二步:打印出hello world在屏幕上
第三步:用下标可以修改字符串的任意字符
第四步:通过下标遍历string的每个字符
第五步:浅拷贝和深拷贝字符串
第六步:=运算符的重载
第七步:实现增删查改——修改基础private
第八步:增删查改——添加字符和字符串(resize和reserve,push_back和append)
第十步:三种遍历方式
第十一步:增删查改——指定插入和指定删除(insert和erase的实现)
第十二步:实现增删查改——查找find函数实现
总代码:
string.h:
test.cpp:
------------------------开始------------------------
为了模拟string,我们需要自己命名一个新的空间,我这里是’bit’,以方便和库中的string作区分
然后创建两个,一个是头文件’string.h’,另一个是.cpp文件"test.cpp"
第一步:将"hello world"赋值给自定义的string
我这里自己定义了一个空间名字为”bit”,然后定义了一个String类。对于我们定义的新字符串_str,
我们需要定义出他的大小,因为原本的字符串中有’ ’,所以在进行字符串拷贝的时候,我们需要在原本的空间大小上+1,然后利用strcpy进行拷贝。
最后再加入一个析构函数
namespace bit
{
class String
{
public:
String(const char* str)//因为有’ ’,所以需要在原有的基础上+1个空间
:_str(new char[strlen(str) + 1])//比如'hello world',实际上是'hello world '
{
strcpy(_str, str);//拷贝字符串
}
~String()//析构函数,将资源还给内存
{
if (_str)
{
delete[] _str;
}
}
private:
char* _str;//自定义一个自己的新指针,指向一个只有' '的字符串空间
};
}
将上述代码放入string.h中,然后我们在test.cpp调用以下代码进行测试
#include#include"string.h" using namespace std;//上面是头文件,下面才是代码 void test_string1() { bit::String s1("hello world"); } int main() { test_string1(); return 0; }
在监视窗口中我们可以看到,我们已经将hello world赋予了,我们自己创建的string的s1中
第二步:打印出hello world在屏幕上
因为我们还没有重载流插入和流提取的运算符,所以如果我们要输出hello world,这里可以使用一个函数c_str.
在string.h的public中加入这段代码
const char* c_str()const
{
return _str;
}
然后就可以在test.app中使用了
void test_string1()
{
bit::String s1("hello world");
cout << s1.c_str() << endl;//加入这句话
}
然后我们在屏幕上就可以看到这段话
注:如果不了解c_str的小伙伴可以看看我的这篇文章
C++:什么是c_str_幻荼的博客-CSDN博客
第三步:用下标可以修改字符串的任意字符
对于修改字符,我们就需要对[]进行运算符重载
在string.h的public放入
char& operator[](size_t pos)
{
assert(pos < strlen(_str));//记得在前面加头文件
return _str[pos];
}
然后在test.cpp中修改字符
void test_string1()
{
bit::String s1("hello world");
cout << s1.c_str() << endl;
s1[0] = 'X';//加入这句话
cout << s1.c_str() << endl;//加入这句话
}
我们可以看到hello world的第一个字符h被替换成了X。
备注:如果对运算符重载有问题的小伙伴可以观看我的这篇文章C++:运算符重载简介_幻荼的博客-CSDN博客
第四步:通过下标遍历string的每个字符
一共是有三种方式:
1.下标+[]
2.迭代器
3. 范围for
因为是从零开始模拟,我们这里先讲解第一种,下标+[]。
我们首先在string.h的public中加入size函数,以便求得下标的整体范围.
size_t size()
{
return strlen(_str);
}
有了这个函数,我们就能再test中遍历每个字符了
void test_string1()
{
bit::String s1("hello world");
cout << s1.c_str() << endl;
s1[0] = 'X';
cout << s1.c_str() << endl;
for (size_t i = 0; i < s1.size(); i++)//加入这段话
{
cout << s1[i] << ' ';
}
cout << endl;//加入这段话
}
会有以下结果
我们可以看到我们就遍历出了每个字符。
备注:如果对strlen不了解的小伙伴可以看我写的这篇文章
strlen简介,和sizeof的区别_幻荼的博客-CSDN博客
第五步:浅拷贝和深拷贝字符串
我们以前使用string的时候,喜欢把一个字符串直接赋值给另一个字符串,即
string s1("123456");
string s2(s1);
那么如何模拟实现呢?
我们先看看直接在test.cpp中试试能不能做到
void test_string2()//这里为了演示深浅拷贝,我重新创立了一个test_string2,前面是1,请将这段代码全部打到test.cpp中
{
bit::String s1("hello world");
bit::String s2(s1);
cout << s1.c_str() << endl;
cout << s2.c_str() << endl;
}
然后就会出现这个结果:
结果就是,拷贝是拷贝过去了,但是编译器会报错,这是为什么呢?
这个就和我们的深浅拷贝有关了
简单来说就是p2通过浅拷贝,和p1指向了同一个空间
由于栈区的规则是先进后出,当执行完拷贝构造函数的时候,我们程序就会先执行p2的析构函数,导致释放堆区开辟的数据,然后这个已经释放的内存又要再被p1释放,这样就会报错
就是说同一块内存数据被释放了两次,这是编译器所不允许的
所以我们需要进行深拷贝。
深拷贝简单来说就是开一个和p1一样大的空间,然后将p1的数据存放到新空间,然后自己指向新空间
这样解析的时候,就各自解析各自的了。
为了实现深拷贝,我们只需要加入这段代码:
String(const String& s)这个隐藏了一个this指针,实际上是s2(s1)
:_str(new char[strlen(s._str) + 1])//开辟(new)一个新空间和s1一样大,来存放数据,+1是因为有’ ’
{
strcpy(_str, s._str);//拷贝数据
}
备注:不了解深浅拷贝的小伙伴可以看看我的这篇文章
C++:浅拷贝和深拷贝简析_幻荼的博客-CSDN博客
第六步:=运算符的重载
为了实现’+=’'+''-'各种运算符的重载,我们首先来实现最简单的’=’符号,即赋值。
我们设定一个场景
void test_string3()//这里是运算符重载,所以重新开了一个test3
{
bit::String s1("hello world");
bit::String s2(s1);
bit::String s3 ("ni hao");
s1 = s3;//通过’=’,将s3赋值给s1
cout << s1.c_str() << endl;
cout << s2.c_str() << endl;
cout << s3.c_str() << endl;
}
有些同学可能会说,直接用strcpy将s3复制给s1不就行了吗?
但是对于赋值,我们有三种情况
s1空间 s1空间=s3空间 s1空间>s3空间 如果我们直接用strcpy复制,s1空间 列如: 如果这么多个1通过strcpy,s1的空间是肯定放不下的,就会造成越界访问。 那么如果s1空间>s3空间是不是可以了呢? 答案是:可以,但是会造成浪费 列如: 如果我S1开了一个1000个字节的空间,结果只用来存放s3这样一个字符’1’,那么是不是会造成很严重的浪费呢? 所以说为了避免越界访问和内存浪费,我们就要使得s1的空间大小和s3一模一样。 所以我们进行三步 第一步:删除s1原有内存 第二步:创建一个和s3一样大的内存 第三步:复制数据 这段代码加入到string.h的public中 然后运行结果如下 就可以看到s1被s3赋值了 通过前面六步,实际上一个最基础的string已经完成了,为了丰富我们的string,我们就要实现增删查改的功能。 为了实现这些功能,一个_str肯定已经不够了,我们需要size和capacity,即容量和已存储数据的大小 然后加入新的capacity函数 同时我们一系列的函数就需要调整 构造函数 因为编译器是根据private的顺序进行定义,我们这里_str的初始化使用了capacity,而capacity在privat的最后面,所以为了方便,我们就直接把他在里面初始化了。 然后是 拷贝 最后是’=’的重载 这些修改实际上就是增加了我们新加入的size和capacity,然后把原先用strlen求有效字符串长度用capacity替换。(capacity是有效字符串个数,不包括' ') push_back(添加字符)append(添加字符串) push_back: 添加字符实际上很简单,只需要考虑扩容,如果容量够直接在末尾添加一个字符,再补一个' '即可 如下图所示: 然后我们就可以测试一下 可以看到1和X就被尾插了。 然后接下来是append: append和push_back几乎一模一样,都是扩容+尾插,却别在于append的扩容不太好直接扩二倍。 假如 如果我们只是单纯的扩容2倍,那么可以看到s*2的capacity=6,也不够10个。 所以在这里我们需要计算一共有多少个字符,再根据字符数量来扩容 小伙伴们可能发现了, 我们这里的扩容运用了一个函数reserve(),而没有像之前push_back一样写一串,这是因为我把扩容,重新用reserve包装了一下 其实和上面扩容一模一样 然后我们也可以吧reserve给push_back使用一下 在一些特殊的情况下,我们也会使用resize对数据进行插入和初始化。 列如: 这样打印出来的结果是"1234569999". 这里就模拟实现一下resizeString& operator=(const String& s)//这里有隐藏的this指针s3(s1)
{
if(this!=&s)//防止有人自己给自己赋值,列如s1=s1,这样会导致乱码
{
delete[]_str;//删除s1空间
_str = new char[strlen(s._str) + 1];//创建和s3一样大的空间
strcpy(_str, s._str);//复制数据
return *this;//返回数据
}
}
第七步:实现增删查改——修改基础private
private:
char* _str;//自定义一个自己的新字符串接受
size_t _size;//容量(新加入)
size_t _capacity;//目前大小(新加入)
};
size_t capacity()const
{
return _capacity;
}
String(const char* str="")//这里全缺省,加入一个空字符串,空字符串中有' ',防止默认构造出错
:_size(strlen(str))//新加入
,_capacity(_size)//新加入
{
_str = new char[_capacity + 1];//将开空间调到这里
strcpy(_str, str);
}
String(const String& s)
:_size(strlen(s._str))
,_capacity(_size)
{
_str = new char[_capacity + 1];
strcpy(_str, s._str);
}
String& operator=(const String& s)
{
if (this != &s)
{
delete[]_str;
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
第八步:增删查改——添加字符和字符串(resize和reserve,push_back和append)
void push_back(char ch)
{
if (_size == _capacity)//查看是否容量已满
{
char* tmp = new char[_capacity * 2 + 1];//new一个新的大空间,+1是因为有' '
strcpy(tmp, _str);//内容复制给新空间
delete[]_str;//删除旧空间
_str = tmp;//将旧空间指向新空间
_capacity *= 2;//' '不是有效字符所以*2就可以了
}
_str[_size] = ch;//尾插
++_size;
_str[_size] = ' ';//最后补一个' '
}
void test_string5()//这里是5了
{
bit::String s1("hello world");
s1.push_back('1');
s1.push_back('X');
cout << s1.c_str() << endl;
}
string s("123");
s.append("12345678910");
void append(const char* str)
{
size_t len = _size + strlen(str);//原本字符数+需要添加的字符数
if (len > _capacity)
{
reserve(len);//扩容
}
strcpy(_str + _size, str);//拷贝数值
_size = len;//拷贝空间
}
void reserve(size_t n)
{
if (n > _capacity)//如果需要扩容
{
char* tmp = new char[n + 1];//创建一个和n一样大的空间防止浪费和越界访问
strcpy(tmp, _str);//复制数据
delete[]_str;//删除旧空间
_str = tmp;//指向新空间
_capacity = n;//把n给capacity
}
}
void push_back(char ch)
{
if (_size == _capacity)
{
reserve(reserve(_capacity == 0 ? 4 : _capacity * 2););//这里扩容二倍刚好合适,因为是只插入单个字符
}
_str[_size] = ch;
++_size;
_str[_size] = ' ';
}
string s1("123456");
s1.resize(10,'9')
void resize(size_t n,char ch=' ')
{
if (n < _size)//n


