【C++第十章】String

【C++第十章】学习String

STL介绍🧐

  学习string之前,我们要先了解一下STL库,STL是C++标准库的重要组成部分,不仅是一个可复用的组件库,也是一个包罗数据结构与算法的软件框架。STL有四个版本——原始版本、P.J.版本、RW版本、SGI版本,本文所学习的是SGI版本的STL。

  STL六大组件,分别为仿函数、算法、迭代器、空间配置器、容器、配接器,我们所学的string便是容器中的一种(不过严格来说string出现时间早于STL库,属于C++标准库)。

image-20240727232936197

认识String🧐

  string也是属于模板的一种,只不过被重命名了,那为什么要写成模板呢?这要从早期的编码说起。

image-20240727233617526

  由于早期的ASCII码不能很好解决各国语言存在的差异化,随后出现了Unicode(万国码)用于解决传统字符编码的局限性,并且万国码兼容ASCII码,以0开头的就是ASCII码,而常见的汉字基本为两个字节。Unicode是字符集,而Unicode中的UTF-8、UTF-16、UTF-32才是字符编码规则,我们用UTF-8就足够了,而其他语言可能要用UTF-16、UTF-32才能存下该字符。

Pasted image 20240723084849

  我们测试发现,前两个字节组成“你”,后两个字节组成“好”,在Windows下一般使用GBK编码,兼容更多的汉字,Linux下用UTF-8

Pasted image 20240723085250

  并且,在早期还出现过wchar_t宽字节去存储字符,后面为了规范还出了char16和char32去兼容UTF-16和UTF-32,它们所占用字节也不同。而我们发现输入一些生僻字时打印出来会变成乱码,这是因为计算机找不到对应的编码规则,比如编译器中仅存了ASCII的编码规则,我们输入中文进去自然无法识别。

Pasted image 20240723090554

  所以string写成模板是为了去兼容各种字符类型,不管是传char16和char32都可以接收。

基本使用🧐

  在C语言中,我们创建字符串没办法按需申请和释放,但在C++中string支持各种运算符重载,如下代码,字符串能够自动扩容,自动追加,为我们节省了不少时间。

Pasted image 20240719161617

  string初始化的方式有很多种,可以从pos位置,读取n个字符,如果不给n就在pos后全部读取,我们也可以用迭代器(后面有介绍迭代器)进行初始化。

Pasted image 20240719175055

Pasted image 20240719175900

  并且,string可读可写,可以用下标的方式去访问字符串,非常便利。

Pasted image 20240719162031

  注意:当我们使用字符串传参时,最好使用引用传参,传值传参会调用拷贝构造,会进行深拷贝,效率低于引用传参。当我们使用const接收string时,会调用const版本的string和迭代器

Pasted image 20240719171727

迭代器🧐

  迭代器就像是指针一样的东西,能够对容器内的数据进行遍历,我们创建一个迭代器的方法如下,begin是字符串开始的位置,end是字符串结束的位置,end会在\0结束,但是不会算上\0

Pasted image 20240719162644

  迭代器遍历时最好用“!=”来做结束条件,因为it和end都是地址,在连续的地址中还可以使用大于小于来判断,但在非连续的地址中就会失效,所以用“!=”是最好的。

  链表也可以用迭代器遍历,这也体现了C++封装的好处,底层代码不一样,但我们使用方式一样,省去了造轮子的时间。

Pasted image 20240719164732

  迭代器又分正向和反向,反向迭代器的++就是倒着走的。

Pasted image 20240719165128

  auto可以简化代码,自动推出类型,创建迭代器时就不用写那么多了。并且,范围for原理就是编译器替换成了迭代器,但范围for不支持反向迭代

Pasted image 20240719170151

Pasted image 20240719170406

string常用函数🧐

size和length🔎

  size和length都是计算字符串大小不包含\0的函数,由于string早于STL,在STL出来后length的函数名对于其它容器不太规范(函数名意思表达不准确),所以STL中又搞了个size来表示容器大小,具有通用性,而length仅string有

  PS:之所以C++还包含\0,是因为很多接口是C语言实现的,C++调用C接口要传字符串参数时还要用上c_str(),将其转为C语言标准的字符串,并且C语言中字符串以\0为结束标识,如果C++没有那传过去就找不到结束位置了,所以需要C++去兼容C

Pasted image 20240719180424

capacity、empty和clear🔎

  capacity当前字符串的容量,empty为判空,空为1,非空为0,clear清除当前数据,但不清除所开的空间,方便之后再次使用。vs下每次扩容大约扩capacity的1.5倍,g++下扩2倍。

Pasted image 20240719180806

