【C++】string模拟实现

各位读者老爷好,俺最近在学习string的一些知识。为了更好的了解string的结构,俺模拟实现了一个丐版string,有兴趣的老爷不妨垂阅!!!

目录

1.string类的定义

2.模拟实现成员函数接口 

2.1.constructor(构造函数)

2.2.destructor(析构函数)

2.3.c_str 

 2.4.size

 2.5.operator[]

2.6.begin、end

2.7.capacity

2.8.reserve 

2.9.push_back 

2.10.append 

2.11.operator+=

2.12.insert 

2.13.erase 

2.14.find

 2.15.substr

2.16.constructor

2.17.operator=

 2.18.clear

 2.19.swap

 3.模拟实现非成员函数接口

3.1.relational operators

3.2.operator<< 

3.3.operator>> 

3.4.getline

4.个别接口的不同写法

 4.1.constructor的不同写法

4.2.operator=的不同写法

4.3.operator>>的不同写法

5. string模拟实现完整代码

5.1.string.h

5.2.string.cpp 

5.3.test.cpp 


俺在上篇博客介绍过,string是一个由类模板实例化而来的类,是用来管理字符串的。其底层实现大致就是字符顺序表。

那么俺模拟实现string,是不是要从类模板开始搞起呢?当然不是,俺可没有这个本事。俺直接定义string类就好。并且string的接口众多,俺只模拟实现部分常用接口噢噢!!

1.string类的定义

namespace HD
{class string{char* _str = nullptr;size_t _capacity = 0;size_t _size = 0;public:typedef char* iterator;typedef const char* const_iterator;static const size_t npos;//string()//	:_str(new char[1]{'\0'})//	,_capacity(0)//	,_size(0)//{//}//string(const char* s)//{//	_size = strlen(s);//	_capacity = _size;//	_str = new char[_capacity+1];//	strcpy(_str, s);//}string(const char* s = "")//以上两构造合二为一{_size = strlen(s);_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, s);}~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}const  char* c_str()const{return _str;}size_t size()const{return _size;}char& operator[](size_t pos){assert(pos <= _size);return _str[pos];}const char& operator[](size_t pos)const{assert(pos <= _size);return _str[pos];}iterator begin(){return _str;}iterator end(){return _str + _size;}const_iterator begin()const{return _str;}const_iterator end()const{return _str + _size;}size_t capacity()const{return _capacity;}string(const string& str)//拷贝构造函数{/写法一//*_str = new char[str._capacity + 1];strcpy(_str, str._str);_size = str._size;_capacity = str._capacity;*//写法二/string tmp(str.c_str());swap(tmp);}void reserve(size_t n = 0);void push_back(char c);string& append(const char* s);string& operator+= (const char* s);string& operator+=(char c);string& insert(size_t pos, size_t n, char c);string& insert(size_t pos, const char* s);string& erase(size_t pos = 0, size_t len = npos);size_t find(char c, size_t pos = 0) const;size_t find(const char* s, size_t pos = 0) const;string substr(size_t pos = 0, size_t len = npos) const;string& operator=(const string& str);//string& operator=(const string str);void clear();void swap(string& str);};
}

对于这个string类的定义如上。

1.俺们知道string类底层是字符顺序表,所以俺定义3个private成员变量_str、_capacity、_size来实现,并且给这3个成员变量缺省值,这3个缺省值是很有意义的,意义的体现会在下面的讲解可见。

2.俺将一些成员函数(接口)的声明和定义全部放到string类当中,则默认当成内联函数处理;还有一些成员函数(接口)的声明和定义分离,只将这些成员函数的声明放到string类当中。

3.为了防止与命名空间std的string(也就是STL的string)产生冲突,俺实现的string使用自己命名空间(如HD)封装起来。

PS:以下所说的string若没有特殊声明,都默认指俺模拟实现的string。若指STL的string,俺会用红色字体标注string,如string

2.模拟实现成员函数接口 

2.1.constructor(构造函数)

对于这个接口,string内重载了很多个函数。俺暂时就模拟实现两个,如下:


1.string()

这个函数的模拟实现如下,俺将其声明和定义都放到string类中:

		string():_str(new char[1]{'\0'}),_capacity(0),_size(0){}

这里俺使用初始化列表来初始化。易知这是一个构造空的string对象的,那么当然将_capacity和_size初始化成0;但是对于_str,俺将其指向堆区动态开辟的一个字节的空间,这块空间存储字符'\0'。

为什么_str不用nullptr来初始化呢?其实大有意义,让子弹再飞一会,原因俺下面介绍。


3.string (const char* s)

这个函数的模拟实现如下,俺将其声明和定义都放到string类中:

		string(const char* s){_size = strlen(s);_capacity = _size;_str = new char[_capacity+1];strcpy(_str, s);}

这里俺在函数体内初始化。俺们知道这个构造函数的功能是用C-string来构造string类对象。那么我们开好空间(记得多开一个空间存放'\0'),将C_string(也就是s)拷贝到空间上,_size和_capacity 初始化为C_string的字节数(长度)就好。


2.2.destructor(析构函数)

这个接口就是析构函数,string类中申请了资源,俺们必须显示写析构函数,不然会造成资源的泄露。这个接口的模拟实现如下,俺将其声明和定义都放到string类中:

		~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}

若不显示写析构函数,_str指向的空间就泄露了。

2.3.c_str 

 这个接口功能就是返回C形式的字符串,也就是返回底层的_str。这个接口的模拟实现如下,俺将其声明和定义都放到string类中:

		const  char* c_str()const{return _str;}

这是一个对this指针指向对象的成员变量只进行读访问的函数,所以我们将其实现成const成员函数,且这样子普通对象和const对象都可调用这个函数。


俺们来使用以下自己模拟实现的接口。事先要先知道的是本工程包含3个文件

……

string.h:存放string类的定义和非成员函数的声明;

……

string.cpp:存放string类部分成员函数的定义、静态成员变量的定义和非成员函数的定义;

……

test.cpp:存放测试代码,来测试咱们模拟实现的string的功能;

……

俺们到现在介绍了几个接口的模拟实现了,咱们在test.cpp中试试:

#include"string.h"
namespace HD
{void test1(){string S1;string S2("hello world");cout << S1.c_str() << endl << S2.c_str() << endl;}
}
int main()
{HD::test1();return 0;
}

运行结果:

 有几个点需要明确:

……

1.为什么test1函数需要用命名空间HD封装起来?

因为若是不用命名空间HD封装起来,string类实例化对象S1、S2会调用string的构造函数接口,且c_str接口的调用亦是调用string的接口,这样子就违背了我们测试模拟实现的接口的初衷。有关命名空间和编译器访问规则的介绍可以看[C++]C++入门1.0。

……

2.主函数中调用test1函数为什么要指定命名空间HD域?

因为若是不指定命名空间HD域,编译器会访问不到test1函数。编译器的访问规则介绍可以看[C++]C++入门1.0。

……

3.string()函数的定义中,_str不用nullptr来初始化呢?

若是用nullptr来初始化_str,当空string对象调用c_str接口时就返回nullptr,当我们对这个返回值进行操作时就有可能访问到空指针,程序就会崩溃。


 2.4.size

 这个接口这个接口返回字符串的长度。这个接口的模拟实现如下,俺将其声明和定义都放到string类中:

		size_t size()const{return _size;}

 这是一个对this指针指向对象的成员变量只进行读访问的函数,所以我们将其实现成const成员函数,且这样子普通对象和const对象都可调用这个函数。

在test.cpp中测试一下:

