C++—string类

本期我们来学习C++的string,本期内容相当的多,且有一定难度,需要大家静下心来看

目录

1.标准库中的string

1.1string类的介绍

1.2 string类的常用接口

构造函数、析构函数、赋值、拷贝构造

npos

push_back

append

operator[ ]

size

迭代器

reverse

sort

反向迭代器

const迭代器

max_size

capacity

reserve

resize

shrink_to_fit

at

insert

erase

replace

c_str

find

substr

rfind

find_first_of和 find_last_of

运算符重载

getline

to_string

模拟实现string

基本框架

增删查改

引用计数和写时拷贝

全部代码


我们日常生活中有很多信息只能用字符串表示,比如名字,住址,个人信息等等,我们之前在C语言学过str的各种函数,比如strlen,strcpy等等,但是这些函数不能满足我们的需求,所以C++以类的角度写了string,不仅可以完成C的功能,还能完成增删查改,以及各种算法,下面我们来对string进行学习

1.标准库中的string

1.1string类的介绍

1. 字符串是表示字符序列的类
2. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作单字节字符字符串的设计特性。
3. string 类是使用 char( 即作为它的字符类型,使用它的默认 char_traits 和分配器类型 ( 关于模板的更多信息,请参阅basic_string)
4. string 类是 basic_string 模板类的一个实例,它使用 char 来实例化 basic_string 模板类,并用 char_traits和allocator 作为 basic_string 的默认参数 ( 根于更多的模板信息请参考 basic_string)
5. 注意,这个类独立于所使用的编码来处理字节 : 如果用来处理多字节或变长字符 ( UTF-8) 的序列,这个类的所有成员( 如长度或大小 ) 以及它的迭代器,将仍然按照字节 ( 而不是实际编码的字符 ) 来操作。
总结:
1. string 是表示字符串的字符串类
2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作 string 的常规操作。
3. string 在底层实际是: basic_string 模板类的别名, typedef basic_stringstring;
4. 不能操作多字节或者变长字符的序列。
使用 string 类时,必须包含 #include 头文件以及 using namespace std ;

C++—string类_第1张图片

 我们来用一下string

 C++—string类_第2张图片

1.2 string类的常用接口

 string的成员函数非常多,有一百多个

C++—string类_第3张图片

 我们来随便看一看,首先是构造函数

构造函数、析构函数、赋值、拷贝构造

C++—string类_第4张图片

 构造函数实现了7个

C++—string类_第5张图片

上面是析构函数 

C++—string类_第6张图片

还有赋值

这么多的函数我们只需掌握常用的,大概二十多个,其余的我们在需要是查一下即可

我们先来学习构造函数

C++—string类_第7张图片

这里就是无参构造和有参的构造

 C++—string类_第8张图片

还可以以一个数字,一个字符,来完成多个字符的初始化,这样我们可以省点力气

 C++—string类_第9张图片

还有拷贝构造(s4)

C++—string类_第10张图片

我们再简单了解一下这三个

 3的功能是从pos位置开始拷贝len长度

C++—string类_第11张图片

 比如这里我们就是从下标为6的位置开始拷贝5个字符

我们在C语言时比较字符串要用strcmp,不过现在就不需要了

C++—string类_第12张图片

string类实现了重载,这里比较s1和s2我们要加上括号,原因是运算符的优先级问题

我们继续看3,3里面有个缺省参数,len = npos

npos

npos是一个静态成员变量,值为-1,因为是size_t类型,所以是整形的最大值

C++—string类_第13张图片

这意味着我们如果不给第三个参数它会拷贝到结尾的位置

 如果len太长,超过字符串长度,或者len是npos,直接取到结尾

C++—string类_第14张图片

我们可以靠这个操作分割字符串,这里是人工数的,后续我们会加上别的操作,就不用我们去数了

因为npos是一个非常大的数,所以我们在取结尾时非常方便,如果没有npos,我们写起来就很麻烦了

.size()可以计算长度,再减去28就是剩余多少

 C++—string类_第15张图片

我们再看5的功能 ,是拷贝s字符的前n个进行初始化,第7个涉及迭代器,我们暂时不介绍

C++—string类_第16张图片

我们再看看赋值

C++—string类_第17张图片

可以支持多种写法,我们看到第三种方法使用的是单引号,这个还被吐槽过,没有必要 

push_back

C++—string类_第18张图片

 push_back可以在string后添加一个字符

比如我们在hello后添加一个空格,那如果还要添加一个world的话,我们也要一个一个写吗?答案是不用

append

C++—string类_第19张图片

我们可以使用append 

C++—string类_第20张图片

非常的方便,而且不像C语言那会,string这里会自动扩容

这还不是最牛的地方,我们看下面

C++—string类_第21张图片

我们可以直接使用+=,非常的舒服,体验直接上升一个档次 

这里有一个x,如果我们要把x转成string对象,该怎么办呢? 

    size_t x = 0;
    cin >> x;
	string xstr;
	while (x) {
		size_t val = x % 10;
		xstr += ('0'+val);
		x /= 10;
	}
	cout << xstr << endl;

我们来看结果

C++—string类_第22张图片

其实还是有点问题的,我们逆置一下即可(一会儿会讲一些方便的函数)

string是一个管理字符数组的类(其实就是字符数组的顺序表)

下面我们来看一个比较重要的重载 

operator[ ]

C++—string类_第23张图片

重载的是方括号,方括号本质是一种解引用,让数组可以访问它的数据

现在我们就可以访问它的每一个字符了,用下标加方括号

C++—string类_第24张图片

我们可以看到,两种方式访问的结果是一样的,只是第一种是直接打印,第二种是一个一个的打印,第二种相对更加自由

上面我们用到了size,我们来看看size

size

C++—string类_第25张图片 大家再想一想,我们知道字符串的结尾是\0,那我们上面可以访问到\0吗?

C++—string类_第26张图片

我们打印一下size,发生并没有统计\0,原因是\0不算有效字符,是特殊字符,是标识字符串结束的特殊字符,这里和C语言的strlen一样,都没有算\0

C++—string类_第27张图片

有人可能回想在size后+1是不是可以访问到\0? 我们看结果发现好像什么也没有变化

有些编译器下,\0是不会被显示的打印出来,其实这里已经访问到\0了,只是没有显示出来

C++—string类_第28张图片

我们打开监视也是看不到的 

C++—string类_第29张图片

但是在这里是可以看到的 ,我们要点开各种底层

C++—string类_第30张图片

这里就可以看到了,这块内容我们未来学习底层会进行讲解

 我们再回过头来看operator[ ],它实现了两个,一个是普通对象的,一个是const对象的

C++—string类_第31张图片

我们可以给每个字符都修改一下

我们可以像数组一样使用,增删查改,非常方便

C++—string类_第32张图片

这里看起来很像,其实底层是天差地别的 

a是数组名,代表首元素地址,s3[1]会变为*(s3+1)

而s1是自定义类型,会调用operator[ ](1);

 我们可以看到call是调用了operator[ ] 的,前面有一个很长的东西,这是string的类型,我们后续会讲这是为什么

 了解了上面的内容,我们下面开始学习迭代器

迭代器

我们现在可以把迭代器理解为像指针一样的东西

C++—string类_第33张图片

比如说我们有一个string对象,它大概就是这样的结构,在堆上开了空间,然后指针指向这个空间,然后把常量字符串拷贝过来

迭代器就是增加一种访问方式,我们先来看迭代器怎么使用

C++—string类_第34张图片

我们先记住迭代器怎么写,至于为什么这样写,我们后面会讲

 s1.begin会返回hello world的开头位置的指针(不一定是指针,我们先想象为指针)

end会返回\0的位置(左闭右开的区间),*it开始是h的位置,所以++it会让它往后走,下一次就是e,然后就是llo

迭代器是像指针一样的类型,有可能是指针,也有可能不是,我们后续会慢慢接触

任何容器,begin都是第一个位置的迭代器

那迭代器能否修改呢?答案是可以的

我们先解引用,再--,就修改了数据

C++—string类_第35张图片

我们的访问方式多种多样,但是我们之前学习过范围for,其实范围for才是我们的最爱 

C++—string类_第36张图片

我们再回忆一下,我们这里进行了++,但是发现输出结果没有改变,这是因为这里是依次取字符拷贝给ch,然后++,本体并没有变化,所以我们应该加引用

C++—string类_第37张图片

加上引用就变化了

其实,范围for的底层会替换为迭代器,也就是说根本就没有什么范围for,都是迭代器罢了

C++—string类_第38张图片 我们看底层,其实也是begin和end

 如果一个类不支持迭代器,就不支持范围for,比如栈就不支持

迭代器的另一个好处是通用,如何容器都支持迭代器,并且用法是类似的

C++—string类_第39张图片

比如我们后面要学的vector,我们发现,代码基本是一样的 

C++—string类_第40张图片

list也同理,也就是说,我们学一个迭代器,其他就基本都会了

 这里还有一个问题,很多人可能会以为下标加方括号是主流的访问方式,但是string可以用,vector可以用,那list呢?

答案是不能,因为只有连续的空间(数组结构)才能重载方括号,比如树也是不行的,但是他们都是可以用迭代器的

总结一下:iterator是像指针一样的类型,有可能是指针,有可能不是指针,iterator提供了一种统一的方式访问和修改容器的数据

下面我们来简单看看算法,这里我们讲算法的主要目的是为了演示迭代器

首先,算法是怎么作用的数据上的呢?数据在容器里,算法是不能直接访问到容器的,因为容器里的数据是私有的

我们先简单看看

reverse

C++—string类_第41张图片

 reverse是逆置的意思,是通用的,无论列表,数组都可以使用,传的参数是左闭右开的区间

C++—string类_第42张图片

这里的两句代码调用的也不是一个函数,因为我们看上面的reverse,是函数模板 

有了迭代器,我们直接使用范围for,非常的舒服 

sort

C++—string类_第43张图片

 还有sort,传的也是迭代器

C++—string类_第44张图片

 另外,这里的列表是不支持sort的,具体原因我们之后再谈

上面我们讲了迭代器的第二个作用,跟算法进行配合

所以算法就可以通过迭代器出处理容器中的数据

迭代器除了普通的迭代器,还有反向的迭代器,比如有时候我们需要倒着遍历

反向迭代器

C++—string类_第45张图片

反向迭代器是reverse_iterator,我们来使用看看

 C++—string类_第46张图片

 反向迭代器调用的自然也是rbegin和rend,我们发现确实反向了

C++—string类_第47张图片

rbegin是最后一个数据的位置,rend是第一个数据的前一个位置 

我们这里调用反向迭代器,非常的麻烦,前面写那么长,还要写string,reverse,所以我们可以用auto

C++—string类_第48张图片

 使用起来非常方便,我们想修改数据也是可以的

C++—string类_第49张图片

这里就进行了修改

仔细想想我们就可以发现,范围for是有局限性的,它是不可以倒着遍历的,只有反向迭代器才能倒着遍历

我们再看一些使用场景

C++—string类_第50张图片

我们这里传s1,其实是不愿意这样传的,因为这里会调用拷贝构造,这里是深拷贝,代价极大

所以我们要加const引用

C++—string类_第51张图片

 我们在里面写点代码

C++—string类_第52张图片

我们看,这里编译是不通过的

原因是const对象不能用普通迭代器,要使用const迭代器

const迭代器

C++—string类_第53张图片

 普通迭代器可以读可以写,const只能读不能写

C++—string类_第54张图片

这样就可以通过了 ,如果要倒着遍历,也是类似的

C++—string类_第55张图片

原因也是一样,需要使用const的迭代器 

 

C++—string类_第56张图片

这样就可以了,但是,这样写太长了,直接用auto就可以了,也就是我屏蔽掉的那一行auto

 总结一下,我们现在学习了四种迭代器

C++—string类_第57张图片

前两种是正向迭代器,后两种是反向迭代器,1和3可以读可写,2和4是只读(给const对象使用)

下面我们再看看容量相关的

C++—string类_第58张图片

 我们看到有size和length,发现他们的解释是一样的

C++—string类_第59张图片

运行结果也是相同,这其实是和C++的发展史有关

C++—string类_第60张图片

containers是容器的意思,但我们发现里面其实并没有string

严格来说,string不属于stl,是属于标准库的,stl是属于标准库的一部分,string比stl产生的早一点

上面的接口,string上面定义的是length,在stl出来后加了size,因为列表这些都可以叫length,但是树叫length是不合适的,所以就有了size

我们一般推荐使用size

max_size

C++—string类_第61张图片

我们来看max_size,意思是最大长度

C++—string类_第62张图片

 最大长度是21亿,但是我们换个编译器就可能有变化

比如vs的13下就是42亿,这个函数有点不靠谱 

stl是一个规范,有很多版本,底层实现大同小异,所以不同版本的底层都是有差异的

比如这里对于max_size就是不同的,max_size在实际中毫无意义,但因为向前兼容的问题,是不能删除的

capacity

C++—string类_第63张图片

我们再看capacity

C++—string类_第64张图片

 是15,我们再换一个编译器,换到Linux下

C++—string类_第65张图片

 

 结果是不一样的,我们再看扩容机制

C++—string类_第66张图片

 除了第一次是二倍,后续大概都是1.5倍,我们再看看Linux下的

C++—string类_第67张图片

 Linux是二倍扩容

vs下最开始的长度其实是16(还有一个\0),第一次扩容后其实是32,上面的没有算 \0

clear是清理数据的意思

C++—string类_第68张图片

大家先想一想clear可能会让size改变,那会不会让capacity改变呢?

 C++—string类_第69张图片

答案是不会,同样的,我们来看下Linux下

 C++—string类_第70张图片

 也是一样的 ,空间是很多的,一般情况下是不会轻易释放空间的,因为最后是有析构函数做保底的

下面我们做些练习题

C++—string类_第71张图片

这道题是大数相加,有些数太大,我们的int,甚至long long都保存不下,比如99个9,这时就有人想到把数据保存到字符串里,所以就有了这种题,我们下面来看代码

class Solution {
public:
    string addStrings(string num1, string num2) {
        int end1=num1.size()-1,end2=num2.size()-1;
        string strRet;
        int carry=0;
        while(end1>=0 || end2 >= 0){
            int val1 = end1 >= 0 ? num1[end1]-'0' : 0;
            int val2 = end2 >= 0 ? num2[end2]-'0' : 0;
            int ret = val1 + val2 + carry;
            carry = ret/10;
            ret = ret%10;
            strRet += ('0'+ret);
            --end1;
            --end2;
        }
        if(carry==1){
            strRet += '1';
        }
        reverse(strRet.begin(),strRet.end());
        return strRet;
    }
};

 我们设置end1和end2取两个字符串的尾部,val1和val2负责保存并转换为数字,strRet是我们最后返回的字符串,ret是我们临时保存val1+val2+carry的值,carry是进位的意思,比如9+9=18,这里carry就是1,然后让strRet保存8即可,因为是字符和整形转换,所以我们要注意加减字符0,最后还要判断一下,如果carry为1时,strRet最后还是要加1的, 因为我们是从最后一位开始保存的,所以最后需要逆置一下

我们回过头继续看capacity

我们知道,扩容是要付出代价的,那我们有什么办法可以减少代价呢?

reserve

C++—string类_第72张图片

所以就有了reserve,他的英语单词和reverse非常像,但reserve是保留的意思

我们看他的参数,有一个n,假设我们知道我们的string要插入100个字符,我们就可以

C++—string类_第73张图片

 就不需要我们后续开空间了,已经提取开好了,至于这里为什么是111,这是和vs的对齐机制有关的,另外,如果在Linux下的话,这里给的就是100,所以不同的平台走的是不同的,不管在哪一个平台,这里一定会大于等于100

我们仔细看reserve的文档,他说在一些情况是会缩容的,我们来验证一下

C++—string类_第74张图片

在某些情况下,他确实是会缩容的,比如我们先开100个空间,然后clear,再reverse,就缩容了,和clear也有点关系,这里是否用for循环填满空间不影响结果

如果有数据,就不会缩容,clear清空一下,就会缩容了

resize

C++—string类_第75张图片

我们再看resize,resize是扩容加填数据

 reserve只对capacity进行改变,是单纯的开空间,resize是开空间加填值初始化

C++—string类_第76张图片 默认填的是0

C++—string类_第77张图片

当然我们想自己设置也是可以的 

resize开空间时,如果n比我们的空间大,他会扩容,如果比空间小的话,他是会删数据的

C++—string类_第78张图片

我们看到size从100变成了20,相当于把后面的数据全删了 

另外,这里是不会缩容的,resize(0)也不会缩容,缩容是要付出代价的,所以一般情况都不会缩容

 如果我们就想要缩容,我们可以

shrink_to_fit

C++—string类_第79张图片

他会将capacity减少到size,可能比size大,因为有对齐等等的原因

C++—string类_第80张图片

operator[ ]可以让我们把string像数组一样使用,返回pos位置的字符,我们可以随意修改,但是const不能修改,这两个接口构成重载 

at

 at的更能和[ ]是一样的,但我们平时不喜欢用,at是早年没有运算符重载时提供的,我们了解一下即可

C++—string类_第81张图片

 at和[ ]的区别是越界的检查,at是抛异常,[ ]是断言

C++—string类_第82张图片

这里[ ]就是断言错误

C++—string类_第83张图片

at是异常,异常需要我们捕获 

C++—string类_第84张图片

C++—string类_第85张图片 append是字符串,push_back是一个字符,不过我们平时基本都使用+=

C++—string类_第86张图片

我们再看assign,是赋值的意思,我们来对比一下

C++—string类_第87张图片

实际中我们也基本不用assign,大家了解即可 

insert

C++—string类_第88张图片

 我们再看insert,他是在pos位置插入一个字符串,或者字符串的前n个

C++—string类_第89张图片

 方式多种多样

C++—string类_第90张图片

还可以是迭代器 ,如果我们也想用迭代器在中间位置插入,我们可以

C++—string类_第91张图片

 insert我们要谨慎对待,因为有数据挪动,不宜多用

erase

C++—string类_第92张图片

我们再看erase,是从pos位置删除n个字符

C++—string类_第93张图片

比如这里我们就把world删除了

C++—string类_第94张图片

如果我们只给其实位置,他就是一直删,直到结尾,如果我们连这个5也不写,就是全部删除 

 C++—string类_第95张图片

给迭代器也是可以的,这里就变成了头删

同样因为数据挪动的原因,不宜多用

replace

C++—string类_第96张图片

再看replace ,是替换的意思,上面的(1)是把pos位置开始的len个字符替换为string

我们简单看看

C++—string类_第97张图片

 大致就是这样一个样子,同样的,还是因为数据挪动问题,不宜多用

c_str

C++—string类_第98张图片

 c_str是返回底层的存储的字符串

C++—string类_第99张图片

 第一个s1是调用string的流插入,下面的是char*

c_str是为了和c语言的一些接口更好的配合

比如我们打开一个文件,我们是不能直接传filename的,但是有了c_str就可以这样写 

find

C++—string类_第100张图片

 find就是查找,字符串和字符都可以查找,这里的pos从pos位置开始进行搜索,会返回找到的第一个位置,没有找到返回-1

我们来用find做个练习,随便给定一个网址,将协议,域名,资源名分开

substr

C++—string类_第101张图片

这里我们结合substr, 这是截取子串,pos位置开始截取len个

int main() {
	string url = "https://legacy.cplusplus.com/reference/string/string/";
	//协议,域名,资源名
	size_t pos1 = url.find("://");
	string protocol;
	if (pos1 != string::npos) {
		protocol = url.substr(0, pos1);//当前位置的下标是前面的数据个数
	}
	cout << protocol << endl;

	string domain;
	size_t pos2 =url.find('/',pos1+3);//pos1+3是查找的其实位置,防止返回://的/,我们要的是com后面的/
	if (pos2 != string::npos) {
		domain = url.substr(pos1+3,pos2-(pos1+3));
	}
	cout << domain << endl;

	string uri;
	uri = url.substr(pos2 + 1);//只给起始位置,会一直截取到结尾
	cout << uri << endl;
}

此时我们随便换网站都是可以的,我们之前写的是写死的

rfind

C++—string类_第102张图片

除了find,有时候我们还需要rfind,比如查找最后一个单词的长度或者查找文件的后缀

rfind和find完全是类似的,只不过rfind是从后往前找

find_first_of和 find_last_of

C++—string类_第103张图片

C++—string类_第104张图片  再看find_first_of还有find_last_of,下面为了方便,简称firstof和lastof

firstof是找任意一个,我们来试一试

C++—string类_第105张图片

 这段代码就是把所有的aeiou替换为*

lastof和firstof的功能是一样的,只不过是从后往前找

还有find_first_not_of和 find_last_not_of,他们的是找不是这里面的,比如我们上面的aeiou,他查找的为不是aeiou的,和上面两个是反正来的,同样的,first是从前向后,last是从后向前

C++—string类_第106张图片

还有运算符重载,大家了解即可

运算符重载

C++—string类_第107张图片

+和+=,+是不改变自己 ,+=是改变自己

getline

C++—string类_第108张图片

然后就是getline 

C++—string类_第109张图片

我们借助这道题来讲解 

C++—string类_第110张图片

这里的代码非常简单,我们直接来看这个问题,这是什么原因?我们在vs下测试一下大家就明白了

C++—string类_第111张图片

原因是空格和空格之后的字符都没有被录入,scanf和cin都有一个特点,多个值之间遇到空格或者换行,认为这次读取结束,剩下都在缓冲区

C++—string类_第112张图片

我们读取两次就可以验证 

C++—string类_第113张图片

所以此时就需要getline ,我们看getline的参数,第一个是istream,第二个是字符串,第三个是结束符,我们可以自己控制在哪里结束,默认是换行符

大家还记得C语言中字符串和整形间的转换吗?还有字符串和浮点数之间的转换

整形是itoa和atoi函数,浮点数没有,要自己写,我们来看C++是怎么玩的

to_string

C++—string类_第114张图片

我们直接来试一试(C++11支持)

C++—string类_第115张图片

 非常强大,我们再看字符串转整形怎么实现

C++—string类_第116张图片

默认是按10进制转,这个我们是可以控制的

还有转浮点数的 

C++—string类_第117张图片

所以我们使用这些函数会非常方便

 C++—string类_第118张图片

最后我们再看string,string是模板,是typedef的,他的本源是basic_string

为什么要写成模板呢?

 因为还有wstring,u16和u32,要兼容这些版本,这些东西和编码有关,目前我们先了解一下即可

我们接触过的编码就有ASCII码,ASCII是美国的,所以不能表示汉字,于是我们就自己设计了gbk编码,还有一个就是国际上的unicode,叫做万国码,他要将全世界的文字收录,所以上面的这几个都是为了更好的表示其他国家的文字而设计的

在实际中,我们使用较多的是utf-8,他是一个变长的,可以兼容ASCII等等,大家感兴趣可以了解一下

模拟实现string

下面我们来模拟实现string

基本框架

C++—string类_第119张图片

我们先把基本框架写下

C++—string类_第120张图片  

我们先看构造函数,我们是不能这样初始化的,因为上面是const char*,下面是char*,涉及权限放大问题,另外,万一传入的是一个常量字符串,这里都不能修改了,所以string的构造函数不能用字符串去初始化,要去开空间,我们尽可能使用初始化列表初始化

C++—string类_第121张图片

 最后我们写成这样,有人可能会把初始化列表里的顺序换一下

C++—string类_第122张图片

这样看起来没有毛病,但是别忘了,初始化列表的初始的顺序不是按照我们写的顺序,而是声明的顺序,即按找private里属性的顺序来初始化,所以这里如果我们运行的话是会出现错误的

我们测试一下,为什么预期结果和我们想的不一样,没有字符?因为我们上面只开了空间,而没有把数据拷贝过去

 C++—string类_第123张图片

C++—string类_第124张图片

此时我们再测试就没问题了,所以大家一定要注意细节

我们的代码还有一点问题,strlen是一个o(N)的接口,是实时去算的,我们初始化用了三遍是非常费时间的 ,还有为了解决乱移动代码顺序问题,我们修改一下

C++—string类_第125张图片

 下面我们再实现一下析构函数和c_str,实现c_str后我们就不用再一直看监视窗口了,方便一点

C++—string类_第126张图片

这两个实现非常简单

 C++—string类_第127张图片

我们简单测试一下

我们再按照实际需求,假设我们需要一个无参的构造函数,所以我们要实现一下默认构造

C++—string类_第128张图片

先看这个,这样写是错误的,_str这里不能给nullptr,否则我们的c_str就悬空了,会崩掉

如果我们使用标准库的sting,定义一个无参的string,他的里面只有一个\0 

C++—string类_第129张图片

所以我们应该这样写,我们合并一下两个构造,无参的带参的使用全全省的即可

C++—string类_第130张图片

我们先看这两种错误的,这两种错误是新手们经常犯的,第一个错误的原因是\0是char,而str是char*,类型都不匹配 ,第二种如果给空的话,下面的strlen就直接崩了

C++—string类_第131张图片

其实我们什么都不用给就可以了 

 我们看到这里的空串其实是有\0的,这个\0是哪来的呢?其实是常量字符串末尾默认是有\0的

然后strcpy就把\0拷贝过去了

如果我们要遍历字符串,是需要知道size的

C++—string类_第132张图片

我们直接返回即可,这个函数和c_str我们最好在后边加上const ,原因是const可以被const对象调用,是权限的平移,他修饰的是this指针指向的对象,普通对象也可以调用,是权限的缩小

下面我们再实现一下operator[ ],这样我们就可以像数组一样去使用string

C++—string类_第133张图片

我们使用assert,返回pos位置的字符,并且这里可以返回引用,因为出了作用域_str还在,他是对象的成员,用引用返回我们可以读,也可以写

C++—string类_第134张图片

 而且我们要提供两个版本,这里我们要多看文档

 C++—string类_第135张图片

 比如只有const版本,因为只用读,不用写

C++—string类_第136张图片

这个就有两个版本,第一个是可以读可以写,第二个个只读,当是const对象时,期望不能修改,所以只读,构成函数重载 C++—string类_第137张图片

 我们简单测试一下,这里就可读可写

而当是const时就不能修改 ,编译器会自动寻找最匹配的

我们再来实现一下迭代器

我们知道迭代器有begin和end,begin是开始的位置,end是最后一个位置的下一个位置(\0不是有效字符)

C++—string类_第138张图片

就是这个样子

 C++—string类_第139张图片

我们简单实现一下

C++—string类_第140张图片

 我们还可以用范围for

C++—string类_第141张图片

 写了迭代器就支持范围for,在底层范围for就被替换成了迭代器

我们可以证明一下,我们把迭代器的end屏蔽掉

然后就会出现这样的错误 ,编译器的底层就是傻瓜式的替换而已,名字都需要是一样的,我们改一下名字都不行

那const对象调用迭代器呢?const对象只能读不能写

C++—string类_第142张图片

 const char*即可

C++—string类_第143张图片

C++—string类_第144张图片

我们简单测试一下,*cit+=1是不允许的 ,const迭代器是只读的

C++—string类_第145张图片

这一串写起来太长了,我们是可以 使用auto的

增删查改

下面我们来实现push_back和append

void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;
				_capacity = n;
			}
		}
		void push_back(char ch)
		{
			if (_size >= _capacity)
			{
				//2倍扩容
				reserve(_capacity == 0 ? 4 : _capacity * 2);//这里的判断是防止空串初始化容量为0
			}
			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		}
		void append(const char* str)
		{
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				//至少扩容到_size + len
				reserve(_size + len);
			}
			strcpy(_str + _size, str);
			_size += len;
		}

 我们添加时都是需要先判断是否需要扩容,所以我们写一个reserve,逻辑也很简单,下面我们测试一下

