本文主要介绍库里面string类的模拟实现
文章目录
- 前言
- 一、string的构造函数
- ①无参的构造函数
- ②带参的构造函数
- ③修改构造函数
- 二、析构函数
- 三、拷贝构造
- 四、赋值重载
- 五、返回size 、capacity和empty
- 六、[]的运算符重载
- 七、迭代器
- ① 正向迭代器。
- ② 正向const迭代器
- 八、string比较大小(运算符重载)
- 九、扩容
- ① reserve扩容
- ② resize扩容
- 十、尾插数据
- ①push_back
- ②append
- 十一、+=的运算符重载(替换尾插数据的函数)
- 十二、在固定为插入,删除(insert和erase)
- ①插入一个字符
- ② 插入一个字符串
- ③ 删除
- 十三、交换swap
- 十四、find
- ① 找字符
- ②找子串
- 十五、流插入,流提取
- ① 流插入
- ②流提取
- 十六、全部源码
- string.h
- test.c
前言
首先创建两个文件
string.h
:用来定义和声明类里面的成员函数和成员变量,以及测试函数。使用命名空间包裹
test.cpp
:用来运行代码
一、string的构造函数
由于我们要模拟实现string,为了不和库里面的string冲突,我们需要使用命名空间将其封装起来。
其实底层其实和我们的顺序表很类似,也是三个成员变量(字符指针)
//定义一个命名空间,防止和库里面的sring类冲突
namespace mwq
{class string{public:private:char* _str;size_t _size;size_t _capacity;};
};
我们先把准备工作做好,在test.cpp里面写一个主函数,然后包含这个string.h头文件。
注意:我们还要包含#include <iostream> 以及using namespace std,因为我们后面需要在头文件的命名空间里面写测试函数,需要用到cout!!
#include <iostream>
using namespace std;#include "string.h"int main()
{mwq::test1();return 0;
}
①无参的构造函数
现在我们开始写一个无参的构造函数
//无参构造函数
string():_str(nullptr), _size(0),_capacity(0)
{}
其实对于我们的无参构造函数看着没什么问题,其实有点问题。那我举一个例子大家看看什么问题。
我们前面了解c_str(返回C形式的字符串),我们如果要实现这个要求我们返回这个字符串的地址。
//返回C形式的字符串
const char* c_str()const
{return _str;
}
好了现在我们写一个test1测试一下(直接在命名空间里面写就行了)
在test.cpp里
#include <iostream>
using namespace std;#include "string.h"int main()
{mwq::test1(); //test1在string.h文件里面的mwq命名空间下return 0;
}
在string.h里
//定义一个命名空间,防止和库里面的sring类冲突
namespace mwq
{class string{public://无参构造函数string():_str(nullptr), _size(0),_capacity(0){}//返回C形式的字符串const char* c_str(){return _str;}private:char* _str;size_t _size;size_t _capacity;};void test1(){string s1; cout << s1.c_str() << endl;
};
注意:这里使用了
cout
,所以test.cpp
里面必须有#include<iostream>以及using namespace std
。
因为头文件包含了#include<iostream>以及using namespace std
,当我们预处理时候会将头文件展开,在链接时,cout
就会去他的定义,如果我们没有定义就会报错,
所以在test.cpp
里面必须写#include<iostream>以及using namespace std
(cout是在这个里面定义的)
我们如果想打印我们的字符串,就会发现直接报错,那么出现了什么问题呢?
大家先思考一下
我们s1进行无参的构造函数,并且在初始化列表中初始化了空指针。我们先通过s1找到了c_str的成员函数,这时候是没有崩溃的,并且将空指针返回也是没有问题的。问题就出在我们通过这个空指针打印,因为cout在打印字符串时候会进行解引用,因为要找\0,这时就涉及到了空指针的解引用。就会出现问题。
②带参的构造函数
现在我们开始写一个带参的构造函数
string(const char* str):_str(str),_size(strlen(str)),_capacity(strlen(str))
{}
其实他也是有问题的
我们设置成带参数,str
的类型是const char*
,但是我们成员变量_str是char*
类型,我们在初始化列表中传参数的时候就涉及到权限的放大。
那有没有解决办法呢?
我们可以想到一种解决办法就是将我们的成员参数也设置为const。
这样虽然可以运行,但是,这样的操作无疑是局限的,那么我们后续的操作就都不可以改变我们的_str。(因为你变成了const的类型)
例如:我们再实现是个[]运算符重载去访问这个字符串
char& operator[](size_t pos)
{assert(pos < _size);return _str[pos];
}
此时去访问s2的数据并试图修改就会报错,这里是因为我们的_str是const,但是返回是char,
那我们再修改一下返回值,将返回值修改成const char&
还是不可以的,其实这个报错很容易看懂,因为我们的返回的是const char,这个是不能修改的。
所以:我们的这个带参的构造函数是有问题的。
综上所述:我们的有无参数的构造函数的问题如下:
- 无参构造函数:不能初始化为nullptr,因为我们打印字符串需要解引用
- 带参构造函数:不能用str直接初始化,因为类型不匹配,我们的_str要设置为char*类型,因为后续还要修改。
③修改构造函数
我们先改我们带参数的构造函数,我们的直接将我们的常量字符串给他,肯定不可以,那么我们就自己开一块空间,将字符串的内容拷贝过来。这样我们自己开辟的空间就可以自己随意修改,而不是像我们上面将成员变量修改为const。
我们开辟空间,就没有必要在初始化列表开空间,就直接在函数中开就可以。具体的细节我们开代码注释。
string(const char* str):_size(strlen(str))
{//为什们将capacity的初始化也放到函数中?//在初始化列表,初始化的顺序跟定义的顺序有关,//如果我们先定义的capacity,我们size还没定义就是随机值。_capacity = _size;//因为capacity不包括字符的结束标准 \0 //所以在开辟空间的时候,我们多开一位。_str = new char [_capacity + 1];//将我们字符串的内容拷贝过来。strcpy(_str, str);
}
修改无参数的构造函数,我们防止空指针的解引用,我们可以也给他开一个字节的空间,放\0,也就是字符串的结束标志,表示空字符串。
string()//我们初始化为什们要用new[]呢?//为了跟我们带参构造函数保持一致,//在析构的时候用一样的类型。delete[] _str;:_str(new char[1]), _size(0),_capacity(0)
{_str[0] = '\0'; //字符串结束标志,不写的话,直接打印s1.c_str()会崩溃,因为找不到\0
}
我们在类和对象中,我们知道,无参的构造函数可以用缺省参数代替,所以我们将带参数的构造函数用缺省参数,就可以将这两个合二为一。
//下面两种形式都可以,我们""里面就有\0。
//string(const char* str = "\0")
string(const char* str = ""):_size(strlen(str))
{_capacity = _size == 0 ? 3 : _size; //防止一开始_capacity = 0,对后面的代码有影响_str = new char [_capacity + 1];strcpy(_str, str);
}
二、析构函数
这个很简单
~string()
{
//注意的点就是,delete一定要和new保持一致delete[]_str;_str = nullptr;_size = _capacity = 0;
}
三、拷贝构造
编译器会自动生成我们的拷贝构造,但是我们在类和对象的章节学过,它完成的是浅拷贝。
对于我们的字符串,我们_str
是一个指针,指向一块数组空间(new出来的),当我们完成浅拷贝的时候,我们拷贝出来的指针只是将_str
的四个字节拷贝了过去,所以两块空间用指针指向同一块空间。
当我们进行析构函数的时候,我们同一块空间析构了两次,这显然是由问题的,所以会直接报错,我们不能直接用默认拷贝构造,我们要自己写。
深拷贝:
//拷贝构造
string(const string& s): _size(s._size), _capacity( s._capacity)
{//防止new失败,我们先创建一个临时变量开辟空间//拷贝完后,在给_strchar* tmp = new char[s._capacity + 1];strcpy(tmp, s._str);_str = tmp;
}
四、赋值重载
这个需要考虑的情况比较多,我们看图
这个也是默认成员函数,编译器会自动生成,跟拷贝构造一样进行浅拷贝,和我们上面出现的情况一样,所以我们要进行深拷贝,自己写一个赋值重载。
我们可以看到,上面有三个问题
- 首先就得自己写一个深拷贝,和上面拷贝构造问题一样
- 当新空间>原空间时,需要扩容,还需要小心扩容失败了
- 当新空间=原空间,直接扩容
- 当新空间<原空间,直接拷贝过去会造成资源浪费
基于上面的这么多复杂的原因,我们编译器直接统一处理,直接new一个空间temp,将需要拷贝的数据拷给temp空间,然后释放原空间,再让原空间指向temp即可解决。
注意:这里为了满足连续赋值,我们使用引用返回
//赋值重载
string& operator=(string& s)
{//防止自己和自己赋值if (this != &s){// 防止new失败,我们先创建一个临时变量开辟空间//拷贝完后,在给_strchar* tmp = new char[s._capacity + 1];strcpy(tmp, s._str);delete[]_str;_str = tmp;_size = s._size;_capacity = s._capacity;}return *this;
}
五、返回size 、capacity和empty
就是将我们的size直接返回,需要注意的一点就是我们返回size,一般都是不可修改的,不可修改一般就设置成
const
这几个我们都不希望他修改,统一用const修饰,这样普通对象可以调用,const对象也可以调用。
//获取字符串元素个数
size_t size()const
{return _size;
}//返回空间容量大小
size_t capacity()const
{return _capacity;
}//判空
bool empty()const
{return _size == 0;
}
六、[]的运算符重载
这个也是比较容易,就是返回字符串的位置,我们可以通过引用返回改变我们字符串的值。
但是当我们想要打印字符串的时候,我们并不想修改字符串,我们就可以用const修饰,让其构成运算符重载。让我们的程序更高效
//[]运算符重载char& operator[](size_t pos){assert(pos < _size);return _str[pos];}//不可修改的[]const char& operator[](size_t pos) const{assert(pos < _size);return _str[pos];}
七、迭代器
我们暂时将其考虑为指针,就可以。
像用指针一样,用迭代器。我们先写一个正向迭代器。
① 正向迭代器。
//迭代器,//我们先知道怎么用就可以
typedef char* itterator;
itterator begin()
{return _str;
}
itterator end()
{return _str + _size;
}
用迭代器遍历字符串。
//测试迭代器
void test_string4()
{string s1("hello world");string::itterator it = s1.begin();while (it != s1.end()){cout << *it << " ";it++;}cout << endl;//范围for的底层就是迭代器for (auto ch : s1){cout << ch << " ";}cout << endl;
}
② 正向const迭代器
//正向const迭代器
typedef const char* const_itterator;
const_itterator begin() const
{return _str;
}
const_itterator end() const
{return _str + _size;
}
八、string比较大小(运算符重载)
注意:这里我们不需要修改字符串,所以使用const修饰
我们比较的不是两个对象的大小,而是两个字符串。使用strcmp即可
int strcmp ( const char * str1, const char * str2 );
str1<str2结果小于0
str1=str2结果等于0
str1>str2结果大于0
//s1>s2bool operator>(const string& s) const{return strcmp(_str, s._str) > 0;}//s1==s2bool operator==(const string& s) const{return strcmp(_str, s._str) == 0;}//s1>=s2bool operator>=(const string& s) const{return strcmp(_str, s._str) >= 0;}//s1<s2bool operator<(const string& s) const{return !(*this >= s);}//s1!=s2bool operator!=(const string& s) const{return !(*this == s);}//s1<=s2bool operator<=(const string& s) const{return !(*this > s);}
九、扩容
扩容我们知道有两种方式:一种是reserve,另外一种是resize
注意:reserve和resize都不支持缩容
① reserve扩容
首先介绍一下reserve
void reserve (size_t n = 0);
函数功能:将空间增加为n个,不会改变有效元素的个数(_size)
- 当参数n小于原空间时,啥也不干,空间大小不变
- 当参数n大于原空间时,将空间大小增加为n+1(为\0留一个空间)
void reserve(size_t n)
{//reserve 不支持缩容//所以我们自己加个条件if (n > _capacity){//给\0开一个空间char* tmp = new char[n + 1];strcpy(tmp, _str);delete[]_str;_str = tmp;_capacity = n;}
}
② resize扩容
void resize (size_t n);
void resize (size_t n, char c);
函数功能:都是将字符串的有效字符改变到n个。不同的是当字符个数增多时:resize(n)用\0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。
注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
详细解释:
- 当n<size时,
void resize (size_t n);
将有效字符个数缩减为n个,全部初始化为\0 - 当size <= n <= capacity时,将有效字符个数增加为n个
void resize (size_t n);
将n-size个字符初始化为\0
void resize (size_t n, char c);
,将将n-size个字符初始化为字符c
其实这种情况就是在有效字符结尾后面添加\0或者字符c - n>capacity,将容量扩展至n+1个空间。将n-size个字符初始化为\0或者字符c
void resize (size_t n);
将n-size个字符初始化为\0
void resize (size_t n, char c);
,将将n-size个字符初始化为字符c
我们发现2和3其实差不多,3就先扩容在初始化后面的字符
综上所述:其实我们会发现,
void resize (size_t n);
,其实有点多余,我们直接使用缺省参数知识将
void resize (size_t n, char c = '\0');
即可。
//不写第二个参数默认改为\0,使用缺省参数就可以将其合二为一//1:n <= size//2:size < n <= capacity//3:n>capacityvoid resize(size_t n, char c = '\0'){if (n < _size){memset(_str, c, n);_size = n;_str[_size] = '\0';}else{//if (n < _capacity)//{// memset(_str + _size, c, n - _size); //size后面的n个字符// _size = n;// _str[_size] = '\0';//}//else //扩容//{// reserve(n);// memset(_str + _size, c, n - _size); //size后面的n个字符// _size = n;// _str[_size] = '\0';//}//第3种情况和第2种差不多if (n > _capacity){reserve(n);//扩容}memset(_str + _size, c, n - _size); //size后面的n个字符_size = n;_str[_size] = '\0';//将即为填上\0,防止出错}}
注意:我们以后改变字符串的时候,
- 都要考结尾有没有\0,防止出错
- 记得改变字符串的size
十、尾插数据
增加数据有两种方式,
一种是push_back
,尾插一个字符
一种是append
,尾插一个字符串
①push_back
//尾插字符string& push_back(char ch){if (_size + 1 > _capacity){//if (_capacity == 0)//{//_capacity = 3;//}reserve(_capacity * 2); //二倍扩容,}_str[_size] = ch;++_size; //不要忘记改变size了_str[_size] = '\0'; //结尾加上\0return *this;}
注意:我们这里会发现,当_size + 1 > _capacity时,我们才扩容至capacity * 2,但是我们构造函数如果一开始为0的话,这里显然就会出问题了,直接把空间干为0了。
解决办法:
- 我们在构造函数里面处理,一开始就给capacity一个空间,防止为0
- 我们可以在这个再判断一下,如果capacity为0,那我们就给他一个空间也可以
②append
我们这里处理扩容和push_back不一样,我们插多少,扩多少,因为如果原数组太小,知识扩二倍之后还可能放不下
//增加一个字符串
void append(const char* str)
{size_t len = strlen(str);if (_size + len > _capacity){//如果原数组太小,二倍之后还可能放不下reverse(_size + len);}strcpy(_str + _size, str);_size += len;//不要忘记改变size了//最后加上\0_str[_size] = '\0';
十一、+=的运算符重载(替换尾插数据的函数)
这个超级容易,其实就是直接复用我们的push_back和append。
利用运算符重载直接写两个即可
string& operator+=(char ch)
{push_back(ch);return *this;
}
string& operator+=(const char* str)
{append(str);return *this;
}
十二、在固定为插入,删除(insert和erase)
这时我们应该用
insert
在我们的插入也分为插入一个字符,和插入一个字符串。
其实思路大致相同,都是先判断是否需要扩容,然后挪数据,插数据
注意:这里挪数据时,很容易出错
总结:以后挪数据都将结尾定位最后一个位置的下一个位置,然后挪动。
①插入一个字符
//再posstring& insert(size_t pos, char ch){//1:判断是否需要扩容assert(pos <= _size && pos >= 0);if (_size + 1 > _capacity){reserve(_capacity * 2);}//2:挪数据size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];--end;}//3:插数据_str[pos] = ch;++_size;//不要忘记改变size了return *this;}
② 插入一个字符串
string& insert(size_t pos, const char* str){assert(pos <= _size && pos >= 0);size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);//提前开空间}//挪动数据(包括\0)//循环size-pos+1次size_t end1 = _size;size_t end2 = _size + len;for (size_t i = 0; i <= _size - pos; ++i){_str[end2--] = _str[end1--];}//插入数据strncpy(_str + pos, str, len);_size += len;//不要忘记改变size了return *this;}
我们这里解释一下挪数据的思路:我们只要考虑挪多少次就行了
插入数据直接使用
strncpy
将新的字符串从pos位置插入即可
char * strncpy ( char * destination, const char * source, size_t num );
③ 删除
string& erase (size_t pos = 0, size_t len = npos);
函数功能:就是删除从pos位置开始数,长度为len的字符(包括pos)
- 当len=npos时(npos是整形的最大值)或者要删除的len大于pos后面字符串的长度,直接将pos以及pos后面的所有字符删除,并且给pos位置赋值为\0
- 当len小于pos后面的字符串长度时候1,直接挪数据覆盖即可
注意:别忘了改变size
//将pos位置的字符,横跨len个长度的串删除(包括pos)string& erase(size_t pos, size_t len = npos){//要删除的个数比pos之后的字符串长if (len == npos || len >= _size - pos){_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + pos + len);_size -= len;}return *this;}
十三、交换swap
将我们两个指针进行交换。
所以比我们库里的交换函数快,少了一次拷贝构造,和两次赋值重载。
//交换
void swap(string& s)
{std::swap(_str, s._str);std::swap(_capacity, s._capacity);std::swap(_size, s._size);
}
十四、find
就是在指定位置找。但是查找有两种方式
一种是查找字符
第二种是查找子串
函数原型:
size_t find(char c, size_t pos = 0)const
;从pos位置(包括pos位置)开始找字符
size_t find(const char* str, size_t pos = 0)const
;从pos位置(包括pos位置)开始找字符串
函数功能:找字符或者字符串,找到返回pos位置,找不到npos
① 找字符
size_t find(char ch, int pos = 0)
{assert(pos <= _size);for (size_t i = pos; i < _size; i++){if (_str[i] == ch){return i;}}return npos;
}
②找子串
这里我们使用strstr
函数功能:函数就是在一个字符串中找另外一个字符串是否存在,找到了就返回匹配的字符串的首元素地址,找不到就返回空指针
函数原型:
const char * strstr ( const char * str1, const char * str2 );
返回const型指针,对其解引用不能修改
char * strstr ( char * str1, const char * str2 );
//查找一个子串
size_t find(const char* str, size_t pos = 0)
{assert(pos < _size);char * p = strstr(_str + pos, str);if (p == nullptr){return npos;}else{//指针相减就是中间的个数return p - _str;}
}
十五、流插入,流提取
① 流插入
我们的流插入不一定就是友元函数,我们也可以用迭代器,和[]来找到他的数据
//流插入
ostream& operator<<(ostream& out, const string s)
{for (int i = 0; i < s.size(); i++){out << s[i];}return out;
}
②流提取
我们看下面这个流提取,并分析他为什么是错的
istream& operator>>(istream& in, string s)
{s.clear();char ch;in >> ch;while (ch != ' ' && ch != '\n'){s += ch;in >> ch;}return in;
}
cin
的缓冲区,输入多个数据,两个数据分割就是空格或者换行,编译器认为我们是要多个字符,会将所以我们的空格换行我们就拿不到,它一直在读数据。所以cin和scanf识别不了' '和'\n'
所以我们可以提取数据需要不区分空格的操作符。
正好C++种get
就不会区分,每一个字符我们都可以拿到。
int get();
istream& get (char& c);
istream& operator>>(istream& in, string& s)
{char ch = in.get(); //当一次键盘输入结束时会将输入的数据存入输入缓冲区,而cin对象直接从输入缓冲区中取数据;或者//char ch;//in.get(ch);while (ch != ' ' && ch != '\n'){s += ch;in.get(ch);}return in;
}
这个代码对于对象里面没有内容是没问题的,但是当对象里面一开始有数据时候就不可以了。
比如这种:
void test10(){string s1("0123456789");s1 += '\0';s1 += "xxxxxxxx";cout << s1 << endl;cout << s1.c_str() << endl;cin >> s1;cout << s1 << endl;}
解释:我们调试会发现是扩容的原因,具体是strcpy导致的!!
我们发现中间出现了一堆乱码,因为在输入123后,cin开始拿数据,首先拿到1,尾插数据,没问题。当我们再拿2尾插的时候,此时我们的size+1 > capacity.我们就需要扩容。
加上1后。此时里面的数据是这个样子的0123456789\0xxxxxxxx1\0
此时我们的capacity=20,size=20,size+1>capacity,需要二倍扩容
但是,我们这里的扩容使用的是strcpy,他遇到第一个\0就会停下来。导致拷贝过去数据其实是
0123456789\0,后面的都没有拷贝过去,但是我们的size没有变,仍然是20,只是中间的数字全部是随机值了。我们添加数字2和数字3仍然是在末尾处添加,最后打印发现中间都是乱码
解决:我们可以使用s.clear(),将字符串清空,这样就不会出现这种问题了。
void clear();
//会有频繁扩容的问题istream& operator>>(istream& in, string& s){s.clear();char ch = in.get(); //当一次键盘输入结束时会将输入的数据存入输入缓冲区,而cin对象直接从输入缓冲区中取数据;或者//char ch;//in.get(ch);while (ch != ' ' && ch != '\n'){s += ch;in.get(ch);}return in;}
但是这个代码其实还可以再优化一下,因为当我们输入的字符串太长时,会发生频繁扩容的问题。这个我们其实也可以解决的
//使用一个数组,将数据填到数组里面,填满了再加一次,然后将i置为0,再重投开始填数组istream& operator>>(istream& in, string& s){s.clear();char ch = in.get(); //当一次键盘输入结束时会将输入的数据存入输入缓冲区,而cin对象直接从输入缓冲区中取数据;char buffer[128];size_t i = 0;while (ch != ' ' && ch != '\n'){buffer[i++] = ch;if (i == 127){buffer[127] = '\0';s += buffer;i = 0;}ch = in.get();}if (i != 0){buffer[i] = '\0';//防止加了上一次后面遗留的数据s += buffer;i = 0;}return in;}
十六、全部源码
string.h
#pragma once#include <assert.h>//定义一个命名空间,防止和库里面的sring类冲突
namespace mwq
{class string{public:typedef char* iterator; //要放在公共区域typedef const char* const_iterator;//迭代器begin和enditerator begin(){return _str;}iterator end(){return _str + _size;}//const迭代器const iterator begin() const{return _str;}const iterator end() const{return _str + _size;}//string()// :_str(new char[1])// , _size(0)// , _capacity(0)//{// _str[0] = '\0'; //字符串结束标志,不写的话,直接打印s1.c_str()会崩溃,因为找不到\0//} //string(const char* str)// : _size(strlen(str))//{// _capacity = _size;// _str = new char[_capacity + 1];// strcpy(_str, str);//}//string(const char* str = nullptr) 不可以//string(const char* str = '\0')//string(const char* str = "\0")//构造函数string(const char* str = ""): _size(strlen(str)){_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);}//拷贝构造函数(深拷贝)string(const string& s):_size(s._size), _capacity(s._capacity){_str = new char[_capacity + 1];strcpy(_str, s._str);}//赋值运算符重载,所有情况统一处理,全部新开一个空间,然后将字符串拷贝到新空间,再销毁_str,再将新空间赋值给_strstring& operator=(const string& s){if (this != &s){char* temp = new char[s._capacity + 1]; //定义一个新空间,防止扩容失败,多开一个放\0strcpy(temp, s._str);delete[] _str;_str = temp;_size = s._size;_capacity = s._capacity;}return *this;}//[]运算符重载char& operator[](size_t pos){assert(pos < _size);return _str[pos];}//不可修改的[]const char& operator[](size_t pos) const{assert(pos < _size);return _str[pos];}//获取字符串元素个数size_t size()const{return _size;}//返回空间容量大小size_t capacity()const{return _capacity;}//判空bool empty()const{return _size == 0;}//clearvoid clear(){_str[0] = '\0';_size = 0;}//返回C形式的字符串const char* c_str() const{return _str;}//swap(s1, s2);//s1.swap(s2);void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}//s1>s2bool operator>(const string& s) const{return strcmp(_str, s._str) > 0;}//s1==s2bool operator==(const string& s) const{return strcmp(_str, s._str) == 0;}//s1>=s2bool operator>=(const string& s) const{return strcmp(_str, s._str) >= 0;}//s1<s2bool operator<(const string& s) const{return !(*this >= s);}//s1!=s2bool operator!=(const string& s) const{return !(*this == s);}//s1<=s2bool operator<=(const string& s) const{return !(*this > s);}//尾插字符string& push_back(char ch)//void push_back(char ch){if (_size + 1 > _capacity){if (_capacity == 0){_capacity = 3;}reserve(_capacity * 2);}_str[_size] = ch;++_size;_str[_size] = '\0';return *this;//insert(_size, ch);}//尾插字符串string& append(const char* str)//void append(const char* str){size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}strcpy(_str + _size, str);_size += len;return *this;//insert(_size, str);}//+=字符string& operator+=(char ch){push_back(ch);return *this;}//+=字符串string& operator+=(const char* str){append(str);return *this;}string& insert(size_t pos, char ch){assert(pos <= _size && pos >= 0);if (_size + 1 > _capacity){reserve(_capacity * 2);}size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];--end;}_str[pos] = ch;++_size;return *this;}string& insert(size_t pos, const char* str){assert(pos <= _size && pos >= 0);size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);//提前开空间}//挪动数据(包括\0)//循环size-pos+1次size_t end1 = _size;size_t end2 = _size + len;for (size_t i = 0; i <= _size - pos; ++i){_str[end2--] = _str[end1--];}//插入数据strncpy(_str + pos, str, len);_size += len;return *this;}//将pos位置的字符,横跨len个长度的串删除(包括pos)string& erase(size_t pos, size_t len = npos){//要删除的个数比pos之后的字符串长if (len == npos || len >= _size - pos){_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + pos + len);_size -= len;}return *this;}//reserve和resize//reserve不支持缩容void reserve(size_t n){if (n > _capacity){char* temp = new char[n + 1];strcpy(temp, _str);delete[] _str;_str = temp;_capacity = n;}}//不写第二个参数默认改为\0,使用缺省参数就可以将其合二为一//1:n <= size//2:size < n <= capacity//3:n>capacityvoid resize(size_t n, char c = '\0'){if (n < _size){memset(_str, c, n);_size = n;_str[_size] = '\0';}else{//if (n < _capacity)//{// memset(_str + _size, c, n - _size); //size后面的n个字符// _size = n;// _str[_size] = '\0';//}//else //扩容//{// reserve(n);// memset(_str + _size, c, n - _size); //size后面的n个字符// _size = n;// _str[_size] = '\0';//}if (n > _capacity){reserve(n);}memset(_str + _size, c, n - _size); //size后面的n个字符_size = n;_str[_size] = '\0';}}size_t find(char c, size_t pos = 0)const{assert(pos >= 0 && pos <= _size);for (size_t i = 0; i < _size; ++i){if (_str[i] == c){return i;}}return npos;}size_t find(const char* str, size_t pos = 0)const{assert(pos >= 0 && pos <= _size);char* p = strstr(_str, str);if (p != nullptr){size_t pos = p - _str;return pos;}return npos;}//析构函数~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}private:char* _str;size_t _size;size_t _capacity;static const size_t npos = -1;};void test1(){string s1;string s2("hello yaya");cout << s1.c_str() << endl;cout << s2.c_str() << endl;s2[0]++;cout << s1.c_str() << endl;cout << s2.c_str() << endl;}void test2(){string s1;string s2("hello yaya");string s3(s2);cout << s1.c_str() << endl;cout << s2.c_str() << endl;cout << s3.c_str() << endl;string s4;s4 = s3;cout << s4.c_str() << endl;}void test3(){string s1("hello yaya");//1:使用[]for (size_t i = 0; i < s1.size(); ++i){cout << s1[i] << " ";}cout << endl;//2:迭代器string::iterator it1 = s1.begin(); //iterator是类型,所以需要typedef一下while (it1 != s1.end()){cout << *it1 << " ";++it1;}cout << endl;//不能修改的版本/*string::const_iterator it2 = s1.begin();while (it2 != s1.end()){(*it2)++;cout << *it2 << " ";++it2;}*///3:范围for,不用实现,因为底层就是调用迭代器for (auto ch : s1){cout << ch << " ";}}void test4(){string s1;string s2("hello yaya");cout << (s1 > s2) << endl; //0cout << (s1 == s2) << endl;//0cout << (s1 >= s2) << endl;//0cout << (s1 < s2) << endl;//1cout << (s1 != s2) << endl;//1cout << (s1 <= s2) << endl;//1}void test5(){string s1("hello yaya");s1.push_back('a');s1.push_back('b');s1.append("haha");s1 += 'm';s1 += "xxxxxx";}void test6(){string s1("hello yaya");/*s1.insert(0, 'x');cout << s1.c_str() << endl;s1.insert(2, 'x');cout << s1.c_str() << endl;s1.insert(10, 'x');cout << s1.c_str() << endl;*/s1.insert(0, "123"); //第0个位置cout << s1.c_str() << endl;s1.insert(2, "qqq"); //第2个位置cout << s1.c_str() << endl;s1.insert(10, "mmm"); //第3个位置 cout << s1.c_str() << endl;s1.erase(12, 20);cout << s1.c_str() << endl;s1.clear();cout << s1.c_str() << endl;s1.insert(0, "123"); //第0个位置cout << s1.c_str() << endl;}void test7(){string s1("hello yaya");s1.resize(5, 'x');cout << s1.c_str() << endl;s1.resize(20, 'a');cout << s1.c_str() << endl;}void test8(){string s1("hello yaya");cout << s1.find('o', 0) << endl;cout << s1.find("yaya", 0) << endl;}//直接在函数外面定义全局函数,使用迭代器和[]也能找到数据,不一定非要用友元ostream& operator<<(ostream& out, const string& s){for (size_t i = 0; i < s.size(); ++i){out << s[i] << "";}return out;/*for (auto ch : s){out << ch << "";}return out; */}//不行,因为字符换字符之间是空格或者换行作为截至的,但是这里面从缓存区读取空格或者换行时,将其看成是一个字符了,导致无休止的输入//istream& operator>>(istream& in,string s)//{// char ch;// in >> ch; //当一次键盘输入结束时会将输入的数据存入输入缓冲区,而cin对象直接从输入缓冲区中取数据// while (ch != ' ' && ch != '\n')// {// s += ch;// in >> ch;// }// return in;//}//会有频繁扩容的问题istream& operator>>(istream& in, string& s){s.clear();char ch = in.get(); //当一次键盘输入结束时会将输入的数据存入输入缓冲区,而cin对象直接从输入缓冲区中取数据;或者//char ch;//in.get(ch);while (ch != ' ' && ch != '\n'){s += ch;in.get(ch);}return in;}使用一个数组,将数据填到数组里面,填满了再加一次,然后将i置为0,再重投开始填数组//istream& operator>>(istream& in, string& s)//{// s.clear();// char ch = in.get(); //当一次键盘输入结束时会将输入的数据存入输入缓冲区,而cin对象直接从输入缓冲区中取数据;// char buffer[128];// size_t i = 0;// while (ch != ' ' && ch != '\n')// {// buffer[i++] = ch;// if (i == 127)// {// buffer[127] = '\0';// s += buffer;// i = 0;// }// ch = in.get();// }// if (i != 0)// {// buffer[i] = '\0';//防止加了上一次后面遗留的数据// s += buffer;// i = 0;// }// return in;//}void test9(){string s1("hello yaya");cout << s1 << endl;;}void test10(){string s1("0123456789");s1 += '\0';s1 += "xxxxxxxx";cout << s1 << endl;cout << s1.c_str() << endl;cin >> s1;cout << s1 << endl;}};
test.c
#define _CRT_SECURE_NO_WARNINGS 1#include <iostream>
using namespace std;#include "string.h"int main()
{mwq::test10();return 0;
}