#include"string.h"
namespace HD
{void test2(){string S1;string S2("hello world");cout << S1.size() << endl << S2.size() << endl;}
}
int main()
{HD::test2();return 0;
}

运行结果:

 2.5.operator[]

本接口返回对字符串中位置pos处的字符的引用。且string中本接口重载了2个函数分别供普通对象和const对象使用。俺也模拟实现2个函数,并将其声明和定义都放到string类中:

		char& operator[](size_t pos){assert(pos <= _size);return _str[pos];}const char& operator[](size_t pos)const{assert(pos <= _size);return _str[pos];}

在test.cpp中测试一下:

#include"string.h"
namespace HD
{void test3(){string S1("nbo");for (int i = 0; i < S1.size(); i++){S1[i] -= 1;cout << S1[i];}cout << endl;const string S2("hello world");for (int i = 0; i < S2.size(); i++){cout << S2[i];}}
}
int main()
{HD::test3();return 0;
}

对于char& operator[](size_t pos)这个函数,这个函数是一个对this指针指向对象的成员变量只进行读访问的函数,可是我们不能将其实现成const成员函数,为什么呢?

因为俺们实现了const char& operator[](size_t pos)const这个函数供const对象调用,若是还将char& operator[](size_t pos)实现成const成员函数的话,无法与const char& operator[](size_t pos)构成函数重载!

2.6.begin、end


有一个问题:基于范围的for循环的方式,对所有容器都适用。那么到现在为止俺们模拟实现的string适用于基于范围的for循环吗?

俺们在test.cpp试试:

#include"string.h"
namespace HD
{void test4(){string S1("hello world");for (auto ch : S1){cout << ch;}cout << endl;}
}
int main()
{HD::test4();return 0;
}

发现行不通的,错误列表有一堆报错:

到现在为止模拟实现的string不适用基于范围的for循环的原因也很简单,因为范围for的底层很简单,容器遍历实际就是替换为迭代器。咱们模拟实现的string还没有模拟实现迭代器,也没有模拟实现迭代器相关接口,当然不适用了。

那么如何模拟实现迭代器呢?


 迭代器的实现很复杂,其实迭代器本质就是模拟指针的行为啊,不管迭代器是用什么方法实现的,都希望迭代器能像指针般使用达到访问容器的目的。

那么针对string的模拟实现来说,不用搞那么复杂,我们正好可以返璞归真,利用string类的底层是字符顺序表这个特点,俺们就可以用指针模拟实现迭代器,因为本身就可以用指针来访问顺序表啊!

正向迭代器的模拟实现如下,对应的迭代器属于对应容器的类域,所以俺将其声明放在string类中:

		typedef char* iterator;typedef const char* const_iterator;

 俺就不模拟实现反向迭代器了,反向迭代器就不是用指针可以模拟实现的了,复杂的很!


模拟实现了正向迭代器,俺们就可以模拟实现正向迭代器相关的接口了,以下接口的声明和定义都放到string类中:

		iterator begin(){return _str;}iterator end(){return _str + _size;}const_iterator begin()const{return _str;}const_iterator end()const{return _str + _size;}

这些接口的模拟实现好像不用解释什么,根据接口的作用很容易就能写出来啊。 

模拟实现了正向迭代器和正向迭代器相关的接口,俺们在test.cpp中测试一下:

#include"string.h"
namespace HD
{void test4(){string S1("hello world");for (auto ch : S1){cout << ch;}cout << endl;const string S2("God is a girl");string::const_iterator cit = S2.begin();while (cit != S2.end()){cout << *cit;cit++;}cout << endl;}
}
int main()
{HD::test4();return 0;
}

运行结果没问题的,基于范围的for循环也能正常使用了:

2.7.capacity

 这个接口这个接口返回字符串的容量。这个接口的模拟实现如下,俺将其声明和定义都放到string类中:

		size_t capacity()const{return _capacity;}

 这是一个对this指针指向对象的成员变量只进行读访问的函数,所以我们将其实现成const成员函数,且这样子普通对象和const对象都可调用这个函数(这点以下不再赘述)。

在test.cpp中测试一下:

#include"string.h"
namespace HD
{void test5(){string S1;string S2("hello world");cout << S1.capacity() << endl << S2.capacity() << endl;}
}
int main()
{HD::test5();return 0;
}

运行结果:

2.8.reserve 

 本接口功能是为string预留空间,不改变有效元素个数。俺模拟实现这个接口不搞那么复杂,当扩容才使其生效。模拟实现本成员函数采用声明和定义分离的写法:

string类(再次说明string的定义放在了string.h中)中声明:

void reserve(size_t n = 0);

 string.cpp中定义:

#include"string.h"
namespace HD
{void string::reserve(size_t n){if (n > _capacity){char* p = new char[n + 1];strcpy(p, _str);delete[] _str;_str = p;_capacity = n;}}
}

1.缺省参数不能在函数声明和定义中同时出现,当函数声明和定义分离时在声明给缺省参数 。 

2.别忘记使用命名空间HD封装起来。

3.该函数声明和定义分离,所以定义成员函数时,成员函数名前需要加string::。

4.该接口模拟实现的思想:当需要扩容时,动态开辟n+1(多开一个空间存储'\0')个字节新空间,在将原来的数据拷贝到新空间,释放旧空间,再让_str指向新空间,更新_capacity即可。

在test.cpp中试试:

#include"string.h"
namespace HD
{void test6(){string S1;string S2("hello world");cout << S1.capacity() << "   " << S2.capacity() << endl;S1.reserve(10); S2.reserve(1);cout << S1.capacity() << "   " << S2.capacity() << endl;}
}
int main()
{HD::test6();return 0;
}

运行结果符合预期:

2.9.push_back 

本接口需要实现将字符c附加到字符串末尾,使其长度增加1。俺的模拟实现采取本成员函数声明和定义分离的写法:

string类(再次说明string的定义放在了string.h中)中声明:

void push_back(char c);

string.cpp中定义:

#include"string.h"
namespace HD
{void string::push_back(char c){if (_capacity == _size)//扩容{reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size++] = c;_str[_size] = '\0';}
}

1.别忘记使用命名空间HD封装起来(这点以下不再赘述)。

2.该函数声明和定义分离,所以定义成员函数时,成员函数名前需要加string::(这点以下不再赘述)。

3.插入字符之前先检查字符串容量是否充足,若是不充足,就调用reserve接口扩容(当字符串旧容量为0时,给4个字节空间;当字符串旧容量不为0时,2倍扩容)。将字符c插入字符串末尾并使字符串长度加一,再在字符串末尾插入'\0'就好了。

在test.cpp中试试:

#include"string.h"
namespace HD
{void test7(){string S1;string S2("hello world");S1.push_back('!');S2.push_back('!');cout << S1.capacity() << "    " << S2.capacity() << endl;cout << S1.c_str()<<"    " << S2.c_str() << endl;}
}
int main()
{HD::test7();return 0;
}

运行结果:

2.10.append 

 这个接口string中重载了很多个函数,俺就模拟实现string& append (const char* s)这个函数。这个函数功能就是在字符串后追加字符串s。

 俺的模拟实现采取本成员函数声明和定义分离的写法:

string类中声明:

string& append(const char* s);