C++—string类_第146张图片

是没有问题的,我们在实际中其实最喜欢的是+=这些,所以我们来实现一下

        string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}
		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}

我们基于前面的代码实现即可,然后我们测试一下

C++—string类_第147张图片

也没有问题

我们再实现一下insert

        void insert(size_t pos, size_t n, char ch)
		{
			assert(pos <= _size);
			if (_size + n > _capacity)
			{
				//至少扩容到_size + n
				reserve(_size + n);
			}
			//挪动数据
			int end = _size;
			while (end >= (int)pos)
			{
				_str[end + n] = _str[end];
				--end;
			}
			for (size_t i = 0; i < n; i++)
			{
				_str[pos + i] = ch;
			}
			_size += n;
		}

 我们先看pos位置插入n个字符的版本,这里挪动数据end设置为int,下面pos强转int,原因是pos如果为0,size_t类型是不会为负数,也就是说while循环是死循环了,我们只把end写成int也不行,因为pos不强转为int的话,在比较时就会发生整形提升,把end转换为无符号的,这是一个解决方案

第二种方法是我们挪动的时候直接把end设置为结尾位置,然后挪动,这样就不会有0的问题,不过这样写出来不太好看,我们来看第三种

我们给定一个npos 

C++—string类_第148张图片

然后我们在挪动数据这里加一个条件即可

