在看string类的模拟实现之前,需要理解stirng类的理解和使用,大家可以看一下我的上一篇讲解string的文章:string类的理解和使用和string的相关文档
目录
8.1 string& operator+=(char ch)
8.2 string& operator+=(const char* str)
9.1 void insert(size_t pos, char ch)
9.2 void insert(size_t pos, const char* str)
一、String类的构成
在上一篇文章string类的理解和使用中,我们讲过vs下string类的构成,如下图所示:

当字符串长度小于16时,会把数据存在_buff的数组中,我们在模拟实现的时候,没有实现这个功能,会将字符串内容都存在_str所指向的堆区。
二、String类的结构设计
我们会创建三个文件,如下所示:
1 test.cpp // 测试逻辑
2 string.cpp // string的方法
3 string.h // string类的声明

此外,为了很好的区分vs自带的string,我们会将我们模拟实现的string写在我们namaspace中,同时也能随时根据类域随时切换到vs自带的string,如下所示:

在string.cpp和test.cpp的文件中也会新建一个同名的类域,多个文件相同的namespace会合在一起。如下所示:


三、构造函数
在string类里面提供了很多的构造函数,我们这里只写两个构造函数,一个是无参的构造函数,另外一个是有参的构造函数
string的无参构造函数和 string的有参构造函数代码如下:
string()
:_str(nullptr)
,_size(0)
,_capacity(0)
{}
string(const char* str)
{
_size = strlen(str);
_capacity = _size;//_capacity不包含‘\0’,因此会在开空间的时候多开一个
_str = new char[_capacity + 1];
strcpy(_str, str);
}
因为此时我们没有重载<<运算符,所有不能通过cout<<s1<<endl;来输出,所以,我们先写一个c_str函数来获取存储字符串空间的地址,然后在通过cout来打印,代码如下:
c_str()函数:
const char* c_str() {
return _str;
}
测试代码1:测试代码是写到test.cpp的zx类域里面
namespace zx
{
void string_test1() {
string s1;
string s2("hello world");
cout << s1.c_str() << endl;
cout << s2.c_str() << endl;
}
}
这里的string前面没有加类域,会默认是zx类域里面我们模拟实现的string,这是因为在同一个类域里面,如果我们想要切换到库里面自带的string,就可以在前面加一个std类域就可以了。
我们在test.cpp里面来调用这个测试函数:zx::string_test1();
我们发现输出不了,这是为什么呢?
我们来切换库里面的string类,代码如下所示:
namespace zx
{
void string_test1() {
std::string s1;
std::string s2("hello world");
cout << s1.c_str() << endl;
cout << s2.c_str() << endl;
}
}
然后再在test.cpp里面来调用这个测试函数:zx::string_test1();
输出如下所示:

因此我们模拟实现写的string是有问题的。
我们在创建s1对象时,没有传入参数,s1的_str是指向nullptr的,会出现错误,因此我们在写string的无参构造函数时,应该给他一个额外的字符空间,用来存放‘\0'.代码如下所示:
string()
:_str(new char[1] {'\0'}) //或者_str(new char('\0'))
,_size(0)
,_capacity(0)
{}
我们再把string的类域切换为zx或者把std::去掉,去掉std::之后他就会优先在namespace里面取寻找string类,然后执行测试,发现能够像库里面的这样输出了。
最后,我们可以将无参构造函数和有参构造函数合并,代码如下:
string(const char* str="")
{
_size = strlen(str);
_capacity = _size;//_capacity不包含‘\0’,因此会在开空间的时候多开一个
_str = new char[_capacity + 1];
strcpy(_str, str);
}
当我们没有传入参数时,默认参数有一个空字符串,空字符串里面时默认包含’\0‘的并且strcpy函数也能拷贝这个’\0‘,因此能够使用这种方法来实例化一个空串。
四、析构函数
我们在堆区里面开辟了数据,因此需要写析构函数来释放堆区的数据,函数如下:
~string() {
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
五、遍历
在string类的理解和使用中我们写了三种方法来遍历string,这里我们也写同样的三种方法。如下所示:
5.1 重载[]
在重载[]之前,我们先写一个返回_size()的函数,如下所示:
size_t size() const{
return _size;
}
string类对象有普通对象和常对象,所有我们应该写两个版本的重载[]的函数,此外,在库里面的string,我们可以使用[]来修改字符串,因此我们应该返回对应位置的引用,如下所示:
char& operator[](int pos) {
assert(pos < _size && pos >= 0);
return _str[pos];
}
const char& operator[](int pos) const{
assert(pos < _size && pos >= 0);
return _str[pos];
}
测试代码如下:测试代码是写到test.cpp的zx类 域里面
void string_test2() {
zx::string s1("hello world");
for (int i = 0; i < s1.size(); i++) {
s1[i] += 2;
}
cout <<s1.c_str()<<endl;
}
我们在test.cpp来调用这个函数:zx::string_test2();
运行结果如下:
模拟实现的string运行起来和库里面自带的string一样
5.2 范围for
那我们模拟实现string能支持范围for遍历嘛?我们可以写个代码来测试一下:测试代码是写到test.cpp的zx类域里面
void string_test3() {
zx::string s1("hello world");
for (auto ch : s1) {
cout << ch;
}
cout << endl;
}
我们来运行这个函数:zx::string_test3();运行之后发现报错了

这是为什么呢?在string的类的理解和使用里面,我们说过范围for的底层就是替换成迭代器。但是我们模拟实现的string类里面是没有迭代器的。于是我们可以使用一个char* 类型的指针来模拟一下迭代器。如下所示:
5.3 自己写一个迭代器
自己模拟实现的迭代器如下所示:
typedef char* iterator;
iterator begin() {
return _str;
}
iterator end() {
return _str+_size;
}
然后写一个测试代码来使用迭代器来遍历string,代码如下:
void string_test4() {
string s1("hello world");
string::iterator it = s1.begin();
while (it != s1.end()) {
cout << *it;
it++;
}
cout << endl;
}
我们来运行这个函数:zx::string_test4();运行结果如下:

我们在实现迭代器之后,再去执行上面的范围for遍历,发现上面的代码也不会报错了。
我们在学到后面其他的STL容器,如list,queue等等,会发现这些容器里面都有iterator,这些iterator实际上都是typedef出来的,但不是char*类型,在list容器里面的iterator是这样定义的,如下所示:

为什么我们能用原生指针char*来模拟实现string类的iterator呢?这个主要的原因就是string类里的字符串数据是连续存放的。
迭代器很好了体现了面向对象的封装特性:屏蔽了底层细节,提供了统一的类似访问容器的方式,不需要关心容器底层结构和实现细节。
除了这个迭代器之外,还有const迭代器,定义如下:
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
iterator end() {
return _str+_size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
六、pushback的实现
在上面的函数都是比较简单的,我们都是直接写在类里面的,现在我们来实现声明和实现相分离,在类域的string里面声明函数,然后在string.cpp的类域里面实现这函数。
在pushback之前,我们会首先检查容量满了没有,我们可以写一个reserve函数来增加空间。代码如下:
void string::reserve(size_t n) {
if (n > _capacity) {
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
pushback代码如下:
void string::pushback(char ch) {
if (_size == _capacity) {
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
测试代码如下:
void string_test5() {
string s1("hello world");
s1.pushback(' ');
s1.pushback('$');
cout << s1.c_str() << endl;
}
运行结果如下:

七、append的实现
append代码实现如下:
void string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity) {
//大于2倍,需要多少开多少,小于2倍按2倍扩空间。
reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
}
strcpy(_str + _size, str);
_size += len;
}
测试代码如下:
void string_test6() {
string s1("hello");
s1.append(" world$$$$$$");
cout << s1.c_str() << endl;
}
运行结果如下:

八、+=运算符的重载
+=运算符有两个版本,可以加一个字符,也可以加一个字符串。
8.1 string& operator+=(char ch)
在上面我们写了pushback来追加一个字符,我们加一个字符可以来调用这个函数,如下所示:
string& string::operator+=(char ch)
{
pushback(ch);
return *this;
}
测试代码如下:
void string_test7() {
string s1("hello");
s1 += ' ';
s1 += '#';
cout << s1.c_str() << endl;
}
运行结果如下:

8.2 string& operator+=(const char* str)
我们在上面写了一个追加字符串的函数,这样函数同样可以调用append函数。如下所示:
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
测试代码如下所示:
void string_test8() {
string s1("hello");
s1 += " world$$$$$$";
cout << s1.c_str() << endl;
}
运行结果如下所示:

九、insert的实现
insert我们提供两种版本,可以往一个位置插入一个字符,也可以往一个位置插入一个字符串。
9.1 void insert(size_t pos, char ch)
代码如下:
void string::insert(size_t pos, char ch) {
assert(pos >= 0 && pos <= _size);
if (_size == _capacity) {
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
size_t end = _size;
while (end>=pos) {
_str[end+1] = _str[end];
end--;
}
_str[pos] = ch;
_size++;
}
我们分别在第一个位置,最后一个位置和中间一个位置插入一个字符,代码如下:
void string_test9() {
string s1("hello");
s1.insert(3, '!');
s1.insert(s1.size(), '@');
s1.insert(0, '#');
cout << s1.c_str() << endl;
}
我们发现会运行错误,这是为什么呢?当我们往0这个位置插入字符的时候,end=0,把0这个位置的字符往后挪之后,end--会变成-1,在内存里面是全1.全1肯定会大于pos的值,因此会无限的执行下去。
那我们把end改成int类型可以吗?也是不可以的,当int类型的end=-1,和size_t类型的pos比较,会悄悄地类型转换,会转换为size_t类型的比较。
把函数的参数改为int pos可以解决,但是库里面的pos都是size_t类型的。
方法一:在比较的时候把pos强转为int进行比较
void string::insert(size_t pos, char ch) {
assert(pos >= 0 && pos <= _size);
if (_size == _capacity) {
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
size_t end = _size;
while (end>=(int)pos) {
_str[end+1] = _str[end];
end--;
}
_str[pos] = ch;
_size++;
}
方法二:我们不让end指向最后一个’\0'的位置,而是指向\0'的下一个位置,让 _str[end] = _str[end-1],当end=1的时候,会把下标为0的元素移到前面,移完之后end=0,并不会出现-1这种情况。
void string::insert(size_t pos, char ch) {
assert(pos >= 0 && pos <= _size);
if (_size == _capacity) {
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
size_t end = _size+1;
while (end>pos) {
_str[end] = _str[end-1];
end--;
}
_str[pos] = ch;
_size++;
}
这两种方法都能解决这个问题,运行结果如下所示:

9.2 void insert(size_t pos, const char* str)
代码如下:
void string::insert(size_t pos, const char* str) {
assert(pos >= 0 && pos <= _size);
size_t len = strlen(str);
if (_size+len > _capacity) {
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
size_t end = _size + len;
while (end > pos + len-1) {
_str[end] = _str[end - len];
end--;
}
for (int i = 0; i < len; i++) {
_str[pos + i] = str[i];
}
_size += len;
}
我们分别在第一个位置,最后一个位置和中间一个位置插入一个字符串,代码如下:
void string_test10() {
string s1("hello");
s1.insert(1, "###");
s1.insert(s1.size(), "###");
s1.insert(0, "!!");
cout << s1.c_str() << endl;
}
运行结果如下:

十、erase的实现
在实现erase的时候,为了和库里面保持一致吗,我们也使用一个静态成员npos,
定义如下:

代码如下:void erase(size_t pos, size_t len = npos);//函数声明
void string::erase(size_t pos, size_t len) {
assert(pos < _size);
if (len >= _size- pos ) {
_size = pos;
_str[pos] = '\0';
}
else {
while (pos <= _size-len) {
_str[pos] = _str[pos + len];
pos++;
}
_size -= len;
}
}
测试代码如下:
void string_test11() {
string s1("hello");
s1.erase(1, 2);
cout << s1.c_str() << endl;
string s2("hello");
s2.erase(1);
cout << s2.c_str() << endl;
string s3("hello");
s3.erase(1,100);
cout << s3.c_str() << endl;
}
运行结果如下:

十一、find的实现
代码如下:size_t find(char ch, size_t pos = 0);size_t find(const char* str, size_t pos = 0);//声明
size_t string::find(char ch, size_t pos)
{
for (size_t i = pos; i < _size; i++) {
if (_str[i] == ch) {
return i;
}
}
return npos;
}
size_t string::find(const char* str, size_t pos) {
assert(pos < _size);
const char* ptr = strstr(_str + pos, str);
if (ptr == nullptr) {
return npos;
}
else {
return ptr - _str;
}
}
十二、substr的实现
这个函数的作用是取一个字符串的字串。
代码如下所示:string substr(size_t pos = 0, size_t len = npos); //声明
string string::substr(size_t pos, size_t len) {
assert(pos < _size);
if (len > _size-pos) {
len = _size - pos;
}
string ret;
ret.reserve(len);
for (int i = 0; i < len; i++) {
ret += _str[pos + i];
}
return ret;
}
测试代码如下所示:
void string_test12()
{
string s1("hello world");
string s2 = s1.substr(6, 10);
cout << s2.c_str() << endl;
string s3 = s1.substr(6);
cout << s3.c_str() << endl;
string s4 = s1.substr(0,5);
cout << s4.c_str() << endl;
}
运行如下所示:

一些编译器可能会做一些优化,把中间的临时对象省略了,会让s2和函数里面的ret指向同一空间,这就会导致substr函数结束之后会释放堆区空间,s2就找不到堆区的字符串了。解决方法之一是写一个深拷贝。
十三、拷贝构造函数
自动生成的拷贝构造函数是一个浅拷贝,可能会带来各种各样的问题,因此,如果类成员有指向堆区的指针,我们都应该自己手动的写一个深拷贝。如下所示:
string(const string& s) {
_str = new char[s._capacity+1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;}
十四、赋值运算符重载
我们在没有写赋值运算符重载的话,类会自动生成一个赋值运算符重载的函数,但是类自己实现的这个函数也是一个浅拷贝。
例如,string s1("hello");string s2("world");s2=s1;
如果没有写深拷贝的话,s1会一个字节一个字节的拷贝自己的数据给s2。导致s2和s1的_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;
}
十五、字符串比较
和库里一样,我们将比较函数写为全局函数,代码如下:
bool operator<(const string& s1, const string& s2) {
return strcmp(s1.c_str(), s2.c_str())<0;
}
bool operator<=(const string& s1, const string& s2) {
return s1 < s2 || s1 == s2;
}
bool operator>(const string& s1, const string& s2) {
return !(s1 < s2);
}
bool operator>=(const string& s1, const string& s2) {
return !(s1 <= s2);
}
bool operator==(const string& s1, const string& s2) {
return strcmp(s1.c_str(), s2.c_str()) == 0;
}
bool operator!=(const string& s1, const string& s2) {
return !(s1 == s2);
}
在库里面的string里面提供了各种各样的比较,如下所示:

但是很多都是多余的
例如“hello"和一个s1比较,字符串“hello”会隐式类型转换为string类型的对象,也同样能调用上面我们所写的运算符重载函数。
十六、流插入运算符<<重载
在上面我们输出string都是通过cout << s4.c_str() << endl;并不能通过cout << s4<<endl;来输出,这是因为我们还没有重载流插入运算符>>,我们可以重载这个运算符,代码如下所示:
ostream& operator<<(ostream& out, const string& s) {
for (auto ch : s) {
out << ch;
}
return out;
}
这样我们就可以通过cout << s4<<endl;来输出string对象了因为我们这个函数里面没有访问私有成员,所以不用在类里面加友元声明。
十七、流提取运算符>>重载
库里的string类式可以通过cin来直接给string对象来赋值的,因为重载了流提取运算符>>。代码如下:
istream& operator >> (istream& in, string& s) {
char ch;
in >> ch;
while (ch != ' ' && ch != '\n') {
s += ch;
in >> ch;
}
return in;
}
我们来测试的时候,发现有bug,这是因为cin会忽略空格和换行符,也就会提取不到空格和换行,因此这个函数就会一直执行。
所以我们就需要使用getchar函数或者get函数来一个字符一个字符读取,如下所示:
istream& operator >> (istream& in, string& s) {
char ch;
/*in >> ch;*/
ch = in.get();
while (ch != ' ' && ch != '\n') {
s += ch;
//in >> ch;
ch = in.get();
}
return in;
}
但是我们在测试的时候还是出现了一个小bug,如下所示:
void string_test15() {
string s1("hello");
cin >> s1;
cout << s1 << endl;
}
运行结果如下:

我们输入asd,他会追加到hello的后面,这是因为没有清空原本的数据。所以我们在输入之前,需要写一个clear的函数将这个string对象的内容都删除,将size设置为0;capacity的大小不变。
void clear() {
_str[0] = '\0';
_size = 0;
}
在string类里面增加这个函数,然后输入之前调用这个函数,代码如下所示:
istream& operator >> (istream& in, string& s) {
s.clear();
char ch;
/*in >> ch;*/
ch = in.get();
while (ch != ' ' && ch != '\n') {
s += ch;
//in >> ch;
ch = in.get();
}
return in;
}
运行结果如下:

17.1 优化
当我们输入的字符太多了,例如输入1000个字符a。这个函数就会调用1000次+=运算符。有可能会导致效率及低,这是因为会频繁的插入,会导致扩容,扩容的消耗是很大的。
那我们能提前开好空间嘛??这样也是不行的,因为不知道要输入多少个字符。
如果reserve(1024),可能开的太多,如果reserve(50),开的空间也可能太少。很难确定。
我们可以使用一个buff数组来存放256个字符,每次输出256个字符之后,就把这个字符串加到string里面。代码如下:
istream& operator >> (istream& in, string& s) {
s.clear();
const int N = 256;
char buff[N];
int i = 0;
char ch;
ch = in.get();
while (ch != ' ' && ch != '\n') {
buff[i++] = ch;
if (i == N - 1) {//最后一个位置
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
if (i > 0) {//输入空格或者换行结束之后,buff里面还有数据,需要加到string里面
buff[i] = '\0';
s += buff;
}
return in;
}
这样写的好处是:
- 如果输入的字符串很长,不会频繁的扩容,每插入255才会扩一次容。
- 如果输入的字符串很短,string开的空间也不会很大。
十八、拷贝构造函数的现代写法
我们在上面写过一个深拷贝的拷贝构造函数,那个拷贝构造函数完全是由我们自己开实现的。我们可以有其他更好的写法,代码如下所示:
string(const string& s) {
string tmp(s._str);
swap(_str, tmp._str);
swap(_size, tmp._size);
swap(_capacity, tmp._capacity);
}
我们使用s的字符串创建一个新的string类型的对象tmp。
然后依次来交换当前对象和tmp对象的每一个成员。
但是这样写是有一个小问题的,因为我们的自定义类型是不会初始化的,把当前对象的_str交换给tmp的_str。我们也不知道这个_str里面是什么内容,当这个函数结束后,会析构tmp对象,,有可能导致错误,所以我们可以在定义类成员的时候给缺省值,这个缺省值主要是给没有显示在初始化列表初始化的成员使用的。如下所示:
private:
char* _str=nullptr;
size_t _size=0;
size_t _capacity=0;
然后我们可以把这个交换给封装起来,如下所示:
void swap(string& s) {
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity,s._capacity);
}
string(const string& s) {
string tmp(s._str);
swap(tmp);
}
代码解读:当我们想用一个已经初始化的对象来创建一个新的对象时,就会调用这个拷贝构造函数,然后再使用s的字符串创建一个临时string类型的对象tmp,然后在调用上面上面所写的swap函数来交换。
十九、赋值运算符重载的现代写法
有了上面的铺垫,我们也同样可以使用上面的方法来写赋值运算符重载函数,代码如下所示:
string& operator=(const string& s) {
if (this != &s) //用来防止自己给自己赋值,也可以不用这个判断了,传统的写法需要判断
{
string tmp(s._str); //这个调用有参构造函数//string tmp(s); //这个调用拷贝构造函数
swap(tmp);
}
return *this;
}
这样写就不用自己释放原本的资源了,他会通过swap函数把原来的string全部交换给tmp对象,等到函数结束,tmp会自动的调用析构函数。这样感觉我们什么都没有写,tmp这个临时对象给我们都弄好了。
既然我们在函数里面要再创建一个string对象,为什么不在使用形参来创建一个tmp对象呢?
下面我们来看一个更加优化的写法,如下所示:
//s2=s1
string& operator=(string tmp) {
swap(tmp);
return *this;
}
两行代码就搞定了赋值运算符重载函数。
二十、代码
20.1 string.h
#define _CRT_SECURE_NO_WARNINGS 0
#pragma once
#include<iostream>
#include<assert.h>
#include<string>
using namespace std;
namespace zx
{
class string {
public:
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
iterator end() {
return _str+_size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
//string()
// :_str(new char[1] {'\0'}) //或者_str(new char('\0'))
// ,_size(0)
// ,_capacity(0)
//{}
string(const char* str="")
{
_size = strlen(str);
_capacity = _size;//_capacity不包含‘\0’,因此会在开空间的时候多开一个
_str = new char[_capacity + 1];
strcpy(_str, str);
}
/*string(const string& s) {
_str = new char[s._capacity+1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}*/
/*string(const string& s) {
string tmp(s._str);
swap(_str, tmp._str);
swap(_size, tmp._size);
swap(_capacity, tmp._capacity);
}*/
void swap(string& s) {
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity,s._capacity);
}
string(const string& s) {
string tmp(s._str);
swap(tmp);
}
//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;
//}
////s2=s1
//string& operator=(const string& s) {
// if (this != &s) //用来防止自己给自己赋值
// {
// string tmp(s._str);
// swap(tmp);
// }
// return *this;
//}
//s2=s1
string& operator=(string tmp) {
swap(tmp);
return *this;
}
~string() {
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
const char* c_str() const{
return _str;
}
size_t size() const{
return _size;
}
size_t capacity() const {
return _capacity;
}
char& operator[](int pos) {
assert(pos < _size && pos >= 0);
return _str[pos];
}
const char& operator[](int pos) const{
assert(pos < _size && pos >= 0);
return _str[pos];
}
void clear() {
_str[0] = '\0';
_size = 0;
}
void reserve(size_t n);
void pushback(char ch);
void append(const char* str);
string& operator+=(char ch);
string& operator+=(const char* str);
void insert(size_t pos, char ch);
void insert(size_t pos, const char* str);
void erase(size_t pos, size_t len = npos);
size_t find(char ch, size_t pos = 0);
size_t find(const char* str, size_t pos = 0);
string substr(size_t pos = 0, size_t len = npos);
private:
char* _str=nullptr;
size_t _size=0;
size_t _capacity=0;
static const size_t npos;
};
bool operator<(const string& s1, const string& s2);
bool operator<=(const string& s1, const string& s2);
bool operator>(const string& s1, const string& s2);
bool operator>=(const string& s1, const string& s2);
bool operator==(const string& s1, const string& s2);
bool operator!=(const string& s1, const string& s2);
ostream& operator<<(ostream& out, const string& s);
istream& operator >> (istream& in, string& s);
}
20.2 string.cpp
#include"string.h"
namespace zx
{
const size_t string::npos = -1;
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::pushback(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) {
//大于2倍,需要多少开多少,小于2倍按2倍扩空间。
reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
}
strcpy(_str + _size, str);
_size += len;
}
string& string::operator+=(char ch)
{
pushback(ch);
return *this;
}
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
void string::insert(size_t pos, char ch) {
assert(pos >= 0 && pos <= _size);
if (_size == _capacity) {
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
size_t end = _size+1;
while (end>pos) {
_str[end] = _str[end-1];
end--;
}
_str[pos] = ch;
_size++;
}
void string::insert(size_t pos, const char* str) {
assert(pos >= 0 && pos <= _size);
size_t len = strlen(str);
if (_size+len > _capacity) {
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
size_t end = _size + len;
while (end > pos + len-1) {
_str[end] = _str[end - len];
end--;
}
for (int i = 0; i < len; i++) {
_str[pos + i] = str[i];
}
_size += len;
}
void string::erase(size_t pos, size_t len) {
assert(pos < _size);
if (len >= _size- pos ) {
_size = pos;
_str[pos] = '\0';
}
else {
while (pos <= _size-len) {
_str[pos] = _str[pos + len];
pos++;
}
_size -= len;
}
}
size_t string::find(char ch, size_t pos)
{
for (size_t i = pos; i < _size; i++) {
if (_str[i] == ch) {
return i;
}
}
return npos;
}
size_t string::find(const char* str, size_t pos) {
assert(pos < _size);
const char* ptr = strstr(_str + pos, str);
if (ptr == nullptr) {
return npos;
}
else {
return ptr - _str;
}
}
string string::substr(size_t pos, size_t len) {
assert(pos < _size);
if (len > _size-pos) {
len = _size - pos;
}
string ret;
ret.reserve(len);
for (int i = 0; i < len; i++) {
ret += _str[pos + i];
}
return ret;
}
bool operator<(const string& s1, const string& s2) {
return strcmp(s1.c_str(), s2.c_str())<0;
}
bool operator<=(const string& s1, const string& s2) {
return s1 < s2 || s1 == s2;
}
bool operator>(const string& s1, const string& s2) {
return !(s1 < s2);
}
bool operator>=(const string& s1, const string& s2) {
return !(s1 <= s2);
}
bool operator==(const string& s1, const string& s2) {
return strcmp(s1.c_str(), s2.c_str()) == 0;
}
bool operator!=(const string& s1, const string& s2) {
return !(s1 == s2);
}
ostream& operator<<(ostream& out, const string& s) {
for (auto ch : s) {
out << ch;
}
return out;
}
//istream& operator >> (istream& in, string& s) {
// s.clear();
// char ch;
// /*in >> ch;*/
// ch = in.get();
// while (ch != ' ' && ch != '\n') {
// s += ch;
// //in >> ch;
// ch = in.get();
// }
// return in;
//}
istream& operator >> (istream& in, string& s) {
s.clear();
const int N = 256;
char buff[N];
int i = 0;
char ch;
ch = in.get();
while (ch != ' ' && ch != '\n') {
buff[i++] = ch;
if (i == N - 1) {//最后一个位置
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
if (i > 0) {//输入空格或者换行结束之后,buff里面还有数据,需要加到string里面
buff[i] = '\0';
s += buff;
}
return in;
}
}
20.3 test.cpp
#include"string.h"
namespace zx {
void string_test1() {
zx::string s1;
zx::string s2("hello world");
cout << s1.c_str() << endl;
cout << s2.c_str() << endl;
}
void string_test2() {
zx::string s1("hello world");
for (int i = 0; i < s1.size(); i++) {
s1[i] += 2;
}
cout << s1.c_str() << endl;
}
void string_test3() {
zx::string s1("hello world");
for (auto ch : s1) {
cout << ch;
}
cout << endl;
}
void string_test4() {
string s1("hello world");
string::iterator it = s1.begin();
while (it != s1.end()) {
cout << *it;
it++;
}
cout << endl;
}
void string_test5() {
string s1("hello world");
s1.pushback(' ');
s1.pushback('$');
cout << s1.c_str() << endl;
}
void string_test6() {
string s1("hello");
s1.append(" world$$$$$$");
cout << s1.c_str() << endl;
}
void string_test7() {
string s1("hello");
s1 += ' ';
s1 += '#';
cout << s1.c_str() << endl;
}
void string_test8() {
string s1("hello");
s1 += " world$$$$$$";
cout << s1.c_str() << endl;
}
void string_test9() {
string s1("hello");
s1.insert(1, '!');
s1.insert(s1.size(), '@');
s1.insert(0, '#');
cout << s1.c_str() << endl;
}
void string_test10() {
string s1("hello");
s1.insert(1, "###");
s1.insert(s1.size(), "###");
s1.insert(0, "!!");
cout << s1.c_str() << endl;
}
void string_test11() {
string s1("hello");
s1.erase(1, 2);
cout << s1.c_str() << endl;
string s2("hello");
s2.erase(1);
cout << s2.c_str() << endl;
string s3("hello");
s3.erase(1,100);
cout << s3.c_str() << endl;
}
void string_test12()
{
string s1("hello world");
string s2 = s1.substr(6, 10);
cout << s2.c_str() << endl;
string s3 = s1.substr(6);
cout << s3.c_str() << endl;
string s4 = s1.substr(0,5);
cout << s4.c_str() << endl;
}
void string_test13() {
string s1("hello");
string s2("helle");
cout << (s1 < s2) << endl;
cout << ("hello world" == "hello world") << endl;
}
void string_test14() {
string s1("hello");
cout << s1 << endl;
}
void string_test15() {
string s1("hello");
cin >> s1;
cout << s1 << endl;
}
void string_test16(){
string s1("hello world");
string s2(s1);
cout << s2 << endl;
}
}
int main() {
//zx::string_test1();
//zx::string_test2();
//zx::string_test3();
//zx::string_test4();
//zx::string_test5();
//zx::string_test6();
//zx::string_test7();
//zx::string_test8();
//zx::string_test9();
//zx::string_test10();
//zx::string_test11();
//zx::string_test12();
//zx::string_test13();
//zx::string_test14();
//zx::string_test15();
zx::string_test16();
return 0;
}
1027

被折叠的 条评论
为什么被折叠?