 string.cpp中定义:

#include"string.h"
namespace HD
{string& string::append(const char* s){if (_capacity < strlen(s) + _size)//扩容{size_t n = strlen(s);reserve(n + _size > 2 * _capacity ? n + _size : 2 * _capacity);}/写法一///for (int i = 0; i < strlen(s); ++i)//{//	_str[_size++] = s[i];//}//_str[_size] = '\0';/写法二/strcpy(_str + _size, s);_size += strlen(s);return *this;}
}

插入字符串s之前先检查字符串容量是否充足,若是不充足,就使用reverse接口扩容(这里的扩容个数不能使用push_back接口的逻辑了,因为有可能4个字节空间或者字符串旧容量的2倍仍然不够字符串s插入的需要)。再将字符串s插入到字符串末尾并且使得字符串长度加strlen(s)并且返回*this就好了。这里插入字符串s到字符串末尾的写法有很多,俺写了两个,但是不管是那种写法都要保证插入之后字符串末尾有'\0'。

在test.cpp中试试:

#include"string.h"
namespace HD
{void test8(){string S1;string S2("hello");S1.append("young man");S2.append(" Totoro!");cout << S1.capacity() << "            " << S2.capacity() << endl;cout << S1.c_str()<<"    " << S2.c_str() << endl;}
}
int main()
{HD::test8();return 0;
}

运行结果:

2.11.operator+=

这个接口string中重载了3个函数,功能就是通过在字符串的当前值末尾附加其他字符来扩展字符串。俺就模拟实现其中2个函数:string& operator+= (const char* s)和string& operator+= (char c)。其实模拟实现这2个函数分别直接调用俺模拟实现的append和push_back,然后返回*this就好了。

俺的模拟实现采取本成员函数声明和定义分离的写法:

string类中声明:

		string& operator+= (const char* s);string& operator+=(char c);

string.cpp中定义:

#include"string.h"
namespace HD
{string& string::operator+= (const char* s){append(s);return *this;}string& string::operator+=(char c){push_back(c);return *this;}
}

在test.cpp中试试:

#include"string.h"
namespace HD
{void test9(){string S1;string S2("hello");S1 += '!'; S2 += " Totoro";cout << S1.c_str()<<"    " << S2.c_str() << endl;}
}
int main()
{HD::test9();return 0;
}

运行结果:

2.12.insert 

 这个接口string中重载了7个函数,都是在字符串中pos(或p)指示的字符之前插入其他字符。俺就模拟实现其中两个:string& insert(size_t pos, size_t n, char c)和string& insert(size_t pos, const char* s)。

俺的模拟实现采取本成员函数声明和定义分离的写法:

string类中声明:

		string& insert(size_t pos, size_t n, char c);string& insert(size_t pos, const char* s);

string.cpp中定义:

#include"string.h"
namespace HD
{string& string::insert(size_t pos, size_t n, char c){assert(pos <= _size);if (_capacity < n + _size)//扩容{reserve(n + _size > 2 * _capacity ? n + _size : 2 * _capacity);}size_t end = _size + n;while (end > pos + n - 1){_str[end] = _str[end - n];end--;}for (size_t i = 0; i < n; i++){_str[pos + i] = c;}_size += n;return *this;}string& string::insert(size_t pos, const char* s){assert(pos <= _size);size_t n = strlen(s);if (_capacity < n + _size){reserve(n + _size > 2 * _capacity ? n + _size : 2 * _capacity);}size_t end = _size + n;while (end > pos + n - 1){_str[end] = _str[end - n];end--;}strncpy(_str + pos, s, n);_size += n;return *this;}
}

这2个函数的模拟实现逻辑都不难。无非就是处理好字符串容量使之足够插入的需要。然后把从字符位置为pos的字符到字符'\0'在内的所有字符往后挪动指定个数个字节位置(指定个数就是所插入字符的个数或者字符串长度),再插入指定个数的字符或者字符串,最后返回*this就好了。

在test.cpp中试试:

#include"string.h"
namespace HD
{void test10(){string S1("A  man");string S2("hello");S1.insert(2, "young");S2.insert(S2.size(), 5, 'A');cout << S1.c_str()<<"    " << S2.c_str() << endl;}
}
int main()
{HD::test10();return 0;
}

运行结果:

2.13.erase 

 string在这个接口重载了3个函数。作用都是擦除字符串的一部分,缩短其长度。这里俺只模拟实现1个函数:string& erase(size_t pos = 0, size_t len = npos)。

俺的模拟实现采取本成员函数声明和定义分离的写法:

string类中声明:

string& erase(size_t pos = 0, size_t len = npos);

 string.cpp中定义:

#include"string.h"
namespace HD
{string& string::erase(size_t pos, size_t len){assert(pos < _size);if (_size - pos <= len){_str[pos] = '\0';_size -= (_size - pos);}size_t begin = pos + len;while (begin < _size + 1){_str[begin - len] = _str[begin];begin++;}_size -= len;return *this;}
}

1.这里用到了缺省参数npos,所以我们必须处理npos:

npos在stringstring类的公共静态成员变量,那么俺模拟实现也在string类中声明为公共静态成员变量:

static const size_t npos;

根据static成员的知识:静态成员变量必须在类外定义,定义时不添加static关键字。俺在string.cpp中定义:

#include"string.h"
namespace HD
{const size_t string::npos = -1;
}

 2.缺省参数不能在函数声明和定义中同时出现,当函数声明和定义分离时在声明给缺省参数(以下不再赘述)。

 3.这个接口的模拟实现思路:若是len>=_size-pos,说明要擦除从pos位置开始的全部字符,那么处理好'\0',并使得字符串长度减去擦除字符个数就好了;否则,将需要擦除的字符(串)后面剩余的字符(串)往前挪动len个字节位置,并且字符串长度减去len即可。最后返回*this。

在test.cpp中试试:

#include"string.h"
namespace HD
{void test11(){string S1("A  man");string S2("hello fat Totoro");S1.erase();S2.erase(5, 4);cout << S1.c_str() << endl << S2.c_str() << endl;}
}
int main()
{HD::test11();return 0;
}

运行结果:

2.14.find

 string中这个接口重载了四个函数。功能就是在字符串中搜索其参数指定的序列的第一个匹配项,找到了返回第一个匹配项的字符位置,找不到返回string::npos。俺就模拟实现其中2个函数:size_t find(char c, size_t pos = 0) const和size_t find(const char* s, size_t pos = 0) const。

俺的模拟实现采取本成员函数声明和定义分离的写法:

string类中声明:

	size_t find(char c, size_t pos = 0) const;size_t find(const char* s, size_t pos = 0) const;

string.cpp中定义:

#include"string.h"
namespace HD
{size_t string::find(char c, size_t pos) const{for (size_t i = pos; i < _size; i++){if (_str[i] == c){return i;}}return npos;}size_t string::find(const char* s, size_t pos) const{if (const char* p = strstr(_str + pos, s)){return p - _str;}return npos;}
}

这个模拟实现逻辑很简单,不解释了。其中size_t find(const char* s, size_t pos = 0) const这个函数俺直接套用了函数strstr,对于这个函数有兴趣可以看【C语言】字符函数和字符串函数的介绍。

在test.cpp中试试:

#include"string.h"
namespace HD
{void test12(){string S1("A  man");string S2("hello fat Totoro");cout << S1.find('m') << endl;cout << S2.find("fat") << endl;}
}
int main()
{HD::test12();return 0;
}

运行结果:

 2.15.substr