C++—string类_第149张图片

我们测试一下,没有问题 

        void insert(size_t pos, const char* str)
		{
			assert(pos <= _size);
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				//至少扩容到_size + len
				reserve(_size + len);
			}
			//挪动数据
			size_t end = _size;
			while (end >= pos && end != npos)
			{
				_str[end + len] = _str[end];
				--end;
			}
			for (size_t i = 0; i < len; i++)
			{
				_str[pos + i] = str[i];
			}
			_size += len;
		}

再来看插入字符串的版本,和上面的基本差不多,我们测试一下

C++—string类_第150张图片

没有问题 

        void erase(size_t pos, size_t len = npos)
		{
			assert(pos <= _size);
			if (len == npos || pos + len >= _size)//说明要删完
			{
				_str[pos] = '\0';
				_size = pos;
				_str[_size] = '\0';
			}
			else 
			{
				size_t end = pos + len;
				while (end <= _size)
				{
					_str[pos++] = _str[end++];
				}
				_size -= len;
			}
		}

我们再实现erase,也是非常简单

C++—string类_第151张图片

 测试一下也没有问题

增删查改我们还差一个查,我们来实现find

find查找字符串我们使用暴力匹配,为什么不用kmp呢?因为实际中kmp其实并不好,甚至strstr这个函数都是暴力匹配写的,一般不要求效率的话都使用暴力匹配,如果大家想换一种写法的话可以去了解一下BM算法

        size_t find(char ch,size_t pos = 0)
		{
			assert(pos < _size);
			for (size_t i = pos; i < _size; i++)
			{
				if (_str[i] == ch)
				{
					return i;
				}
			}
			return npos;
		}
		size_t find(const char* str, size_t pos = 0)
		{
			assert(pos < _size);
			const char* ptr = strstr(_str + pos, str);
			if (ptr)
			{
				return ptr - _str;
			}
			else
			{
				return npos;
			}
			return npos;
		}