reserve🔎

  reserve可以提前开空间,在知道要开多少空间的情况下,减少扩容次数,提高效率,但不能进行初始化,一般情况下reserve不能将空间缩小。

Pasted image 20240720134105

resize🔎

  resize用于改变字符串长度,当resize大于原字符长度时,给值就插入数据,不给值就添加\0,如果resize大于capacity则会自动扩容小于size就会删除多余字符,我们一般用于开空间+初始化

Pasted image 20240720135339

Pasted image 20240720135215

Pasted image 20240720135553

at🔎

  at功能与方括号一样,但方括号是断言报错,at是抛异常

Pasted image 20240720140316

Pasted image 20240720140254

push_back、append、insert和erase🔎

  由于string给了很多函数重载,我们在这就写几个比较常用的,append和push_back都属于尾插,也可以使用“+=”的方式进行尾插,insert和erase是在pos位置插入和删除,但会挪动数据,效率不高

Pasted image 20240720142233

Pasted image 20240720143903

assign🔎

  将一个已有的字符串赋值给另一个,如果另一个已经有数据了,就会进行覆盖

Pasted image 20240720142950

replace🔎

  replace可以替换字符串中的内容,这里是将pos为5的后两个字符替换成AAA,也会挪动数据

Pasted image 20240720144630

swap🔎

  string的swap与标准库的swap不同,它会交换指针指向减少了拷贝代价,但是我们直接使用swap交换两个字符串时,编译器可以识别到参数类型,调用全局的std::swap(string)函数,所以当我们传入string类型时,不会调用到普通的swap函数

Pasted image 20240720150339

4c15983a0873603108491ad24015ab5

  注:在vs下string的存储方式不一样,内部结构为,先有一个联合体,联合体用来定义string中字符串的存储空间,当字符串小于16时,使用内部固定的字符数组来存放,当字符串大于16时,才会从堆上开辟空间,这样设计是认为大多数情况16个字符长度够用了,所以直接为我们创建一个固定的数组空间,不用再去堆上开辟,提高效率。所以当字符串长度小于16时,不会交换地址,而是直接进行值的交换

image-20240728135013428

find、substr🔎

  参考以下代码,find默认从0开始找,找到后返回下标没找到返回nposrfind从末尾开始找substr从pos位置开始取子串,没限定取多少个就是全部取。

void test_string11()
{
	string s1("test.cpp.tar.zip");
	int i = s1.find("."); //找到第一次出现.的下标
	string s2 = s1.substr(i); //取到该下标的后面的所有字符
	cout << s2 << endl;

	string s3("test.cpp.tar.zip");
	int j = s3.rfind("."); //倒着找
	string s4 = s3.substr(j);
	cout << s4 << endl;

	//查找域名  资源名
	string s5("https://cplusplus.com/reference/string/");
	string sub1, sub2, sub3;
	
	int a = s5.find(':');
	if (a != string::npos) //没找到就会返回npos
		sub1 = s5.substr(0, a); //取0-a之间的子串
	else
		cout << "没有找到a" << endl;

	int b = s5.find("/", a + 3);
	if (b != string::npos)
		sub2 = s5.substr(a + 3, b - (a + 3));
	else
		cout << "没有找到b" << endl;

	sub3 = s5.substr(b+1);

	cout << sub1 << endl;
	cout << sub2 << endl;
	cout << sub3 << endl;
}

Pasted image 20240720162912

find_first_of、find_first_not_of🔎

  find_first_of找出所有参数中字符的下标,find_last_of就是倒着找,第二个参数为在pos位置后开始寻找,下图中找到字符串中’a’、‘c’、‘f’、'l’字符的下标,并全部修改为“=”。

Pasted image 20240720221311

  find_first_not_of找出非参数字符的下标

Pasted image 20240720221555

写实拷贝、引用计数(了解)🧐

  由于存在深浅拷贝,在涉及开辟空间问题上都会自己写一个拷贝构造进行深拷贝,但这时所有的拷贝都会调用拷贝构造,而深拷贝影响效率,所以我们可以用写时拷贝和引用计数的方式来进行优化

  浅拷贝的问题在于修改和析构,所以用引用计数记录有几个对象指向这个空间,在拷贝时++引用计数析构时–引用计数,当我们要修改一个引用计数不为1的数据时,才进行深拷贝,当引用计数减到0时才能进行析构,用这种方式可以减少不必要的深拷贝。

  g++下用的写时拷贝,谁修改谁去做深拷贝,而vs下直接深拷贝。

Pasted image 20240724152337

Pasted image 20240724152209

结尾👍

   以上便是string的全部介绍,如果有疑问或者建议都可以私信笔者交流,大家互相学习,互相进步!🌹

  • 15
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

A.A呐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值