 功能:在str中从pos位置开始,截取len个字符,将这些字符构造一个新的字符串对象返回。

俺的模拟实现采取本成员函数声明和定义分离的写法:

string类中声明:

string substr(size_t pos = 0, size_t len = npos) const;

string.cpp中定义:

#include"string.h"
namespace HD
{string string::substr(size_t pos, size_t len) const{assert(pos <= _size);if (len >= _size - pos){len = _size - pos;}string S1;S1.reserve(len);for (int i = 0; i < len; i++){S1 += _str[pos + i];}return S1;}
}

这个模拟实现思路:主要就是构造一个局部空字符串S1,将需要截取的字符一个个摘取到S1当中,返回S1就好了。

俺们在test.cpp中试试:

#include"string.h"
namespace HD
{void test13(){string S1("hello fat Totoro");string S2 = S1.substr(6, 3);cout << S2.c_str() << endl;}
}
int main()
{HD::test13();return 0;
}

运行结果:

其实模拟实现到此为止,这个代码不是在任何环境都能运行成功的!

因为substr接口是传值返回,理论上是将substr的返回值S1拷贝到一个临时对象当中,局部对象S1便销毁了,再用临时对象拷贝构造S2的。C++规定⾃定义类型对象进⾏拷⻉⾏为必须调⽤拷⻉构造,但是俺还没有模拟实现拷贝构造呢,若未显式定义,编译器会生成默认的拷贝构造函数,默认的拷贝构造函数完成的是浅拷贝噢! 

结论就是若没有显示实现拷贝构造函数,substr的调用理论上是会出问题的,因为浅拷贝导致S2底层_str指向一块已经销毁的空间(substr的局部对象底层的_str)。

那么在俺的环境(VS2022)中为什么能运行成功呢?

因为俺这个VS2022优化了,但是不是任何编译器都会进行优化,所以为了保证substr的正常调用,必须显示实现拷贝构造函数。

2.16.constructor

 模拟实现其中的拷贝构造函数。这个接口的模拟实现如下,俺将其声明和定义都放到string类中:

		string(const string& str)//拷贝构造函数{_str = new char[str._capacity + 1];strcpy(_str, str._str);_size = str._size;_capacity = str._capacity;}

这个模拟实现思路很简单,不解释了。一定记住,类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝,会出问题。 

2.17.operator=

 string中重载了3个函数。俺就模拟实现了1个函数:string& operator=(const string& str)。

注意模拟实现的时候需要完成深拷贝就好,思路跟拷贝构造函数大同小异了。若是实现浅拷贝,问题就很大,俺画图方便理解:

俺的模拟实现采取本成员函数声明和定义分离的写法:

string类中声明:

string& operator=(const string& str);

string.cpp中定义:

#include"string.h"
namespace HD
{string& string::operator=(const string& str){if (this==&str){return *this;}delete[]_str;_str = new char[str._capacity + 1];strcpy(_str, str._str);_size = str.size();_capacity = str.capacity();return *this;}
}

这种写法要注意的是,防止自己给自己赋值噢,若是自己给自己赋值没有检查的话,一上来就把_str给delete掉了,那就没得玩了!

 2.18.clear

 这个接口擦除字符串的内容,该字符串将变为空字符串(长度(size接口的返回值)为0个字符),一般不会清理字符串容量。

俺的模拟实现采取本成员函数声明和定义分离的写法:

string类中声明:

	void clear();

string.cpp中定义:

#include"string.h"
namespace HD
{void string::clear(){_str[0] = '\0';_size = 0;}
}

这个接口的模拟实现在test.cpp中试试:

#include"string.h"
namespace HD
{void test14(){string S1("hello fat Totoro");cout << S1.capacity() << endl;S1.clear();cout << S1.c_str() << "    " << S1.capacity() << endl;}
}
int main()
{HD::test14();return 0;
}

运行结果:

 2.19.swap

 这个接口的功能就是与str交换内容,就是两个字符串底层的内容。

俺的模拟实现采取本成员函数声明和定义分离的写法:

string类中声明:

void swap(string& str);

string.cpp中定义:

#include"string.h"
namespace HD
{void string::swap(string& str){std::swap(str._str, _str);std::swap(str._size, _size);std::swap(str._capacity, _capacity);}
}

对于这个模拟实现,俺直接调用C++算法库的swap ,调用3次swap分别交换_str、_size、_capacity。

俺们在test.cpp中试试:

#include"string.h"
namespace HD
{void test15(){string S1("hello world");string S2("I do love you,yes I do love you");cout << "交换前S1:" << S1 << "                        " << S1.size() << "    " << S1.capacity() << endl;cout << "交换前S2:" << S2 << "    " << S2.size() << "    " << S2.capacity() << endl;cout << endl;S1.swap(S2);cout << "交换后S1:" << S1 << "    " << S1.size() << "    " << S1.capacity() << endl;cout << "交换后S2:" << S2 << "                        " << S2.size() << "    " << S2.capacity() << endl;}
}
int main()
{HD::test15();return 0;
}

运行结果:


 C++算法库的swap是一个函数模板,任何两个同类型的变量都可以直接调用以达到交换的目的,俺们可以看看截图:

但是这个函数模板对于交换两个申请了资源的自定义类型变量就不是很友好,虽然可以完成交换,但是消耗比较大,为什么消耗大?

当2个申请了资源的自定义类型类型变量要交换而调用这个函数模板时,这个模板就生成对应自定义类型的函数,我们看这个生成的函数体内:

……

T c(a):用第1个形参a来拷贝构造1个同类型临时对象c,由于本自定义类型申请了资源,那么调用拷贝构造时就完成1次深拷贝!

……

a=b:将第2个形参b赋值给a,调用operator=,涉及资源申请的自定义类型来说,operator=需要实现深拷贝,完成第2次深拷贝!

……

b=c:将临时对象c赋值给b,调用operator=,完成第3次深拷贝! 

交换两个申请了资源的自定义类型变量需要完成3次深拷贝,消耗当然大了!

这就是为什么string中为什么成员函数接口实现了1个swap了,非成员函数接口还要实现1个swap的意义:

俺们看string中的成员函数接口swap的底层实现跟俺模拟实现的成员函数接口swap是大同小异的,都是把自定义类型拆成若干个内置类型分别交换的,这样子交换就不会涉及深拷贝,1次深拷贝也没有。

……

string中非成员函数接口swap的行为跟成员函数接口swap是一模一样的,也是把自定义类型拆成若干个内置类型分别交换,也不涉及深拷贝。

……

若是string中没有实现非成员函数接口swap,那么当2个string对象想要交换且没有调用成员函数接口swap时,可以调用swap完成交换,会走函数模板,就需要3次深拷贝。

……

若是string中实现了非成员函数接口swap,那么那么当2个string对象想要交换且没有调用成员函数接口swap时,当去调用swap完成交换时,就会走非成员函数接口swap,而不是走函数模板,因为对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例,这样子不是避免深拷贝了吗。。。

……

 所以,string中的非成员函数接口swap和成员函数接口swap都是避免两个string类对象交换时陷入深拷贝的。

 3.模拟实现非成员函数接口

3.1.relational operators

string类重载了一堆运算符重载用于比较两个C++字符串的关系或者比较一个C++字符串和一个C字符串的关系。 俺在这里只是模拟实现比较2个C++字符串的关系的函数如下:

俺的模拟实现采取函数声明和定义分离的写法:

在string.h中声明:

namespace HD
{bool operator>(const string& lhs, const string& rhs);bool operator==(const string& lhs, const string& rhs);bool operator<(const string& lhs, const string& rhs);bool operator>=(const string& lhs, const string& rhs);bool operator<=(const string& lhs, const string& rhs);bool operator!=(const string& lhs, const string& rhs);
}

别忘了用命名空间HD封装起来以区分命名空间std内实现的非成员函数(这点以下不再赘述)。

在string.cpp中定义:

#include"string.h"
namespace HD
{bool operator>(const string& lhs, const string& rhs){return strcmp(lhs.c_str(), rhs.c_str()) > 0;}bool operator==(const string& lhs, const string& rhs){return strcmp(lhs.c_str(), rhs.c_str()) == 0;}bool operator<(const string& lhs, const string& rhs){return !(lhs >= rhs);}bool operator>=(const string& lhs, const string& rhs){return lhs > rhs && lhs == rhs;}bool operator<=(const string& lhs, const string& rhs){return !(lhs > rhs);}bool operator!=(const string& lhs, const string& rhs){return !(lhs == rhs);}
}