我们借助strstr就可以轻松实现find,我们再测试一下

C++—string类_第152张图片

没有问题 

我们再写一个substr,截取子串,而且我们还要写拷贝构造

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

我们要开一个一样大的空间来完成深拷贝

        string substr(size_t pos = 0, size_t len = npos)
   		{
			assert(pos < _size);
			size_t n = len;
			if (len == npos || pos + len > _size)//长度超过size
			{
				n = _size - pos;
			}
			string tmp;
			tmp.reserve(n);
			for (size_t i = pos; i < pos + n; i++)
			{
				tmp += _str[i];
			}
			return tmp;
		}

代码也非常简单,我们再测试一下

C++—string类_第153张图片

没有问题 

我们再实现一下resize

        void resize(size_t n, char ch = '\0')
		{
			if (n < _size)
			{
				_size = n;
				_str[_size] = '\0';
			}
			else
			{
				reserve(n);//直接调用reserve检查看是否扩容
				for (size_t i = _size; i < n; i++)
				{
					_str[i] = ch;
				}
				_size = n;
				_str[_size] = '\0';
			}
		}

代码逻辑也非常简单,我们再测试一下

C++—string类_第154张图片

我们和标准库的对比一下,没有问题 

我们再来插入一下流插入和流提取,流插入和流提取涉及抢占第一个位置的原因,不能写成成员函数,所以我们写成全局函数

