提醒:因本文章干货满满,没有什么废话,文字很多,大都在分析过程,仔细看一定会有收获!
C++最重要的模块之一就是string类,很多人在这一节点被劝退,在本篇文章中小编将逐个为你们突破,分模块将string中几个重要的接口实现,如果对string类有困难的读者们,强烈推荐仔细阅读本文章。
本章没有废话,全是干货,请读者们仔细阅读!!!!!!
💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜
1、string类的四个默认成员函数
一个类有6个默认成员函数,其中取地址重载相关的成员函数这里不做讲解,我们实现剩下的四个默认成员函数:
A:构造函数–主要完成初始化工作
B:拷贝构造函数–同类对象的初始化
C:赋值重载函数–把一个对象赋值给另一个对象
D:析构函数–完成清理工作
对于上面4个默认成员函数的概念、功能以及调用过程我们不在本章赘述,可以查看小编的其他文章。直接干代码!!!
1.1 构造函数
构造函数需要对3个成员变量进行初始化,_size代表有效的数据个数,_capacity代表可容纳的有效数据,对于一个5byte大小的内存空间,其size最大为4,如果将’\0’看作标识符,我们认为capacity也为4,所以在初始化的时候,我们将_size,_capacity设置为一样的值,这时始终要注意还有一个’\0’需要留空间。
此外,在形参中,我们采用缺省值的方式进行初始化:
a:如果初始化的时候没有给定初始值,我们就默认给’\0’,此时_size,_capacity均为0。
b:如果初始化的时候给定初始值,我们就按照初始值进行初始化,此时_size,_capacity的大小就是该字符串的有效长度,对于给_str数组初始化的时候,我们先创建一个和str同样大小的数组,这里需要注意的是要给’\0’预留一个字节的空间。然后调用stl中的strcpy函数,至此,构造函数的模拟已经实现。
1.2 拷贝构造
拷贝构造和赋值重载都涉及深浅拷贝的问题,关于深浅拷贝的问题,小编单独写一篇文章来帮助大家更好的理解,在这里我们需要知道的是,string类完成的是深拷贝,不仅仅是将值进行拷贝,还要在内存中创建一块空间,存储相同的内容。
拷贝构造完成的是同类型之间的拷贝初始化,关于拷贝构造的模拟实现,我们有两种方式:s2(s1)
方式一: 借用swap函数,首先我们将s2中的成员变量进行置0操作(关键!!),然后创建一个string类的临时对象tmp,并将s1的字符串对象传过去,此时string tmp(s._str);会去调用上面的构造函数,此时临时对象tmp中也存在3个成员变量,然后调用stl中的swap函数,将s2和s1的三个成员变量分别进行交换,这就保证s2的三个成员变量都是s1的了。重点来了,因为tmp是一个临时变量,出了作用域就要调用其析构函数,如果第一步不采用置空的操作,s2的随机值就会交换给s1,此时free、delete就会导致程序崩溃,但是free和delete是可以对空进行操作的,所以置空保证了程序的正常运行。至于s2为什么是随机值,因为s2没有调用构造函数,而且已经被定义出来了,系统会默认给一个随机值。
1.3 赋值重载
赋值重载也是一种拷贝行为,拷贝构造是创建一个对象时,拿同类对象初始化的拷贝,这里的赋值重载时两个对象已经都存在了,都被初始化过了,现在想把一个对象,赋值拷贝给另一个对象。关于赋值重载的模拟实现,我们有两种方式:s3 = s1
方式一: 赋值重载同样完成的是深拷贝,我们这里采用传引用返回,直接调用swap函数,将s3的三个成员变量与s1的三个成员变量进行交换,这里s采用的传值传参,这里会发生一次深拷贝,将s1的三个成员变量拷贝给s,然后s再和s3进行交换,如果采用传引用传参会导致s1发生变化。最后将s2返回即可。
!!!拷贝构造和赋值重载的模拟实现中大量重复使用了swap函数,这里我们可以写一个swap函数用来实现代码的复用。!!!
1.4 析构函数
析构函数的实现就比较简单了,就相当于置空操作,代码如下:
namespace XD
{
class string
{
public:
//1.1 构造函数
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
//未代码复用前:
/*
//1.2 拷贝构造
string(const string& s)
{
_str = nullptr;
_size = 0;
_capacity = 0;
string tmp(s._str);
::swap(_str, tmp._str);
::swap(_size, tmp._size);
::swap(_capacity, tmp._capacity);
}
//1.3 赋值重载
string& operator=(string s)
{
::swap(_str, s._str);
::swap(_size, s._size);
::swap(_capacity, s._capacity);
return *this;
}
*/
//代码复用后:
void swap(string& s)
{
//加一个域作用限定符,这样swap就会去全局域寻找,就会找到库函数中的swap
::swap(_str, s._str);
::swap(_size, s._size);
::swap(_capacity, s._capacity);
}
string(const string& s)
{
_str = nullptr;
_size = 0;
_capacity = 0;
string tmp(s._str);
swap(tmp);
}
string& operator=(string s)
{
swap(s);
return *this;
}
//1.4 析构函数
~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
//输出该对象
char* c_str()const
{
return _str;
}
private:
char* _str;
size_t _capacity;
size_t _size;
};
}
//四个默认构造函数测试
void Test1()
{
XD::string s1("Hello World!");
cout << s1.c_str() << endl;
XD::string s2(s1);
cout << s2.c_str() << endl;
XD::string s3;
s3 = s1;
cout << s3.c_str() << endl;
}
int main()
{
Test1();
return 0;
}
1.5 拷贝构造和赋值重载的方法二:
这两个方法本质一样,唯一不同就是赋值重载的时候要先删除原s3,因为s1的大小不知道,即s1和s3内存空间大小不确定,所以要先删除s3,然后创建一个与s1同样大小的内存空间。
//a:s2(s1)--拷贝构造
string(const string& s)
{
_size = strlen(s._str);
_capacity = _size;
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
}
//b:s3 = s1--赋值重载
string& operator=(const string& s)
{
if (this != &s)//防止自己给自己赋值s1 = s1
{
delete[] _str;
_size = strlen(s._str);
_capacity = _size;
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
}
return *this;
}
程序运行结果如图:
💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚
2、string类的增删改查
2.1 string的各种增操作
不止是string,只要是数据的增操作,就涉及内存是否够用的问题,因为插入数据可能会存在增容的操作,所以在这里我们同时将reserve函数模拟实现用来增容,除了reserve还有一个resize,二者的区别的联系不用小编多说,大家应该知道,这里resize没有什么实质性的作用,小编还是给出了其模拟实现的代和讲解,帮助大家理解。
2.1.1 模拟实现resize
string s1(“Linux”);
s1 += ‘!’;
resize()的情况分为多种:s1经过构造函数,_size=5,_capacity=5._str=“Linux”,然后+=’!'后,_size=6,_capcacity=10,_str=“Linux!”,即有10个有效空间大小的容量,存储有效数据6个,现在进行resize
a:resize(3) – resize的大小<_size
b:resize(8) – resize的大小>_size,但<_capacity
c:resize(15) – resize的大小>_capacity
//设置size
void resize(size_t n, char c = '\0')
{
if (n < _size)
{
_str[n] = '\0';
_size = n;
}
else
{
if (n > _capacity)
{
reserve(n);
}
for (size_t i = _size; i < n; i++)
{
_str[i] = c;
}
_str[n] = '\0';
_size = n;
}
}
2.1.2 模拟实现reserve
对于reserve,改变的是capacity,设置一个形参n,即当需要的capacity大于本身的_capacity时就需要扩容,在这里,我们创建一个临时数组tmp,其大小就为我们需要的空间n,切记预留一个’\0’的位置,然后利用strncpy函数。
这里需要注意为什么不用strcpy,stcpy是以’\0’为拷贝的结束标志,如果原字符串_str后面有多个’\0’,且只有最后一个’\0’是作为标识符的,其它都是有效字符,而strcpy只会讲第一个’\0’拷贝过来,所以我们这里用strncpy,直接讲有效字符的长度拷贝过来,避免出错。
然后置空原字符串_str,因为其空间不够了,然后改变_capacity参数即可。
//设置capacity
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strncpy(tmp, _str, _size + 1);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
2.1.3 模拟实现push_back
不管在哪进行增加操作,刚上来就要进行判断是否进行增容,如果_size=_capacity,则表明原数组满了,需要增容,添加字符可以每次扩容原容量的2倍,但是需要注意的是,原容量可能为0,因为我们的默认构造函数的形参给的’\0’,所以这里需要主要,我们使用一个三目运算符来进行避免这个问题。
不管是否进行扩容,代码只要执行到if语句下面,就说明此时空间够用,我们只需将字符
c添加到尾部,更新其_size即可,这里需要主要不要忘了将’\0’添加,因为原’\0’的位置被我们换成了字符c。
//push_back字符
void push_back(char c)
{
if (_size == _capacity)
{
//reserve(_capacity * 2);
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = c;
_str[_size + 1] = '\0';
_size++;
}
2.1.4 模拟实现append
对于append的扩容就不能采取原容量的2倍,因为我们不知道插入的字符串到底有多大,如果很大,一直2倍扩容多次,效率很低,所以我们直接计算所需要的容量,进行扩容,这是一个需要注意的地方。
然后将需要插入的字符串str,拷贝到原字符串数组_str的尾部,更新_size即可。
//append字符串
void append(const char* str)
{
size_t len = strlen(str);
if ((_size + len) > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, str);
_size += len;
}
2.1.5 +=重载
对于+=运算符的重载,完全是复用push_back和append代码,这里不多加赘述
//重载+=字符
string& operator+=(char c)
{
push_back(c);
return *this;
}
//重载+=字符串
string& operator+=(const char* str)
{
append(str);
return *this;
}
2.1.6 模拟实现insert
字符: 同样,对于添加字符,仍要判断是否扩容,方法同push_back添加字符,在挪动数据时有多种方式,我们这里采用指针的方式,定义一个尾指针指向数组的尾部,此时指向的是’\0’的位置,然后依次往后挪动数据,直到在pos位置停下来,因为插入数据是用下标的形式,所以就实现了在pos位置之前(pos下标)的位置插入数据。当数据挪动完后,将字符c插入pos下标位置,然后更新_size,并将其以引用的形式返回。
字符串: 字符串的insert和字符的类似,首先判断是否扩容,这个和上面的append类似。在保证容量够的情况下开始挪动数据,此时挪动数据和添加字符挪动数据不同的是,添加字符挪动数据是后移一位,而添加字符串挪动数据,是往后挪动strlen(str)个,即挪动添加字符串长度,这样挪动完数据后,从pos位置开始,就会空出len长度的字符,用来插入str,此时仍需要注意的是,这里使用的是strncpy原理在2.1.1中讲过,这里不进行赘述。最后更新_size的数据,将结果返回即可。
我们实现insert后,insert的代码可以让push_back和append进行复用,为了不让大家混淆,这里就不提供复用的代码,避免大家一头雾水,各位读者有没有感觉环环相扣的感觉,这也就是string的魅力之处。
//在pos位置前插入字符c
string& insert(size_t pos, char c)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
char* end = _str + _size;
while (end > (_str + pos))
{
*(end + 1) = *end;
--end;
}
_str[pos] = c;
_size++;
return *this;
}
// 在pos位置前插入字符串str
string& insert(size_t pos, const char* str)
{
size_t len = strlen(str);
if ((_size + len) > _capacity)
{
reserve(_size + len);
}
char* end = _str + _size;
while (end > (_str + pos))
{
*(end + len) = *end;
--end;
}
strncpy(_str + pos, str, len);
_size += len;
return *this;
}
运行结果如下:
真的是干货满满,不知道各位读者有没有仔细阅读,能将本文章读完,你对string类将会有更深入的理解。加油!!!!!
2.2 string类的删除数据的函数模拟实现
删除数据分两种情况
a:剩余的字符长度不够删,即要删除的长度大于等于左边剩余的字符(后面全部删完)
b:剩余的字符长度够删,即要删除的长度小于左边剩余的字符
对于a情况,直接将pos位置的数据换为’\0’即可,然后更新_size,对于b情况,直接利用strcpy进行拷贝,因为strcpy以’\0’作为拷贝结束的标志,所以把末尾的’\0’标识符一块拷贝过去,再更新_size即可。
对于b情况,即将len长度后面的数据拷贝到从pos位置开始的len长度即可,然后更新_size,这里我们在传参里面使用缺省参数将len=-1,对于无符号整数,-1是一个很大的整数,我们默认一个字符串的长度没有这么大,所以在没有指定长度的情况下,默认删除到最后。
// 删除pos位置开始len长度的元素
string& erase(size_t pos, size_t len = -1)
{
//leftnumber代表从pos位置开始到末尾,总共有多少数据
size_t leftnumber = _size - pos;
//a情况
if (len > leftnumber)
{
_str[pos] = '\0';
_size = pos;
}
//b情况
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
return *this;
}
运行结果如下
2.3 string类中[]运算符的重载-用于修改数据
我们知道对于内置类型的数组,我们可以使用下标的方式进行遍历,通过使用[]下标运算符,输出某一下标的数据,还可以通过下标对某一位置的数据进行修改,那么我们的string类可以使用这一功能吗?答案是可以。这时候我们就需要实现对[]运算符的重载。
这个重载比较简单,我们直接在函数里面访问_str数组,然后使用[]操作符访问这个数组中的元素即可,使用引用返回可以实现对数据的修改。
下面两个函数构成重载,这样就可以适应const和非const对象的使用。
//*******************这个接口给const对象,只读******************
char& operator[](size_t index)
{
return _str[index];
}
//*******************这个接口给非const对象,可读可写******************
const char& operator[](size_t index)const
{
return _str[index];
}
测试结果:
2.4 string类中查找字符或字符串函数模拟实现
对于string类中的查找操作也分为查找字符和字符串,二者本质上是一样的,只不过查找字符串的时候调用了stl库中的strstr函数帮我们查找,但本质也是遍历数组,找到符合的并返回,strstr如果找到则返回对应字符串的起始位置下标,如果没找到则返回NULL。
这两个接口比较简单,在这里不过多说明,大家看代码,如果不懂给小编留言,小编给大家解答。
// 返回c在string中第一次出现的位置
size_t find(char c, size_t pos = 0) const
{
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == c)
{
return i;
}
}
return -1;
}
// 返回子串s在string中第一次出现的位置
size_t find(const char* s, size_t pos = 0) const
{
const char* ret = strstr(_str + pos, s);
if (ret)
{
return ret - _str;
}
else
{
return -1;
}
}
运行结果如下:
💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛
3、string类重载各种比较关系运算符
关于各种运算符的重载,我们只需要实现<和==,其它的都复用即可。
这里我们比较两个运算符,使用的是stl中的strcmp函数。
//比较关系的运算符重载
bool operator<(const string& s)
{
return strcmp(_str,s._str) < 0;
}
bool operator==(const string& s)
{
return strcmp(_str, s._str) == 0;
}
bool operator<=(const string& s)
{
return ((_str < s._str) || (_str == s._str));
}
bool operator>(const string& s)
{
return !((_str < s._str) || (_str == s._str));
}
bool operator>=(const string& s)
{
return !(strcmp(_str, s._str) < 0);
}
bool operator!=(const string& s)
{
return !(strcmp(_str, s._str) == 0);
}
运算结果
💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙
4、string类的清空、计算大小、容量和判断空的接口实现
4.1 string类中的清空接口
直接将数组第一个位置的元素改为’\0’,然后更改_size的值。
//清空
void clear()
{
_str[0] = '\0';
_size = 0;
}
4.2 string类中的计算大小接口
直接返回_size
//计算大小
size_t size()const
{
return _size;
}
4.3 string类中的计算容量接口
直接返回_capacity
//计算容量
size_t capacity()const
{
return _capacity;
}
4.4 string类中的判断是否为空接口
判断_size是否为0,然后将结果返回
//判断对象是否为空
bool empty()const
{
return (_size == 0);
}
运行结果如图:
over!!!!坚持到这里的小伙伴相信一定会有所收获,我们已经将string类中常用的接口进行了模拟实现,文章末尾小编会给出本章的全部源代码,供大家学习,源代码中还会有迭代器的实现,范围for的解释,这几个知识点不作为本章内容的研究,有感兴趣的小伙伴可以私聊小编。
❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️
完整代码:
namespace XD
{
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
public:
//迭代器的本质是指针
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end()const
{
return _str + _size;
}
//string的四个默认成员函数
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
//法一:
//未代码复用前:
/*
string(const string& s)
{
_str = nullptr;
_size = 0;
_capacity = 0;
string tmp(s._str);
::swap(_str, tmp._str);
::swap(_size, tmp._size);
::swap(_capacity, tmp._capacity);
}
string& operator=(string s)
{
::swap(_str, s._str);
::swap(_size, s._size);
::swap(_capacity, s._capacity);
return *this;
}
*/
//代码复用后:
/*
void swap(string& s)
{
//加一个域作用限定符,这样swap就会去全局域寻找,就会找到库函数中的swap
::swap(_str, s._str);
::swap(_size, s._size);
::swap(_capacity, s._capacity);
}
string(const string& s)
{
_str = nullptr;
_size = 0;
_capacity = 0;
string tmp(s._str);
swap(tmp);
}
string& operator=(string s)
{
swap(s);
return *this;
}
*/
//法二:
//a:s2(s1)--拷贝构造
string(const string& s)
{
_size = strlen(s._str);
_capacity = _size;
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
}
//b:s3 = s1--赋值重载
string& operator=(const string& s)
{
if (this != &s)//防止自己给自己赋值s1 = s1
{
delete[] _str;
_size = strlen(s._str);
_capacity = _size;
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
}
return *this;
}
~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
//输出该对象
const char* c_str()const
{
return _str;
}
//设置capacity
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];//永远留一个位置给\0
strncpy(tmp, _str, _size + 1);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
//设置size
/*
string s1("Linux");
s1 += '!';
resize()的情况分为多种:s1经过构造函数,_size=5,_capacity=5._str="Linux",然后+='!'后,_size=6,_capcacity=10,_str="Linux!",即有10个有效空间大小的容量,存储有效数据6个,现在进行resize
a:resize(3) -- resize的大小<_size
b:resize(8) -- resize的大小>_size,但<_capacity
c:resize(15) -- resize的大小>_capacity
*/
void resize(size_t n, char c = '\0')
{
if (n < _size)
{
_str[n] = '\0';
_size = n;
}
else
{
if (n > _capacity)
{
reserve(n);
}
for (size_t i = _size; i < n; i++)
{
_str[i] = c;
}
_str[n] = '\0';
_size = n;
}
}
//push_back字符
void push_back(char c)
{
if (_size == _capacity)
{
//reserve(_capacity * 2);
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = c;
_str[_size + 1] = '\0';
_size++;
}
//append字符串
void append(const char* str)
{
size_t len = strlen(str);
if ((_size + len) > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, str);
_size += len;
}
//重载+=字符
string& operator+=(char c)
{
push_back(c);
return *this;
}
//重载+=字符串
string& operator+=(const char* str)
{
append(str);
return *this;
}
// 在pos位置前插入字符c
string& insert(size_t pos, char c)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
char* end = _str + _size;
while (end > _str + pos)
{
*(end + 1) = *end;
--end;
}
_str[pos] = c;
_size++;
return *this;
}
// 在pos位置前插入字符串str
string& insert(size_t pos, const char* str)
{
size_t len = strlen(str);
if ((_size + len) > _capacity)
{
reserve(_size + len);
}
char* end = _str + _size;
while (end > (_str + pos))
{
*(end + len) = *end;
--end;
}
strncpy(_str + pos, str, len);
_size += len;
return *this;
}
//清空
void clear()
{
_str[0] = '\0';
_size = 0;
}
//计算大小
size_t size()const
{
return _size;
}
//计算容量
size_t capacity()const
{
return _capacity;
}
//判断对象是否为空
bool empty()const
{
return (_size == 0);
}
//[]运算符的重载-可以用来遍历
char& operator[](size_t index)
{
return _str[index];
}
const char& operator[](size_t index)const
{
return _str[index];
}
//比较关系的运算符重载
bool operator<(const string& s)
{
return strcmp(_str,s._str) < 0;
}
bool operator==(const string& s)
{
return strcmp(_str, s._str) == 0;
}
bool operator<=(const string& s)
{
return ((_str < s._str) || (_str == s._str));
}
bool operator>(const string& s)
{
return !((_str < s._str) || (_str == s._str));
}
bool operator>=(const string& s)
{
return !(strcmp(_str, s._str) < 0);
}
bool operator!=(const string& s)
{
return !(strcmp(_str, s._str) == 0);
}
// 返回c在string中第一次出现的位置
size_t find(char c, size_t pos = 0) const
{
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == c)
{
return i;
}
}
return -1;
}
// 返回子串s在string中第一次出现的位置
size_t find(const char* s, size_t pos = 0) const
{
const char* ret = strstr(_str + pos, s);
if (ret)
{
return ret - _str;
}
else
{
return -1;
}
}
//删除pos位置开始len长度的元素
/*
删除数据分两种情况
a:剩余的字符长度不够删,即要删除的长度大于等于左边剩余的字符(后面全部删完)
b:剩余的字符长度够删,即要删除的长度小于左边剩余的字符
*/
string& erase(size_t pos, size_t len = -1)
{
//leftnumber代表从pos位置开始到末尾,总共有多少数据
size_t leftNum = _size - pos;
//a情况
if (len >= leftNum)
{
_str[pos] = '\0';
_size = pos;
}
//b情况
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
return *this;
}
private:
char* _str;
size_t _capacity;
size_t _size;
};
}
//输入输出运算符!!!!
ostream& operator<<(ostream& out, const string& s)
{
for (auto ch : s)
{
out << ch;
}
return out;
}
istream& operator>>(istream& in, string& s)
{
//要先把原来的东西清空,而不是加在后面,库里面有这个clear,我们上面自己实现
s.clear();
char ch;
//这个地方会有问题,忽略空格或者回车,在while循环中,最后一个是回车或者空格,应该结束,但是没有结束,而是忽略掉了回车或者空格,即根本没拿到换行符,而是等待下一次接收,所以不对
//因为默认回车或者空格为字符间的间隔符,in>>ch,直接会忽略
//in >> ch;
ch = in.get();
while (ch != ' ' && ch != '\n')
{
//每进行一次+=,'\0'会自动添加到尾部,直到遇到退出的条件
s += ch;
//in >> ch;
ch = in.get();
}
return in;
}
//有可能字符串本身就有空格,这时候我们需要获取一行的数据,即库中的getline
istream& getline(istream& in, string& s)
{
s.clear();
char ch;
ch = in.get();
while (ch != '\n')
{
s += ch;
ch = in.get();
}
return in;
}
//四个默认构造函数测试
void Test1()
{
XD::string s1("Hello World!");
cout << s1.c_str() << endl;
XD::string s2(s1);
cout << s2.c_str() << endl;
XD::string s3;
s3 = s1;
cout << s3.c_str() << endl;
}
//添加字符、字符串测试--尾部插入字符push_back、尾部插入字符串append、重载+=、任意位置插入字符字符串insert
void Test2()
{
XD::string s1("Hello World!");
cout << s1.c_str() << endl;
s1.push_back('x');
s1.push_back('x');
s1.push_back('x');
cout << s1.c_str() << endl;
s1.append("yyy");
cout << s1.c_str() << endl;
s1 += 'C';
s1 += 'P';
s1 += 'P';
cout << s1.c_str() << endl;
s1 += "hello everyone";
cout << s1.c_str() << endl;
s1.insert(0, 'q');
s1.insert(3, 'q');
s1.insert(8, 'q');
cout << s1.c_str() << endl;
s1.insert(0, "pppp");
s1.insert(10, "pppp");
cout << s1.c_str() << endl;
}
//重载比较运算符的测试
void Test3()
{
XD::string s1("Hello");
XD::string s2("Hello");
cout << (s1 > s2) << endl;
cout << (s1 == s2) << endl;
XD::string s3("Hell");
XD::string s4("Hello");
cout << (s3 > s4) << endl;
cout << (s3 < s4) << endl;
}
//重载[]运算符的测试
void Test4()
{
XD::string s1("Hello");
cout << s1[0] << endl;
s1[1] = 'H';
cout << s1.c_str() << endl;
}
//删除某一位置数据的测试
void Test5()
{
XD::string s1("Hello");
s1.erase(0,2);
cout << s1.c_str() << endl;
XD::string s2("Hello World!!!!!!!!!!");
s2.erase(5);
cout << s2.c_str() << endl;
XD::string s3("Hello World");
s3.erase(3,15);
cout << s3.c_str() << endl;
}
//查找字符或字符串的测试
void Test6()
{
XD::string s1("Hello World,My name is xd!");
cout << s1.find('o') << endl;
cout << s1.find("mm") << endl;
cout << s1.find("My") << endl;
cout << s1.find("My",20) << endl;
}
//清空、计算大小、容量和判断空的测试
void Test7()
{
XD::string s1("Hello World!!");
cout << s1.size() << endl;
cout << s1.capacity() << endl;
cout << s1.empty() << endl;
s1.clear();
cout << s1.empty() << endl;
}
//迭代器的测试
void Test8()
{
XD::string s1("Hello World!!");
XD::string::iterator it = s1.begin();
while (it != s1.end())
{
cout << *it << " ";
it++;
}
cout << endl;
//范围for是由迭代器支持的
//依次取s1里面的值,依次赋值给ch,自动判断结束,自动迭代++
//看起来很神奇,但是原理很简单,这个范围for会被编译器替换成迭代器形式
//也就是说范围for是由迭代器支持的
for (auto ch : s1)
{
cout << ch << " ";
}
cout << endl;
}
int main()
{
//Test1();
//Test2();
//Test3();
//Test4();
//Test5();
Test6();
//Test7();
//Test8();
return 0;
}