 模拟实现直接调用strcmp比较2个C++字符串底层的_str就好了。实现了operator>和operator==,其他的函数实现可以直接复用这2个函数的逻辑。

在test.cpp中试试:

#include"string.h"
namespace HD
{void test16(){string S1("hello world");string S2("I do love you,yes I do love you");cout << (S1 == S1) << endl;cout << (S1 != S1) << endl;cout << (S1 > S2) << endl;cout << (S1 <= S2) << endl;cout << (S1 <= S1) << endl;cout << (S1 < S2) << endl;}
}
int main()
{HD::test16();return 0;
}

运行结果:


 俺虽然只是模拟实现了比较2个C++字符串的关系的函数,但是却也可以支持1个C++字符串与1个C字符串的比较。举例如下

#include"string.h"
namespace HD
{void test17(){const char* S1 = "hello world";string S2("I do love you,yes I do love you");cout << (S1 == S1) << endl;cout << (S1 != S1) << endl;cout << (S1 > S2) << endl;cout << (S1 <= S2) << endl;cout << (S1 <= S1) << endl;cout << (S1 < S2) << endl;}
}
int main()
{HD::test17();return 0;
}

运行结果:

为什么可以这样子呢?

因为构造函数不仅可以构造与初始化对象,对于单个参数构造函数,还具有类型转换的作用。具体介绍可以去看【C++】类和对象3.0 。


俺模拟实现了比较2个C++字符串的关系的函数,却不支持2个C字符串的比较噢,因为俺在【C++】类和对象2.0中介绍过重载操作符必须有一个类类型参数,不能通过运算符重载改变内置类型对象的含义。

例如:

#include"string.h"
namespace HD
{void test18(){const char* S1 = "hello world";const char* S2 = "I do love you,yes I do love you";cout << (S1 == S1) << endl;cout << (S1 != S1) << endl;cout << (S1 > S2) << endl;cout << (S1 <= S2) << endl;cout << (S1 <= S1) << endl;cout << (S1 < S2) << endl;}
}
int main()
{HD::test18();return 0;
}

要搞清楚,若是代码写成这样子,不会调用模拟实现的非成员函数接口relational operators的函数 ,也不是2个C字符串的比较,两个C字符串的比较要用函数strcmp。这里本质是比较2个地址呢。

运行结果:

3.2.operator<< 

 流插入运算符重载模拟实现还是很简单的,将底层_str的字符一个个输出就好了。

俺的模拟实现采取函数声明和定义分离的写法:

在string.h中声明:

namespace HD
{ostream& operator<<(ostream& os, const string& str);
}

在string.cpp中定义:

#include"string.h"
namespace HD
{ostream& operator<<(ostream& os, const string& str){for (size_t i = 0; i < str.size(); i++){os << str.c_str()[i];}return os;}
}

在test.cpp中试试:

#include"string.h"
namespace HD
{void test19(){string S1;string S2("hello world");const string S3("I love Totoro");cout << S1 << endl << S2 << endl << S3 << endl;}
}
int main()
{HD::test19();return 0;
}

运行结果:

3.3.operator>> 

 流提取运算符重载的实现好像也不难,俺们模拟实现采用声明和定义分离的写法:

在string.h中声明:

namespace HD
{istream& operator>>(istream& is, string& str);
}

在strng.cpp中定义:

#include"string.h"
namespace HD
{istream& operator>>(istream& is, string& str){str.clear();char ch;is >> ch;while (ch == ' ' || ch == '\n')//确保第一次拿到的ch不是空格或者换行{is >> ch;}while (ch != ' ' && ch != '\n'){str += ch;is >> ch;}return is;}
}

这样子写其实是有问题的,俺们可以先在test.cpp中试试:

#include"string.h"
namespace HD
{void test20(){string S1;cin >> S1;cout << S1;}
}
int main()
{HD::test20();return 0;
}

运行结果如下,会陷入死循环:

俺们看模拟实现函数体内的第2个while循环,导致运行结果陷入死循环的就是第2个while循环。原因就是上一篇博客介绍过的:使用C++的标准输入对象(键盘)cin和C语言的函数scanf获取字符串的时候获取不到空格(' ')或换行('\n'),它们均把空格(' ')和换行('\n')作为默认分隔符号,当它们在缓冲区提取到这两个默认分隔符号之一时,就停止在缓冲区拿数据了。也就是说第2个while循环的条件语句永远是真的,那不就陷入死循环了吗。


解决的办法很多,其中一种办法就是调用istream类的成员函数接口get来获取字符:

get这个接口就是单单取一个字符,没有声明分隔符的概念。

在string.cpp种定义:

#include"string.h"
namespace HD
{istream& operator>>(istream& is, string& str){str.clear();char ch;is.get(ch);while (ch == ' ' || ch == '\n')//确保第一次拿到的ch不是空格或者换行{is.get(ch);}while (ch != ' ' && ch != '\n'){str += ch;is.get(ch);}return is;}
}

 在test.cpp中试试:

#include"string.h"
namespace HD
{void test20(){string S1;cin >> S1;cout << S1;}
}
int main()
{HD::test20();return 0;
}

运行结果如下,没问题:

3.4.getline

 这个接口string中重载了2个函数。俺也模拟实现2个函数,实现方法跟operator>>大同小异,如下:

俺们模拟实现采用声明和定义分离的写法:

在string.h中声明:

namespace HD
{istream& getline(istream& is, string& str);istream& getline(istream& is, string& str, char delim);
}

在string.cpp中定义:

#include"string.h"
namespace HD
{istream& getline(istream& is, string& str){str.clear();char ch;is.get(ch);while (ch != '\n'){str += ch;is.get(ch);}return is;}istream& getline(istream& is, string& str, char delim){str.clear();char ch;is.get(ch);while (ch != delim){str += ch;is.get(ch);}return is;}
}

在test.cpp中试试:

#include"string.h"
namespace HD
{void test21(){string S1;getline(cin, S1);cout << S1 << endl<<endl;string S2("hello world");getline(cin, S2, '@');cout << S2 << endl;}
}
int main()
{HD::test21();return 0;
}

运行结果:

4.个别接口的不同写法

 4.1.constructor的不同写法

 俺一共模拟实现了3个构造函数,分别是string()、string(const char* s)、string(const string& str)。


对于string()和string(const char* s)俺们可以用一个带缺省参数的构造函数代替: 

		string(const char* s = "")//以上两构造合二为一{_size = strlen(s);_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, s);}

1.string的构造函数接口内并没有重载带缺省参数的构造函数,俺实现这个函数可以用来替代以上两个函数罢了

2.当调用这个函数时,若我们给了s,那么就相当于调用string(const char* s);若我们没有给s,那么就相当于调用string()。

3.这里缺省值给空字符串(""),字符串默认结尾有'\0'的。若是缺省值给了"\0",其实这个字符串包含2个'\0',概念要分清捏。 


对于string(const string& str)俺们还可以这样写

		string(const string& str)//拷贝构造函数{string tmp(str.c_str());swap(tmp);}