C++—string类_第155张图片

 我们先这样写,发现报错了,原因是这里涉及到防拷贝的知识,大家现在先了解一下,后面我们会讲,我们修改一下 

ostream& operator<<(ostream& out, const bai::string& s)
{
	/*for (size_t i = 0; i < s.size(); i++)//两种方法都可以
	{
		out << s[i];
	}*/
	for (auto ch : s)
	{
		out << ch;
	}
	return out;
}

代代码一样很简单,我们再测试一下

C++—string类_第156张图片

 此时我们就可以不用c_str来打印了

这里再说一下,函数参数里的bai::string,我们也可以把他放到命名空间里,这样就不用加前缀了

那这样打印和c_str有什么区别呢?

c_str是返回c的字符串,打印的是const char*,是内置类型,遇到\0就终止

而流插入是不管\0的,有多少字符就打印多少字符,目前是有bug的,但我们目前不用管,我们简单实现就行,我们来测试看看区别就明白了

C++—string类_第157张图片

 这里大家就可以看到他们的区别了

这里的bug很麻烦,我们中间有\0的话,我们上面的实现就得改很多内容,我们使用了非常多的str函数,比如strcpy等等,遇到\0就终止,我们都得修改,如果不用strcpy的话,我们得用memcpy

我们来看一个bug,就用拷贝构造举例

