STL——string(二)

本文详细介绍了C++中的string类,包括迭代器的使用,如正向遍历、反向遍历和const迭代器。此外,还讲解了string的成员函数,如[]重载、at、insert、erase、replace、swap、c_str、find、rfind等,以及如何在特定场景下有效地使用它们。文章强调了了解不常用成员函数的重要性,并提醒注意操作的效率和数据移动的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

string类迭代器

遍历数组

 反向遍历

const迭代器

[ ]重载

at

back&&front 

 insert

erase

replace

string::swap

c_str

find 

rfind

 find_last_of

 运算符重载


 

前面我们简单介绍了string的一些常用成员函数,今天我们接着选择性地讲解string类,不常用的不代表没用,我们得知道,常用的是必须掌握的。

string类迭代器

2edcac4678d54ce9a77f6ca98c9b0055.png

遍历数组

e42faa6ce0934cda9d8e8e2523f84a6f.png

34e940d92b28493287f74dd3c45b2210.png 除了用size()遍历数组,这里我简单介绍一下用迭代器访问数组每个元素的操作,也为我们后面的学习做铺垫。

string a("hello world");
	string::iterator it = a.begin();
	while (it != a.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

iterator作为一个独立的类型,需要指定string::才能被解析成string内部的迭代器。这里的begin和end我们先肤浅地理解为指针,大概像下图这个样子。
a39c8b39b8254feb99a4ece4dc099dcc.png

  除了用这种方式,我们还可以用范围for,底层还是迭代器。

e91af1d3c709426eb269651727b292b2.png

 反向遍历

反向遍历通过反向迭代器的方式实现,这里我们使用rbegin和rend。

090a0561f9ae4d2795062c31f8907985.png

 遍历结构应该是这样:

71f9cd074471467784ec6fceeb271cdd.png

const迭代器

顾名思义,const迭代器用于返回const对象,我们遍历数组的时候因为不涉及改动对象就可以加上const

void Func(const string& s)
{
	string::const_iterator it = s.begin();//只能读不能写
	while (it != s.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

此时名称发生了变化,注意这里的const有一个下划线,是一个名称不是一个关键字,真正被const修饰的是对象,而不是 it(begin的返回值)

const正向迭代器声明: 

const_iterator begin() const;//修饰this指针

 我们也可以这样理解:

const int* p;//指向的对象不能改变

反向迭代器也同理。

如果觉得麻烦,可以使用auto自动推导类型:
2b9409588dac49809877948360654e1a.png

这里auto推导的类型为对象返回类型,这里我们定义的是const类型的对象。 

为了区分const和普通迭代器,string类添加了这样的函数:

648264bdb40a478380a0820b96f88a3a.png

 用法基本一致,大家可按照自己的规范去使用。

5f7b3b16b25c4429a79efba8697374fc.png

[ ]重载

89983ed1464d4bc98da784bf488453f1.png

注意区分const和普通对象

迭代器看起起来没有直接用[ ]重载来的方便,但他的出现能解决以后一些很复杂的数据结构,希望大家对此有个清晰的认识。

at

87d7659372c74f118cf0dbaa58474d31.png

 与[ ] 不同的是对越界的检查,前者是assert,后者是抛出异常的方式,我们可以通过捕获的方式发现异常。

bad186f4fcb84fb48083472acea9b472.png

back&&front 

 back和front是返回string数组的第一个和最后一个元素(不包括\0)

25c80f79c99a4416b7d4a1ee51816329.png

 insert

0d668f14b206464e88bb72966308b790.png

 我们简单写几个insert实例

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

意为在第pos个位置插入字符串(数组下标)

742a3a6741ad45ee880a163682cb1cf7.png

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

 意为在第pos个位置插入n个字符c

dc985db0b9404a4abfd685d4533933cc.png

iterator insert (iterator p, char c);

 迭代器插入:c6bac77add3f4a358938755de856c7ff.png

 一般情况下我们不推荐使用insert插入数据,结合数据结构的知识,我们知道插入是需要移动空间并提高复杂程度的,需要时可以查文档使用。

erase

与insert相对的就是erase,删除元素。

8e4b87fe00a549a48a8feabc5e05642e.png

 这里我们介绍前面两个:

327105f8aedf4c73a4b2f95ae77284b3.png

 迭代器单参可以删除pos位置的一个元素。如果删除长度len大于数组自身长度,就会删除其后的所有元素。同样地,erase也不推荐经常使用。

replace

replace能替换指定位置的字符或字符串,但使用replace有两个弊端,第一是空间不够需要扩容,其次是需要挪动数据。我们举个填充字符串中的空格的例子来看一下replace的常用用法。

在这之前,我们需要引入find来查找空格的位置:

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

遍历string,我们可以用string::npos(静态成员)的方式循环: 

e9776c6ba94b4bc1bd442c51a9142b62.png

意为匹配失败则返回-1(整形的最大值) 

用到的replace:

string& replace (size_t pos,  size_t len,  const char* s);

意为用字符串替换第pos个位置的后len个字符。 

这里我们用reserve进行提前开空间,避免了扩容重新分配空间和复制内容的消耗。接着我们用循环来遍历string,因为填充后的内容早已不是空格,为了避免无用的查找,我们直接跳过该字符串,提升了查找的效率。 

string s2("i  love you more than you will ever know");
	size_t num = 0;
	for (auto ch : s1)
	{
		if (ch == ' ')
		{
			num++;
		}
	}
	//提前开空间,避免扩容
	s2.reserve(s2.size() + 2 * num);
		size_t i = s2.find(' ');
	while (i != string::npos)
	{
		s2.replace(i, 1, "%20");
		i = s2.find(' ',i+3);
	}

 输出结果:

d087b8722e3f45578c6fbd730a24cb0e.png

虽然经过了细致的处理,但还是少不了数据的移动,这里我们简单介绍一下空间换时间的做法:

	string New_s2;
	New_s2.reserve(s2.size() + 2 * num);
	for (auto ch : s2)
	{
		if (ch != ' ')
		
			New_s2 += ch;
		else
			New_s2 += "%20";
	}
	s2 = New_s2;
	cout << New_s2 << endl;

string::swap

e3e89ee2fae54e4f8f635ddacdf5cb8f.png

为什么要在前面特别写出string类域呢?那是因为在std中也有一个类模板的swap函数,但是用法不同,那他们的差别在哪呢?

效率对比:

string中的swap交换只需改变指针的指向,就能实现元素的交换。而类模板的swap涉及深拷贝,其实现逻辑可能更为复杂,之后我们会谈到。

dab60ed0e39642fb9b6963b3d5d7a2e3.png

c_str

 99d7760dba7745fbb638390195e696d1.png

 这个接口的出现主要是为了兼容c风格返回的字符串,什么意思呢,我们举例说明。

b096f66096ab4b4abc3a3ca5244f52ae.png  可以看到第一次打印并没什么区别,第二次s3遇到\0没有终止继续输出后面的内容,而c_str结束了打印。在c语言中,\0作为char*类型的结束的标志,而c_str继承了这一特性,以常量指针的方式记录string的值,而直接使用重载了cout的string则会输出它全部的size大小,不需要管\0是否存在。

当我们在使用c的接口打开string类型的文件名时,为了区分c++的接口,需要用到这个c接口。

31417470652141b09bb1af3a24c182f6.png

 成功读取内容

find 

9f047b3b3bc4498a9a96e5c923e797e0.png

可以看到,find能查找常量字符串,string类型和单个字符。

我们以查找文件后缀为例讲解

这里我们用到substr接口来保存后缀名

31eec80bbed9481bb5b3ebcae27d30da.png

 用法就是保留第pos个位置后npos个字符串

1434596fb4614c4483736889d6377b57.png

 这里可以直接用substr的缺省npos,直接取到 .后到文件名结束。

思考如果有多个后缀想取它真实的后缀(最后一个后缀)怎么办呢?

除了有find接口,string还提供了rfind接口,意为反向查找

rfind

86e19e48d76b497d9043b7e8e702d967.png

 利用npos从最后一个向前找的功能,我们可以这样修改代码:

string file("test.cpp.zip");
	size_t pos = file.rfind('.');
	if (pos != string::npos)
	{
		string sf = file.substr(pos);
		cout << sf << endl;
	}

下面有一段查找网址的代码,大家可以自行领悟

string url("http://www.cplusplus.com/reference/string/string/find/");
		cout << url << endl;
		size_t start = url.find("://");
		if (start == string::npos)
		{
			cout << "invalid url" << endl;
		}
		else
		{
			start += 3;
			size_t end = url.find('/', start);
			string address = url.substr(start,end-start);
			cout << address << endl;
		}

find_fisrt_of 

72366001e42946b786a75722a774e781.png

 注意这里的first容易被误解成查找第一个字符串,实际他是将查找出现的任何字符或字符串。只要找到其中任意一个字符,它就会返回。

ecf394869a854984bcf697b19d9a0984.png found第一次的值时=是匹配字串的数量,这里found的初值为3。

 find_last_of

与find_first_of相反,find_last_of是倒着找。大家可以看看官方文库理解

f6340289cd3b4fc19a1e7c666cbcd48e.png

 除此之外还有下面两个接口与它们作用相反,大家了解一下。ed26a6505d354842992686e83d09856b.png

 运算符重载

在运算符重载那一张我们模拟实现了日期类的运算符,相信大家也不陌生了。需要注意的是,string类没有-运算符重载,存在+重载,但尽量少用(涉及拷贝)。

希望大家通过今天的学习,对string有个更深的认识,之后我会将这部分的习题写成博客上传,喜欢的别忘了一键三连! 

 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小C您好

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

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

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

打赏作者

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

抵扣说明:

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

余额充值