 俺们希望用C++字符串str来构造一个新对象。俺们可以调用构造函数,用C字符串str.c_tr()来构造一个新对象tmp,这个新对象tmp不就是我们想要的吗,调用swap将tmp和*this的内容交换一下不就好了。

俺们知道string类底层是字符顺序表,所以俺定义3个private成员变量_str、_capacity、_size来实现,并且给这3个成员变量缺省值,这3个缺省值的意义就可以在这里体现:

当调用swap将tmp和*this的内容交换后,tmp得到的就是*this的旧内容且出了函数作用域就会销毁tmp。若是没有给缺省值,当调用构造函数走初始化列表的时,_str就得到一个随机值,也就是指向某块随机空间,当调用swap后并销毁tmp时,这块随机空间不就随着tmp的销毁而被delete掉了,这是不允许的;若是给缺省值,_str就得到nullptr,当tmp被销毁时是没问题的。

4.2.operator=的不同写法

 operator=还可以这样子写,跟构造函数的不同写法的思想是一样的:

	string& string::operator=(const string& str){string tmp(str);swap(tmp);return *this;}

operator=也可以如以下代码,只不过string中并没有重载以下operator=,但是下面这个operator=也能实现2个string类对象赋值的:

俺的模拟实现采取本成员函数声明和定义分离的写法:

string中声明:

string& operator=(const string str);

 在string.cpp中定义:

#include"string.h"
namespace HD
{string& string::operator=(string str){swap(str);return *this;}
}

思路:调用swap时要先传参,由于C++规定⾃定义类型对象进⾏拷⻉⾏为必须调⽤拷⻉构造,那么理论上,operator=的形参str拷贝构造一个临时对象,这个临时对象再拷贝构造swap的参数str,哪怕编译器优化掉了临时对象,swap的形参str也是通过ooperator=形参str拷贝构造的一个新对象,将这个新对象str与*this的内容交换一下不就搞定了。

 这个operator=的模拟实现为什么参数部分设计成传值而不是传引用呢?

若是传引用,那么调用swap时,就不存在拷贝行为,就不会调用拷贝构造函数,就没有新对象的产生。在将operator=的形参str与*this交换内容,不就出大问题了吗。

以上2种写法若是自己给自己赋值也不会出现问题,仔细想想就能明白!

4.3.operator>>的不同写法

	istream& operator>>(istream& is, string& str){str.clear();char ch;const size_t n = 256;char p[n];is.get(ch);size_t i = 0;while (ch == ' ' || ch == '\n'){is.get(ch);}while (ch != ' ' && ch != '\n'){p[i++] = ch;is.get(ch);if (i == n-1){p[i] = '\0';str += p;i = 0;}}if (i > 0){p[i] = '\0';str += p;}return is;}

这种写法与原本那种写法最大的不同就是,原本的写法是每取一个字符就插入str,容易造成频繁扩容;这种写法是每得到一串字符串再插入str,扩容次数就少了,一种优化吧!

5. string模拟实现完整代码

 关于这个string模拟实现完整代码如下,看完整代码也方便理解,有兴趣可以瞅瞅。本工程包括3个文件,分别是:string.h、string.cpp、test.cpp。

5.1.string.h

 存放string类的定义和非成员函数的声明。

#pragma once
#include<assert.h>
#include<iostream>
#include<string>
#include<string.h>
using namespace std;
namespace HD
{class string{char* _str = nullptr;size_t _capacity = 0;size_t _size = 0;public:typedef char* iterator;typedef const char* const_iterator;static const size_t npos;//string()//	:_str(new char[1]{'\0'})//	,_capacity(0)//	,_size(0)//{//}//string(const char* s)//{//	_size = strlen(s);//	_capacity = _size;//	_str = new char[_capacity+1];//	strcpy(_str, s);//}string(const char* s = "")//以上两构造合二为一{_size = strlen(s);_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, s);}~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}const  char* c_str()const{return _str;}size_t size()const{return _size;}char& operator[](size_t pos){assert(pos <= _size);return _str[pos];}const char& operator[](size_t pos)const{assert(pos <= _size);return _str[pos];}iterator begin(){return _str;}iterator end(){return _str + _size;}const_iterator begin()const{return _str;}const_iterator end()const{return _str + _size;}size_t capacity()const{return _capacity;}string(const string& str)//拷贝构造函数{/写法一//*_str = new char[str._capacity + 1];strcpy(_str, str._str);_size = str._size;_capacity = str._capacity;*//写法二/string tmp(str.c_str());swap(tmp);}void reserve(size_t n = 0);void push_back(char c);string& append(const char* s);string& operator+= (const char* s);string& operator+=(char c);string& insert(size_t pos, size_t n, char c);string& insert(size_t pos, const char* s);string& erase(size_t pos = 0, size_t len = npos);size_t find(char c, size_t pos = 0) const;size_t find(const char* s, size_t pos = 0) const;string substr(size_t pos = 0, size_t len = npos) const;string& operator=(const string& str);//string& operator=(const string str);void clear();void swap(string& str);};bool operator>(const string& lhs, const string& rhs);bool operator==(const string& lhs, const string& rhs);bool operator<(const string& lhs, const string& rhs);bool operator>=(const string& lhs, const string& rhs);bool operator<=(const string& lhs, const string& rhs);bool operator!=(const string& lhs, const string& rhs);ostream& operator<<(ostream& os, const string& str);istream& operator>>(istream& is, string& str);istream& getline(istream& is, string& str);istream& getline(istream& is, string& str, char delim);
}

5.2.string.cpp 