C++—string类_第158张图片

 strlen是不用替换的,因为c语言的字符串结束位置是\0,而string的结束位置是看size的

所以我们把strcpy替换为memcpy

C++—string类_第159张图片

C++—string类_第160张图片

修改完后就没有问题了 ,大家要记得把strcpy都替换掉,比如reserve,append那里,也会有bug

C++—string类_第161张图片

在结尾我会把代码全都附上,大家也可以到时候去对比 

C++—string类_第162张图片

我们再看流提取,直接说结论,这样写是错误的,我们输入换行和空格是不能结束的,我们来看一段程序

C++—string类_第163张图片

我们要输入多个字符,中间是以换行或者空格为分割的,流是认为换行和空格是分隔符 

C++—string类_第164张图片

C++—string类_第165张图片 这个问题在整形里尤为明显 

如果没有换行和空格,是不知道输入的到底是什么,比如第二张图的123,这是1,2,3还是123?

此时我们就需要get,无论是什么字符都可以拿过来

另外,我们使用cin对于同一个string每次输入是独立的,但我们现在不是,我们测试一下就明白区别了

C++—string类_第166张图片

C++—string类_第167张图片

我们的代码不会覆盖掉之前的内容,所以我们需要一个clear函数 

C++—string类_第168张图片

clear函数非常简单,这里就不多说了

istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char ch = in.get();
		//in >> ch;
		while (ch != ' ' && ch != '\n')
		{
			s += ch;
			//in >> ch;
			ch = in.get();
		}
		return in;
	}

 我们看流提取,再测试一下

C++—string类_第169张图片

此时就没有问题了 ,不过我们的代码还可以再优化一下,while里面有个+=,怎么看怎么难受,我们一次输入很长的字符串时,会多次扩容,所以我们修改一下

    istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char ch = in.get();
		//in >> ch;
		char buff[128];
		int i = 0;
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == 127) //要给\0留一个位置,所以不是128
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}
			//s += ch;
			//in >> ch;
			ch = in.get();
		}
		if (i != 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}

这里就相当我们用了一个桶,水满了或者接完了我们再放入, 而之前的代码相当于一滴一滴的接 

此时我们的问题都解决了码?没有,如果我们一上来就输入空格,然后输入数据呢?官方的库是可以拿到数据的,而我们的是拿不到数据的,我们下面来解决这个问题

C++—string类_第170张图片

我们加上框里的代码就可以了

我们平时运行代码时看着一行就完事了,其实当我们自己上手的话,会发现有很多很多的问题,所以,任重而道远啊

我们再来写比较大小,string的比较是按ASCII码比较的

C++—string类_第171张图片

 字符串的比较都是按ASCII比较的

C++—string类_第172张图片

我们简单写写,不过这样写还是有bug的,遇到中间\0的还是有问题

用memcmp也是有问题的,坑非常多,首先memcmp要给一个长度,我们给定两个字符串中长度小的那一个,但是还有问题,比如这两个字符串,hello和helloxxx,这样结果就变成相等了,长度+1也不行,比如前面的中间\0问题,一个是hello\0xxxx,一个是hello\0yyyy,问题非常多

        //bool operator<(const string& s)
		//{
		//	size_t i1 = 0;
		//	size_t i2 = 0;
		//	while (i1 < _size && i2 < s._size)
		//	{
		//		if (_str[i1] < s._str[i2])
		//		{
		//			return true;
		//		}
		//		else if (_str[i1] > s._str[i2])
		//		{
		//			return false;
		//		}
		//		else
		//		{
		//			++i1;
		//			++i2;
		//		}
		//	}
		//	//到这里说明字符串前面部分一样
		//	/*if (i1 == _size && i2 != s._size)
		//	{
		//		return true;
		//	}
		//	else
		//	{
		//		return false;
		//	}*/
		//	//return i1 == _size && i2 != s._size;//这2种更简洁
		//	return _size < s._size;
		//}
		bool operator<(const string& s)//复用版本
		{
			bool ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);
			return ret == 0 ? _size < s._size : ret < 0;//ret<0是第一个小于第二个
		}
        //3个测试用例及答案
        //"hello" "hello" false
		//"helloxx" "hello" false
		//"hello" "helloxx" true 

我在这里提供了多种版本,代码的逻辑有点绕,各位最好自己画一下,再对比后面的三个测试例子就明白了

有了小于,大家就可以轻松写出其他的比较,记得使用复用可以写起来更加简便,比如我们写大于等于就可以用小于取反

        bool operator==(const string& s) const
		{
			return _size == s._size &&
				memcmp(_str, s._str,_size) == 0;
		}
		bool operator<=(const string& s) const
		{
			return *this < s || *this == s;
		}
		bool operator>(const string& s) const
		{
			return !(*this <= s);
		}
		bool operator>=(const string& s) const
		{
			return !(*this < s);
		}
		bool operator!=(const string& s) const
		{
			return !(*this == s);
		}

全部复用即可,最后再全部加上const

下面我们再实现赋值,也就是=,我们要实现深拷贝

        string& operator=(const string& s)
		{
			if (this != &s)
			{
				char* tmp = new char[s._capacity+1];
				memcpy(tmp, s._str, s._size+1);
				delete[] _str;
				_str = tmp;
                _size = s._size;
				_capacity = s._capacity;
			}
			return *this;
		}

C++—string类_第173张图片

这样就搞定了 

 不过上面是传统写法,都什么年代了还在写传统代码,接下来看现代的写法

        string& operator=(const string& s)
		{
			if (this != &s)
			{
				string tmp(s);
				std::swap(_str, tmp._str);
				std::swap(_size, tmp._size);
				std::swap(_capacity, tmp._capacity);
			}
			return *this;
		}

C++—string类_第174张图片

 这里是先用拷贝构造,把tmp开辟一个和s一样大的空间,然后tmp出了作用域就会销毁,所以把内容全部换一下就ok了

C++—string类_第175张图片

另外这里是不能和*this换的,会出现栈溢出的问题,我们这里是赋值,然后调用swap,swap的是两个string对象,又是赋值,死循环递归了

C++—string类_第176张图片

 要交换成员才可以

        void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}
		string& operator=(const string& s)
		{
			if (this != &s)
			{
				string tmp(s);
				//this->swap(tmp);两种都可以
				swap(tmp);
			}
			return *this;
		}

最后我们把swap分出来,就变成了这样,当然,还可以继续简化

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

是不是很神奇?这个写法是全自动的,比如我们写s1=s2,这里tmp一上来就是s2的拷贝,还是深拷贝,也就是会先调用拷贝构造,出了作用域tmp还会调用析构函数

拷贝构造也可以使用现代写法,我们来实现一下

        string(const string& s)//拷贝构造
			:_str(nullptr)
			,_size(0)
			,_capacity(0)
		{
			string tmp(s._str);
			swap(tmp);
		}

我们需要对数据进行初始化一下,因为内置类型编译器不做处理,这种写法是调用了构造函数

但是这种写法是有问题的,在中间是\0的时候就会出问题

C++—string类_第177张图片

s4这里后面是没有xxxx的,所以我们建议还是用传统的

引用计数和写时拷贝

写时拷贝这个概念我们了解一下即可,深拷贝的代价是很大的,有时候我们只拷贝,不做别的事情,是很浪费的,我们对比浅拷贝,浅拷贝的问题是会析构两次,一个对象修改会影响另一个对象,所以就出现延迟拷贝(写时拷贝)

C++—string类_第178张图片

他在这里加一个一个引用计数, 只有s1指向时是1,s2也指向时就变成了2,当s3也指向时就变成了3

这样做的话当s2释放时不析构,他会减一下引用计数,s1再释放时再次减少,引用计数变为0,此时才会析构,也就是最后走的一个人关灯

再看第二个问题,如果要修改,发现引用计数是2时,就不能修改,此时就会发生写时拷贝,即写的时候,引用计数如果不是1,就进行深拷贝,再修改

C++—string类_第179张图片

比如s1和s2指向一个空间,然后要修改s2,就会深拷贝,引用计数也会拷贝过来,然后就可以修改了 ,Linux下就使用的是这个

最后,我在这里附上我们模拟实现的全部代码

全部代码

#include
#include
#include
#include
using namespace std;
namespace bai
{
	class string
	{
	public:
		typedef char* iterator;//迭代器
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}

		typedef const char* const_iterator;//const迭代器
		const_iterator begin() const
		{
			return _str;
		}
		const_iterator end() const
		{
			return _str + _size;
		}