 存放string类部分成员函数的定义、静态成员变量的定义和非成员函数的定义。

#include"string.h"
namespace HD
{const size_t string::npos = -1;void string::reserve(size_t n){if (n > _capacity){char* p = new char[n + 1];strcpy(p, _str);delete[] _str;_str = p;_capacity = n;}}void string:: push_back(char c){if (_capacity == _size)//扩容{reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size++] = c;_str[_size] = '\0';}string& string::append(const char* s){if (_capacity < strlen(s) + _size)//扩容{size_t n = strlen(s);reserve(n + _size > 2 * _capacity ? n + _size : 2 * _capacity);}/写法一///for (int i = 0; i < strlen(s); ++i)//{//	_str[_size++] = s[i];//}//_str[_size] = '\0';/写法二/strcpy(_str + _size, s);_size += strlen(s);return *this;}string& string::operator+= (const char* s){append(s);return *this;}string& string::operator+=(char c){push_back(c);return *this;}string& string::insert(size_t pos, size_t n, char c){assert(pos <= _size);if (_capacity < n + _size)//扩容{reserve(n + _size > 2 * _capacity ? n + _size : 2 * _capacity);}size_t end = _size + n;while (end > pos+n-1){_str[end] = _str[end - n];end--;}for (size_t i = 0; i < n; i++){_str[pos + i] = c;}_size+=n;return *this;}string& string::insert(size_t pos, const char* s){assert(pos <= _size);size_t n = strlen(s);if (_capacity < n + _size){reserve(n + _size > 2 * _capacity ? n + _size : 2 * _capacity);}size_t end = _size + n;while (end > pos + n - 1){_str[end] = _str[end - n];end--;}strncpy(_str + pos, s, n);_size += n;return *this;}string& string::erase(size_t pos, size_t len){assert(pos < _size);if (_size - pos <= len){_str[pos] = '\0';_size -= (_size - pos);}size_t begin = pos + len;while (begin < _size + 1){_str[begin - len] = _str[begin];begin++;}_size -= len;return *this;}size_t string::find(char c, size_t pos ) const{for (size_t i = pos; i < _size; i++){if (_str[i] == c){return i;}}return npos;}size_t string::find(const char* s, size_t pos) const{if (const char* p = strstr(_str + pos, s)){return p - _str;}return npos;}string string::substr(size_t pos, size_t len) const{assert(pos <= _size);if (len >= _size - pos){len = _size - pos;}string S1;S1.reserve(len);for (int i = 0; i < len; i++){S1 += _str[pos + i];}return S1;}string& string::operator=(const string& str){/写法一//*if (this==&str)//防止自己给自己赋值{return *this;}delete[]_str;_str = new char[str._capacity + 1];strcpy(_str, str._str);_size = str.size();_capacity = str.capacity();return *this;*//写法二/string tmp(str);swap(tmp);return *this;}/*string& string::operator=(string str){swap(str);return *this;}*/void string::clear(){_str[0] = '\0';_size = 0;}void string::swap(string& str){std::swap(str._str, _str);std::swap(str._size, _size);std::swap(str._capacity, _capacity);}bool operator>(const string& lhs, const string& rhs){return strcmp(lhs.c_str(), rhs.c_str()) > 0;}bool operator==(const string& lhs, const string& rhs){return strcmp(lhs.c_str(), rhs.c_str()) == 0;}bool operator<(const string& lhs, const string& rhs){return !(lhs >= rhs);}bool operator>=(const string& lhs, const string& rhs){return lhs > rhs && lhs == rhs;}bool operator<=(const string& lhs, const string& rhs){return !(lhs > rhs);}bool operator!=(const string& lhs, const string& rhs){return !(lhs == rhs);}ostream& operator<<(ostream& os, const string& str){for (size_t i = 0; i < str.size(); i++){os << str.c_str()[i];}return os;}istream& operator>>(istream& is, string& str){/写法一//*str.clear();char ch;is.get(ch);while (ch == ' ' || ch == '\n'){is.get(ch);}while (ch != ' ' && ch != '\n'){str += ch;is.get(ch);}return is;*//写法二/str.clear();char ch;const size_t n = 256;char p[n];is.get(ch);size_t i = 0;while (ch == ' ' || ch == '\n'){is.get(ch);}while (ch != ' ' && ch != '\n'){p[i++] = ch;is.get(ch);if (i == n-1){p[i] = '\0';str += p;i = 0;}}if (i > 0){p[i] = '\0';str += p;}return is;}istream& getline(istream& is, string& str){str.clear();char ch;is.get(ch);while (ch != '\n'){str += ch;is.get(ch);}return is;}istream& getline(istream& is, string& str, char delim){str.clear();char ch;is.get(ch);while (ch != delim){str += ch;is.get(ch);}return is;}
}

5.3.test.cpp 