		/*string()
			:_size(0)
			,_capacity(0)
			,_str(new char[1])
		{
			_str[0] = '\0';
		}*/
		//string(const char* str = '\0')//构造函数
		//string(const char* str = nullptr)//构造函数
		string(const char* str = "")//构造函数
			:_size(strlen(str))
		{
			_capacity = _size;
			_str = new char[_capacity + 1];
			//strcpy(_str, str);
			memcpy(_str, str,_size+1);
		}
		string(const string& s)//拷贝构造
		{
			_str = new char[s._capacity + 1];
			//strcpy(_str, s._str);
			memcpy(_str, s._str, s._size + 1);
			_size = s._size;
			_capacity = s._capacity;
		}
		//string(const string& s)//拷贝构造
		//	:_str(nullptr)
		//	,_size(0)
		//	,_capacity(0)
		//{
		//	string tmp(s._str);
		//	swap(tmp);
		//}
		//string& operator=(const string& s)//赋值
		//{
		//	if (this != &s)
		//	{
		//		char* tmp = new char[s._capacity+1];
		//		memcpy(tmp, s._str, s._size+1);
		//		delete[] _str;
		//		_str = tmp;
		//		_size = s._size;
		//		_capacity = s._capacity;
		//	}
		//	return* this;
		//}
		void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}
		//string& operator=(const string& s)
		//{
		//	if (this != &s)
		//	{
		//		string tmp(s);
		//		//this->swap(tmp);两种都可以
		//		swap(tmp);
		//	}
		//	return *this;
		//}
		string& operator=(string tmp)
		{
			swap(tmp);
			return *this;
		}
		~string()
		{
			if (_str)
			{
				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];
		}
		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				//strcpy(tmp, _str);
				memcpy(tmp, _str, _size + 1);
				delete[] _str;
				_str = tmp;
				_capacity = n;
			}
		}
		void resize(size_t n, char ch = '\0')
		{
			if (n < _size)
			{
				_size = n;
				_str[_size] = '\0';
			}
			else
			{
				reserve(n);//直接调用reserve检查看是否扩容
				for (size_t i = _size; i < n; i++)
				{
					_str[i] = ch;
				}
				_size = n;
				_str[_size] = '\0';
			}
		}
		void push_back(char ch)
		{
			if (_size >= _capacity)
			{
				//2倍扩容
				reserve(_capacity == 0 ? 4 : _capacity * 2);//这里的判断是防止空串初始化容量为0
			}
			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		}
		void append(const char* str)
		{
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				//至少扩容到_size + len
				reserve(_size + len);
			}
			//strcpy(_str + _size, str);
			memcpy(_str + _size, str, len + 1);
			_size += len;
		}
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}
		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}
		void insert(size_t pos, size_t n, char ch)
		{
			assert(pos <= _size);
			if (_size + n > _capacity)
			{
				//至少扩容到_size + n
				reserve(_size + n);
			}
			//挪动数据
			/*int end = _size;
			while (end >= (int)pos)
			{
				_str[end + n] = _str[end];
				--end;
			}*/
			size_t end = _size;
			while (end >= pos && end != npos)
			{
				_str[end + n] = _str[end];
				--end;
			}
			for (size_t i = 0; i < n; i++)
			{
				_str[pos + i] = ch;
			}
			_size += n;
		}
		void insert(size_t pos, const char* str)
		{
			assert(pos <= _size);
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				//至少扩容到_size + len
				reserve(_size + len);
			}
			//挪动数据
			size_t end = _size;
			while (end >= pos && end != npos)
			{
				_str[end + len] = _str[end];
				--end;
			}
			for (size_t i = 0; i < len; i++)
			{
				_str[pos + i] = str[i];
			}
			_size += len;
		}
		void erase(size_t pos, size_t len = npos)
		{
			assert(pos <= _size);
			if (len == npos || pos + len >= _size)//说明要删完
			{
				_str[pos] = '\0';
				_size = pos;
				_str[_size] = '\0';
			}
			else 
			{
				size_t end = pos + len;
				while (end <= _size)
				{
					_str[pos++] = _str[end++];
				}
				_size -= len;
			}
		}

		size_t find(char ch,size_t pos = 0)
		{
			assert(pos < _size);
			for (size_t i = pos; i < _size; i++)
			{
				if (_str[i] == ch)
				{
					return i;
				}
			}
			return npos;
		}
		size_t find(const char* str, size_t pos = 0)
		{
			assert(pos < _size);
			const char* ptr = strstr(_str + pos, str);
			if (ptr)
			{
				return ptr - _str;
			}
			else
			{
				return npos;
			}
			return npos;
		}
		string substr(size_t pos = 0, size_t len = npos)
		{
			assert(pos < _size);
			size_t n = len;
			if (len == npos || pos + len > _size)//长度超过size
			{
				n = _size - pos;
			}
			string tmp;
			tmp.reserve(n);
			for (size_t i = pos; i < pos + n; i++)
			{
				tmp += _str[i];
			}
			return tmp;
		}
		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}
		 
		//"hello" "hello" false
		//"helloxx" "hello" false
		//"hello" "helloxx" true 
		//bool operator<(const string& s)
		//{
		//	size_t i1 = 0;
		//	size_t i2 = 0;
		//	while (i1 < _size && i2 < s._size)
		//	{
		//		if (_str[i1] < s._str[i2])
		//		{
		//			return true;
		//		}
		//		else if (_str[i1] > s._str[i2])
		//		{
		//			return false;
		//		}
		//		else
		//		{
		//			++i1;
		//			++i2;
		//		}
		//	}
		//	//到这里说明字符串前面部分一样
		//	/*if (i1 == _size && i2 != s._size)
		//	{
		//		return true;
		//	}
		//	else
		//	{
		//		return false;
		//	}*/
		//	//return i1 == _size && i2 != s._size;//这2种更简洁
		//	return _size < s._size;
		//}
		bool operator<(const string& s) const//复用版本
		{
			bool ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);
			return ret == 0 ? _size < s._size : ret < 0;//ret<0是第一个小于第二个
		}
		bool operator==(const string& s) const
		{
			return _size == s._size &&
				memcmp(_str, s._str,_size) == 0;
		}
		bool operator<=(const string& s) const
		{
			return *this < s || *this == s;
		}
		bool operator>(const string& s) const
		{
			return !(*this <= s);
		}
		bool operator>=(const string& s) const
		{
			return !(*this < s);
		}
		bool operator!=(const string& s) const
		{
			return !(*this == s);
		}
		
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
		static size_t npos;
	};
	size_t string::npos = -1;

	ostream& operator<<(ostream& out, const string& s)
	{
		/*for (size_t i = 0; i < s.size(); i++)//两种方法都可以
		{
			out << s[i];
		}*/
		for (auto ch : s)
		{
			out << ch;
		}
		return out;
	}
	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char ch = in.get();
		while (ch == ' ' || ch == '\n')//处理缓冲区前的空格或者换行
		{
			ch = in.get();
		}
		//in >> ch;
		char buff[128];
		int i = 0;
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == 127) //要给\0留一个位置,所以不是128
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}
			//s += ch;
			//in >> ch;
			ch = in.get();
		}
		if (i != 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}
};

以上即为本期全部内容,希望大家可以有所收获

如有错误,还请指正

你可能感兴趣的:(c++,开发语言,c语言,string)