 存放测试代码,来测试咱们模拟实现的string的功能。

#include"string.h"
namespace HD
{void test1(){string S1;string S2("hello world");cout << S1.c_str() << endl << S2.c_str() << endl;}void test2(){string S1;string S2("hello world");cout << S1.size() << endl << S2.size() << endl;}void test3(){string S1("nbo");for (int i = 0; i < S1.size(); i++){S1[i] -= 1;cout << S1[i];}cout << endl;const string S2("hello world");for (int i = 0; i < S2.size(); i++){cout << S2[i];}}void test4(){string S1("hello world");for (auto ch : S1){cout << ch;}cout << endl;const string S2("God is a girl");string::const_iterator cit = S2.begin();while (cit != S2.end()){cout << *cit;cit++;}cout << endl;}void test5(){string S1;string S2("hello world");cout << S1.capacity() << endl << S2.capacity() << endl;}void test6(){string S1;string S2("hello world");cout << S1.capacity() << "   " << S2.capacity() << endl;S1.reserve(10); S2.reserve(1);cout << S1.capacity() << "   " << S2.capacity() << endl;}void test7(){string S1;string S2("hello world");S1.push_back('!');S2.push_back('!');cout << S1.capacity() << "    " << S2.capacity() << endl;cout << S1.c_str() << "    " << S2.c_str() << endl;}void test8(){string S1;string S2("hello");S1.append("young man");S2.append(" Totoro!");cout << S1.capacity() << "            " << S2.capacity() << endl;cout << S1.c_str() << "    " << S2.c_str() << endl;}void test9(){string S1;string S2("hello");S1 += '!'; S2 += " Totoro";cout << S1.c_str() << "    " << S2.c_str() << endl;}void test10(){string S1("A  man");string S2("hello");S1.insert(2, "young");S2.insert(S2.size(), 5, 'A');cout << S1.c_str() << "    " << S2.c_str() << endl;}void test11(){string S1("A  man");string S2("hello fat Totoro");S1.erase();S2.erase(5, 4);cout << S1.c_str() << endl << S2.c_str() << endl;}void test12(){string S1("A  man");string S2("hello fat Totoro");cout << S1.find('m') << endl;cout << S2.find("fat") << endl;}void test13(){string S1("hello fat Totoro");string S2 = S1.substr(6, 3);cout << S2.c_str() << endl;}void test14(){string S1("hello fat Totoro");cout << S1.capacity() << endl;S1.clear();cout << S1.c_str() << "    " << S1.capacity() << endl;}void test15(){string S1("hello world");string S2("I do love you,yes I do love you");cout << "交换前S1:" << S1 << "                        " << S1.size() << "    " << S1.capacity() << endl;cout << "交换前S2:" << S2 << "    " << S2.size() << "    " << S2.capacity() << endl;cout << endl;S1.swap(S2);cout << "交换后S1:" << S1 << "    " << S1.size() << "    " << S1.capacity() << endl;cout << "交换后S2:" << S2 << "                        " << S2.size() << "    " << S2.capacity() << endl;}void test16(){string S1("hello world");string S2("I do love you,yes I do love you");cout << (S1 == S1) << endl;cout << (S1 != S1) << endl;cout << (S1 > S2) << endl;cout << (S1 <= S2) << endl;cout << (S1 <= S1) << endl;cout << (S1 < S2) << endl;}void test17(){const char* S1 = "hello world";string S2("I do love you,yes I do love you");cout << (S1 == S1) << endl;cout << (S1 != S1) << endl;cout << (S1 > S2) << endl;cout << (S1 <= S2) << endl;cout << (S1 <= S1) << endl;cout << (S1 < S2) << endl;}void test18(){const char* S1 = "hello world";const char* S2 = "I do love you,yes I do love you";cout << (S1 == S1) << endl;cout << (S1 != S1) << endl;cout << (S1 > S2) << endl;cout << (S1 <= S2) << endl;cout << (S1 <= S1) << endl;cout << (S1 < S2) << endl;}void test19(){string S1;string S2("hello world");const string S3("I love Totoro");cout << S1 << endl << S2 << endl << S3 << endl;}void test20(){string S1;cin >> S1;cout << S1;}void test21(){string S1;getline(cin, S1);cout << S1 << endl << endl;string S2("hello world");getline(cin, S2, '@');cout << S2 << endl;}
}
int main()
{HD::test21();return 0;
}

感谢阅读! 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/13517.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

c_str()函数 string类型转换成char*类型 C++实现

问题&#xff1a;在 class 的构造函数中&#xff0c;如果我们在类中初始化了 char * 类型&#xff0c;在调用构造函数时&#xff0c;如果直接传入字符串( string )类型&#xff0c;编译器会提出如下警告&#xff1a; 想要消除这个警告&#xff0c;就需要将 string 类型的变量转…

【vue3文件上传同时出现两个提示框,一个提示成功,一个提示失败,一个是用写死的,一个是接口返回的】

文件上传同时出现两个提示框&#xff0c;一个提示成功&#xff0c;一个提示失败&#xff0c;一个是用写死的&#xff0c;一个是接口返回的 原因&#xff1a; 接口返回的是字符串code200" 把判断的code码改为字符串的就好了

选择哪种Facebook广告目标更有效

在Facebook广告投放中&#xff0c;广告目标的选择决定了投放效果和转化率&#xff0c;但很多人往往忽略了这一步的细节。今天&#xff0c;我们来一起看看Facebook广告目标有哪些&#xff0c;以及如何精准选择&#xff01; 1. 广告目标在投放中的重要性 广告目标不仅仅是一…

matlab实现主成分分析方法图像压缩和传输重建

原创 风一样的航哥 航哥小站 2024年11月12日 15:23 江苏 为了研究图像的渐进式传输技术&#xff0c;前文提到过小波变换&#xff0c;但是发现小波变换非常适合传输缩略图&#xff0c;实现渐进式传输每次传输的数据量不一样&#xff0c;这是因为每次变换之后低频成分大约是上一…

【缓存策略】你知道 Cache Aside(缓存旁路)这个缓存策略吗

&#x1f449;博主介绍&#xff1a; 博主从事应用安全和大数据领域&#xff0c;有8年研发经验&#xff0c;5年面试官经验&#xff0c;Java技术专家&#xff0c;WEB架构师&#xff0c;阿里云专家博主&#xff0c;华为云云享专家&#xff0c;51CTO 专家博主 ⛪️ 个人社区&#x…

稀疏视角CBCT重建的几何感知衰减学习|文献速递-基于深度学习的病灶分割与数据超分辨率

Title 题目 Geometry-Aware Attenuation Learning forSparse-View CBCT Reconstruction 稀疏视角CBCT重建的几何感知衰减学习 01 文献速递介绍 稀疏视角锥形束计算机断层扫描&#xff08;CBCT&#xff09;重建的几何感知学习方法 锥形束计算机断层扫描&#xff08;CBCT&a…

电子应用产品设计方案-3:插座式自动温控器设计

一、设计 插座式自动温控器作为一种便捷的温度控制设备&#xff0c;在日常生活和工业应用中发挥着重要作用。它能够根据环境温度的变化自动控制连接设备的电源通断&#xff0c;实现对温度的精确调节和节能控制。本设计旨在提供一种功能强大、易于使用、安全可靠的插座式自动温控…

机器学习—神经网络的Softmax输出

为了建立一个能进行多类分类的神经网络&#xff0c;将采用Softmax回归模型&#xff0c;把它放入神经网络的输出层&#xff0c;如何实现&#xff1f; 当我们用两门课做手写数字识别的时候&#xff0c;我们使用这种架构的神经网络&#xff0c;如果你现在想用十个类进行手写数字分…

web——sqliabs靶场——第五关——报错注入和布尔盲注

这一关开始上强度了&#xff0c;不回显东西了&#xff0c;又要学到新的东西了 发现它没有正确的回显&#xff0c;学到了新知识&#xff0c;报错注入 报错注入 什么是报错注入&#xff1a; MySQL提供了一个 updatexml() 函数&#xff0c;当第二个参数包含特殊符号时会报错&am…

【JavaScript】LeetCode:86-90

文章目录 86 只出现一次的数字87 颜色分类88 下一个排列89 寻找重复数90 前K个高频元素 86 只出现一次的数字 异或x ^ x 0&#xff0c;x ^ 0 x&#xff0c;相同为0&#xff0c;相异为1&#xff0c;且满足交换律。例如&#xff1a;[4, 1, 2, 1, 2] > 1 ^ 1 ^ 2 ^ 2 ^ 4 0 …

CSS回顾-基础知识详解

一、引言 在前端开发领域&#xff0c;CSS 曾是构建网页视觉效果的关键&#xff0c;与 HTML、JavaScript 一起打造精彩的网络世界。但随着组件库的大量涌现&#xff0c;我们亲手书写 CSS 样式的情况越来越少&#xff0c;CSS 基础知识也逐渐被我们遗忘。 现在&#xff0c;这种遗…

Spring Boot编程训练系统:构建可扩展的应用

摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了编程训练系统的开发全过程。通过分析编程训练系统管理的不足&#xff0c;创建了一个计算机管理编程训练系统的方案。文章介绍了编程训练系统的系统分析部分&…

点云论文阅读-1-pointnet++

pointnet局限性&#xff1a;不能获取局部结构信息 作者提出pointnet需要解决的问题&#xff1a; 如何生成点云的分区&#xff08;需要保证每一个分区具有相似的结构&#xff0c;使学习算法的参数在局部共享&#xff09;如何通过一个局部特征学习算法抽象点云或局部特征 解决…

Summaries 总结

Goto Data Grid 数据网格 Summaries 摘要 Summary Types 摘要类型 Total Summary 总摘要 汇总总数 &#xff08;GridSummaryItem&#xff09; 将针对所有数据网格记录进行计算&#xff0c;并显示在视图页脚中。启用 View 的 OptionsView.ShowFooter 设置以显示视图页脚。 …

MySQL技巧之跨服务器数据查询:基础篇-如何获取查询语句中的参数

MySQL技巧之跨服务器数据查询&#xff1a;基础篇-如何获取查询语句中的参数 上一篇已经描述&#xff1a;借用微软的SQL Server ODBC 即可实现MySQL跨服务器间的数据查询。 而且还介绍了如何获得一个在MS SQL Server 可以连接指定实例的MySQL数据库的连接名: MY_ODBC_MYSQL 以…

unity3d————协程练习题

1.计秒器&#xff1a; void Start(){StartCoroutine(MyCoroutine());}IEnumerator MyCoroutine(){int time 0;while(true){print(time "秒");time;yield return new WaitForSeconds(1);}} 结果&#xff1a; 2.生成多个cude &#xff08;不卡顿&#xff09;&#x…

Go开发指南- Gorouting

目录&#xff1a; (1)Go开发指南-Hello World (2)Go开发指南-Gin与Web开发 (3)Go开发指南-Gorouting Goroutine 在java中我们要实现并发编程的时候&#xff0c;通常要自己维护一个线程池&#xff0c;并且需要去包装任务、调度任务和维护上下文切换。这个过程需要消耗大量的精…

R语言机器学习与临床预测模型69--机器学习模型解释利器:SHAP

R小盐准备介绍R语言机器学习与预测模型的学习笔记&#xff0c; 快来收藏关注【科研私家菜】 01 机器学习的可解释性 对于集成学习方法&#xff0c;效果虽好&#xff0c;但一直无法解决可解释性的问题。我们知道一个xgboost或lightgbm模型&#xff0c;是由N棵树组成&#xff0c;…

Docker部署青龙面板,实现京东自动签到刷京东,提供脚本

项目简介 青龙面板是一个基于Docker的可视化任务管理系统&#xff0c;用于执行定时任务&#xff0c;如自动签到。 部署安装 安装Docker curl -sSL https://get.docker.com/ | sh 安装Docker-compose 下载 Docker-Compose 二进制包 curl -L https://github.com/docker/compo…

路径穿越浅析

当使用 RouterFunctions 来处理静态资源且资源处理通过 FileSystemResource 进行配置时&#xff0c;攻击者可以通过构造恶意 HTTP 请求&#xff0c;利用路径遍历漏洞获取相关受影响版本文件系统中的任意文件。 主要影响范围&#xff1a; Spring Framework 5.3.0 - 5.3.39 6.…