C++ string类 迭代器 范围for

news2024/11/15 19:56:08

string类

 在C语言当中 ,也有字符串,它是以 " \0 " 结尾 的 一些字符的集合,在C的标准库当中还有一些 用于操作 str 类型的库函数,但是,这些函数的功能不是很全面,而且这些操作函数和 str 类型是分开的,不符合 OPP 思想,如果用户对 这些操作函数使用不当的话,可能会有访问越界,或者是 实现不了 想要效果的这些问题。

所以在C++ 当中,对这些 进行了优化,包装,改进,做出了 string类。

string类就是 管理的 字符数组,里面包含了 增删查改 和 一些 功能算法。

string类的文档:

https://cplusplus.com/reference/string/string/?kw=stringhttps://cplusplus.com/reference/string/string/?kw=string

 我们可以在官方文档当中使用 ctrl + F 来搜索当前浏览器当中的内容。

 string类属于C++ 标准库当中的 内容,所以是包含在 std 命名空间当中的,用的时候,要么展开命名空间,要么使用 std::string 的方式访问这个string的内容:

string s1;
std::string s2;

string name("张三");
name = "张飞";

string类的构造函数

string();
创建一个空的string对象,也就是创建一个空的字符串
string (const char* s);
以 C 当中 str 的方式 创建这个 string对象,最后以 " \0 " 结尾。
string (size_t n, char c);
string类当中有 n 个 c 这个字符。
string (const string& str);
拷贝构造函数
string (const string& str, size_t pos, size_t len = npos);
拷贝构造函数,从某一位置开始(pos),拷贝 len 个字符。
string (const char* s, size_t n);
构造一个string 类 ,在 s 常量字符串 的前n 个字符中拷贝
int main()
{
	string s1();
	string s2("张三");
	string s3("hello world");
	string s4(10 , '$');
	string s5(s2);

	string s6(s3, 2, 5);

	return 0;
}

上述的这个拷贝构造函数当中 string (const string& str, size_t pos, size_t len = npos);   npos的值是-1,但是npos 是一个 静态的常量,无符号数,它给的初始值是 -1 ,也是 size_t  ,是无符号数,那么-1 就是最大的数。

那么在官方文档中也有说明:

 表示,如果 给的 len 大于 字符串长度,或者是 此时的 len = npos ;那么都取到字符串的结尾。

 string类对象的访问及遍历操作

当然,在string类当中,还实现了很多的重载符函数,比如 = <   <=  >=  == 等等操作符,基本实现的功能都差不多,但是在Stringe类当中有一个 很厉害的重载符函数(operator[] )。

 operator [] 

 operator[] 在C当中 " [] " 这个操作符相当于是 解引用操作,只有在连续的空间当中才能使用这个 " [] " 这个操作符,比如在栈上开辟的数组和 堆上动态开辟的空间。

那么在自定义类型string 类当中,我们也可以使用 " [] " 来访问这个字符串数组。

使用 下标 + []  的方式来访问string自定义类型。

	string s3("hello world");
	// 直接打s3 当中的内容
	cout << s3 << endl;

	// 下标 + []
	for (int i = 0; i < s3.size(); i++)
	{
		cout << s3[i];
	}
	cout << endl;

	return 0;

输出:

 在string 类当中,底层是使用一个 在堆上开辟的数组来存储 字符串的,那么既然是字符串,就是以 " \0 " 结尾的,我们上述的 循环能不能访问到 "\0"呢?

我们先来看看 size()函数计算出的大小是多少:

	cout << s3.size() << endl;

输出:
 

 我们发现输出的是 11 ,而上述的 "hello world" 的字符串个数就是11 个 ,不加 "\0" 。

因为 "\0" 不是有效字符,他是表示字符串结束的特殊字符。

当我们故意打印出 "\0" 实际上是可以访问到的,但是有些编译器不会显示这个 "\0" ,但是实际上是会访问到的。

那么我们同样可以像使用 " [] " 修改数组一样,对 string 当中的字符串进行修改,因为这个 " operator [] "  函数返回的是 当前 传入的 pos 位置的引用:

 当我们传入的是 普通对象的时候,就是 POS 位置的引用,如果传入的这个对象都是 const 的,那么这个函数的返回值就是  const char&  ,常量引用。

	string s3("hello world");
	char s1[] = "hello world";

	s1[1]++;  // 等价于  *(s1 + 1);
	s3[1]++;  // 等价于 s3.operator[](1);

 上述代码的反汇编如下:

 我们发现,在s3 这个对象当中使用 s3[1]++; 这样的实现,在底层其实就是 调用的 operator[] 这个重载运算符函数。

 关于 string 当中的容量的函数

函数名称功能说明
size(重点)返回字符串有效字符长度
length返回字符串有效字符长度
capacity返回空间总大小
empty (重点)检测字符串释放为空串,是返回true,否则返回false
clear (重点)清空有效字符
reserve (重点)为字符串预留空间**
resize (重点)将有效字符的个数该成n个,多出的空间用字符c填充

 max_size() 计算这个字符串最大可以达到多大的空间:

 但是,max_size ( )计算的值在不同的编译器下的结果是不同的,比如上述结果是在 VS2022 环境下所 输出的结果。

但是如果实在 VS2013 环境下输出如下:

 所以 max_size ()函数在实际当中没有多大是使用意义。

capacity()返回这个空间的容量:

	string s3("hello world");

	cout << s3.capacity() << endl; // 15

 当然,在数据结构当中我们知道,比如在栈当中,当这个栈满的时候,我们插入元素,那么就会就扩容,而一般我们是扩到原来大小的两倍,但是这个扩容大小在不同的版本的C++之下,有所不同。

在 VS2019 下:

注意:

  • 上述的 size( )和 length()这两个函数在底层的实现逻辑是一样的,出现 size( )的目的就是与其他的容器的实现保持一致。
  • clear()这个函数只是将 string类中底层的 数组当中的有效字符清空,并不会改变这个数组的 空间大小。
  • .resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大
  • 小,如果是将元素个数减少,底层空间总大小不变。
  • eserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小

 迭代器

我们可以使用begin和 end  来获取这个string 类当中的字符串的 首位字符串的迭代器,和 最后一个有效字符的后一位置的字符的迭代器。

begin+ endbegin获取一个有效字符的迭代器 + end获取最后一个有效字符下一个位置的迭
代器
rbegin + rendrbegin获取最后一个有效字符的迭代器 + rend获取第一个有效字符前一个位置的迭
代器
	string s3("hello world");
	string::iterator it = s3.begin();
	while (it != s3.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;

输出:

像上述使用 迭代器访问的方式,我们可以把这里的 it 迭代器理解为一个指针,但是迭代器不完全是指针。

而像上述当中的 begin( )和 end(),两个函数返回的是对应位置的迭代器,向上述例子当中可以理解为指针,但是这个函数不是都返回的指针。

 像上述当中的 *it 就是这个it位置的字符。

++it , 就是让 it 这个迭代器往下走,当 it == "\0" 就结束循环。

iterator是一个像指针一样的类型,可能是指针,可能是封装的自定义类型。

 *it 可以理解是指针的解引用,那么就可以使用 解引用来修改字符:
 

	cout << s3 << endl;
	string::iterator it = s3.begin();
	while (it != s3.end())
	{
		(*it)--;

		++it;
	}
	cout << endl;

	it = s3.begin();
	while (it != s3.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;

输出:

不仅仅是在 string类 当中支持迭代器,任何容器都是支持迭代器的,而且用法都类似。 

	vector<int>v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	vector<int>::iterator vit = v.begin();
	while (vit != v.end())
	{
		cout << *vit << endl;
		++vit;
	}

 迭代器的好处就在于,我们之前访问数组这些连续空间当中访问的时候,就可以使用 "[]" 的方式来访问这个其中的数据,但是 "[]" 这样方式访问的前提是 ,这空间是连续的,如果是向链表,树,这样的结构就不能使用 "[]" 的方式访问,但是像上述的这些类型,有些可以实现迭代器,那么就可以使用迭代器的方式来访问其中的数据了。

迭代器提供了一种统一的方式去访问 简单的数组,复杂一点 链表,更复杂的 树,哈希表这些结构。

迭代器的用法就是 和算法来一起使用的 ,我们在库当中查看一些算法的时候,发现很多的函数都实现的所有的类型,比如如下的 reverse()逆置函数,他支持的是所有的类型,在这里就是使用的是迭代器来进行传参,而用模板来实现各种迭代器的匹配问题:

 利用函数模板,来实现不同类型的迭代器的传参,实现函数的重载,这样不管是什么类型的迭代器传进来,都可以使用这个算法。

那么如上述的过程,算法就可以通过容器去修改容器当中的数据,使用迭代器和模板之后,我们就可以不用在去关系实现的数据是什么类型的了,只需要传入这个类型的迭代器即可。 

对于上述的当中的 begin()和 end()函数是顺序访问的取其中的迭代器的函数,那么如果我们想要逆向访问的话,也是可以的。

使用 rbegin()和 rend()函数都是可以进行操作的,所对应获取的位置的迭代器如下所示:
 

上两个函数对应的迭代器就是 reverse_iterator 迭代器,所以我们在使用迭代器的时候,应该使用的迭代器应该是 reverse_iterator。

例子:

	string s3("hello world");

	string::reverse_iterator vit = s3.rbegin();
	while (vit != s3.rend())
	{
		cout << *vit << " ";
		vit++;
	}
	cout << endl;

 输出:
 

 对于上述当中的迭代器,在写起来的时候已经有些麻烦了,我们可以使用auto来自动推导这个迭代器的类型:

auto vit = s3.rbegin();

 一些问题

 我们在实现函数的传参的时候,比如现在我想把 一个 string类的对象传入到函数当中去,那么这里就会取调用这个string类的拷贝构造函数,去创建一个空间,这里是一个深拷贝,这样不仅仅会浪费空间,而且会有损效率,我们在这里的优化解决方案是传入这个 string类对象的 引用,这样就不会发生深拷贝了。如果我们不想修改这个对象,那么我们还会用 const 修饰这个形参

但是如果我们在传入引用之后,函数中使用了这个对象的迭代器,不会报错;但是如果用const 修饰之后就会报错!!!

 如这个例子:

void func(const string& s)
{
	string::reverse_iterator vit = s.rbegin();
	while (vit != s.rend())
	{
		cout << *vit << " ";	
		vit++;
	}
	cout << endl;
}

int main()
{
	string s3("hello world");

	func(s3);

	return 0;
} 

 报错:

 我们发现,报的错是模板的错,而且报的错很复杂,因为模板的实现很复杂。

 这里其实发生的权限的放大,而且对于const 对象的传参,有对应的const 迭代器来使用:

 在官方文档当中,有 const char& operator[] (size_t pos_ const; 这个const的成员函数当我们函数中传入的是 const 的对象的时候,在其中使用的迭代器应该是 const 的 迭代器:

void func(const string& s)
{
	string::const_reverse_iterator vit = s.rbegin();
	while (vit != s.rend())
	{
		cout << *vit << " ";	
		vit++;
	}
	cout << endl;
}

 输出:

对于 普通的迭代器,可以读写,但是对于const 的迭代器,就只能进行读的操作。其中的写功能也就是能不能对string类当中给定字符串中的字符进行修改。

 也就是说 如果 it 是我们定义的迭代器的话,如果不能进行 写的操作,那么是 *it 不能改变,而 it 是可以修改的。

 针对上述的 const 的问题,我们使用 auto就非常的好用,他同样会自动推导这个 const 的迭代器

void func(const string& s)
{
	/*string::const_reverse_iterator vit = s.rbegin();*/
	auto vit = s.rbegin();
	while (vit != s.rend())
	{
		cout << *vit << " ";	
		vit++;
	}
	cout << endl;
}

 输出:

 范围for

 在string类当中的访问,其实最方便的就是使用 返回for 的方式来访问这个 string类当中的字符串。

范围for是在C++11  支持的更简洁的新的遍历方式。

 语法:

for(变量的类型 变量名(s1) : 需要迭代的变量名(s2))
{
    // 其中就可以使用新创建的 s1 这个变量来迭代 s2 
}
	string s3("hello world");
	for (auto str : s3)
	{
		cout << str;
	}

输出:

 在此处我们使用了 auto 来自动的推出这个这个str 的类型,然后他会自动的 迭代,自动判断结束。

如上述例子,就是依次从 s3 当中取数据,然后赋值给 str ,通过这样的方式来进行迭代。

 所以像上述的 方式,如果我们直接修改 str 的内容,s3 当中的字符串是不会改变的

	string s3("hello world");
	for (auto str : s3)
	{
		str++;
	}
	cout << s3 << endl;

输出:

 如果我们使用类型是 这个目标变量的引用就可以修改了:

	string s3("hello world");
	for (auto& str : s3)
	{
		str++;
	}
	cout << s3 << endl;

输出:

 这时候,每一次传入的就是依次传入 这个 字符串当中字符的引用,所以我们就可以进行修改

范围for 其实 在底层的实现就是用的迭代器来实现的,他其实就是使用类似于我们上述在迭代器当中实现的遍历一样来实现,而上述的依次拷贝其实就是把 *it 拷贝给了 str,从而使实现自动迭代。

 我们查看反汇编也能看到一些我们之前在 迭代器当中的一些影子:

 在 返回for 当中也 调用了 begin()和 end()函数来判断 开始和结束。

也就是说,我们使用得很香的 范围for 其实就是使用的 迭代器 来实现的,没有迭代器就没有 范围for,那么有些类型是不支持  迭代器的,那么它就不支持 范围for,例如:

在 Stack 当中就不支持 范围 for:

范围for 也是有局限性的,因为范围for 只能正向的遍历,不能像之前的迭代器一样,还有返乡遍历的 迭代器,范围for只是一个傻瓜式用 正向遍历的 迭代器做成的。

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

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

相关文章

B2B企业需要什么样的客户体验管理?销售易出手了

导读&#xff1a;如何将类似B2C领域的私域体验延展到B2B领域&#xff1f; “不愿在顾客上花时间带来的结果只有一个&#xff0c;那就是让客户转而寻找值得他们花时间的消费体验。”2012年问世的《体验经济》一书&#xff0c;一语道破客户体验的重要性。 过去&#xff0c;提到体…

PG安装使用walminer插件教程

一、下载源码 https://gitee.com/movead/XLogMiner/tree/walminer_3.0_stable/ 二、编译安装插件 克隆下载源码后&#xff0c;将walminer目录放进pg下的contrib目录中 cd /home/postgres/postgresql-15.3/contrib/将walminer源码目录放进此路径下&#xff0c;进入walminer目…

卷麻了,公司新来的00后测试用例写的比我还好,简直无地自容......

经常看到无论是刚入职场的新人&#xff0c;还是工作了一段时间的老人&#xff0c;都会对编写测试用例感到困扰&#xff1f;例如&#xff1a; 如何编写测试用例&#xff1f; 作为一个测试新人&#xff0c;刚开始接触测试&#xff0c;对于怎么写测试用例很是头疼&#xff0c;无法…

STM32寄存器映射

1. 寄存器基本原理 寄存器是单片机内部一种特殊的内存&#xff0c;可以实现对单片机各个功能的控制&#xff0c;我们编写程序最终就是去控制寄存器 下面的举例平台为STM32F407ZG 1.1 STM32寄存器分类 大类小类说明 内核寄存器 内核相关寄存器 包含R0~R15、xPSR、特殊功能寄…

《幸福关系的7段旅程》

关于作者 本书作者安德鲁∙马歇尔&#xff0c;英国顶尖婚姻咨询机构RELATE的资深专家&#xff0c;拥有 30年丰富的咨询经验&#xff0c;并为《泰晤士报》《观察家》和《星期日快报》撰写专栏文章。已出版19部作品&#xff0c;并被翻译成20种语言。 关于本书 《幸福关系的7段…

SQL查询比较慢,如何进行排查?如何进行SQL优化?

目录 一、开启慢查询日志 二、SQL优化 三、总结 一、开启慢查询日志 SQL慢查询是指执行时间较长的SQL语句&#xff0c;可能导致系统性能下降和响应时间延长。通过以下步骤可以开启慢查询日志记录&#xff1a; #查询是否开启慢查询日志 slow_query_log显示ON说明已开启&#…

广和通携手有人物联网完成5G SUL辅助上行功能验证

近日&#xff0c;广和通5G模组FM650-CN已在商用网络中实现5G SUL上行能力增强&#xff0c;助力有人物联网工业路由器在仿真网络环境中完成SUL辅助上行功能的验证。本次验证成功&#xff0c;意味着FM650-CN已具备SUL辅助上行商用能力&#xff0c;有利于推动更多5G终端支持SUL特性…

es 7 Es分布式基础

目录 复杂特性es已经做了分片副本负载均衡实现 设置分片数副本数 双机器读写 自动横向扩容 Node 节点宕机主节点切换 数据路由 增删改操作 读操作 _bulk 复杂特性es已经做了分片副本负载均衡实现 1.每个索引包含多个分片 设置分片数副本数 双机器读写 自动横向扩容 No…

React | React的CSS方式

✨ 个人主页&#xff1a;CoderHing &#x1f5a5;️ React.js专栏&#xff1a;React的CSS方式 &#x1f64b;‍♂️ 个人简介&#xff1a;一个不甘平庸的平凡人&#x1f36c; &#x1f4ab; 系列专栏&#xff1a;吊打面试官系列 16天学会Vue 11天学会React Node专栏 &#x…

Axure教程—多色折线图(中继器)

本文将教大家如何用AXURE中的中继器制作多色折线图 一、效果介绍 如图&#xff1a; 预览地址&#xff1a;https://xpdm3g.axshare.com 下载地址&#xff1a;https://download.csdn.net/download/weixin_43516258/87814320 二、功能介绍 简单填写中继器内容即可动态显示值样…

AppJoint2-2023再看安卓组件化框架

零、什么是组件化 为了避免一些小伙伴一脸懵的进来&#xff0c;又一脸懵的出去&#xff0c;我先简单的说一下什么是组件化。 开发程序时&#xff0c;我们都希望功能间的耦合度尽可能的低&#xff0c;这样的好处是&#xff1a;便于并行开发、代码易于维护、出问题时也好定位。…

抖音seo矩阵系统源码开发开源型私有化部署方案

抖音SEO矩阵系统是基于抖音平台的搜索引擎优化技术的一种系统&#xff0c;其主要作用是通过一系列的技术手段&#xff0c;提高抖音视频的曝光和排名&#xff0c;使其获得更多的流量和粉丝。在本文中&#xff0c;我们将介绍抖音SEO矩阵系统的开发技术&#xff0c;包括系统设计、…

map reduce实现累加器

需求&#xff1a;数组长度为100&#xff0c;每一项为对应下标&#xff0c;累加求和。 切题思路&#xff1a; 1.如何声明一个长度为100的数组&#xff1f;答&#xff1a;new Array(100) 2.数组每一项如何比前一项1 答&#xff1a;map(item,index)index为数组下标&#xff0c;…

企业推行OKR的必要条件

今天我们的话题是“OKR在企业落地执行,有哪些必要条件&#xff1f;” 对于有落地 OKR 经验的人可能更深有感触&#xff0c;OKR理解起来容易&#xff0c;但落地起来却困难重重&#xff0c;常言道“万事开头难”&#xff0c;那接下来我们就先从落地 OKR 的先决条件开始说起吧。 …

我的创作纪念日,成为创作者的第512天

机缘 从事编程岗一有将近4年的时光了&#xff0c;但正在开始总结写博客还是一年前&#xff0c;是在百度搜素资料了解到的CSDN开发者社区。在CSDN认识了很多技术大牛&#xff0c;他们的文章记录了他们的学习路径&#xff0c;看到他们从小白一步一步成长为大牛&#xff0c;这才下…

大数运算(加法,减法,乘法,除法)

目录 一.大数加法 1.题目描述 2.问题分析 3.代码实现 二.大数减法 1.题目描述 2.问题分析 3.代码实现 三.大数乘法 1.题目描述 2.问题分析 3.代码实现 四.大数除法 1.题目描述 2.问题分析 3.代码实现 一.大数加法 1.题目描述 以字符串的形式读入两个数字&#…

Smartbi电子表格故事之高效营销活动后的自助数据分析

自助数据分析是BI的潮流&#xff0c;但几乎都是数据可视化流派&#xff0c;Smartbi电子表格另辟蹊径&#xff0c;在Excel中提供自助分析的能力&#xff0c;然后通过服务器进行发布&#xff0c;这个功能我们称之为“Excel融合分析”&#xff0c;目前在免费版中即可体验。 系统数…

Python脚本如何定时运行

一、背景 很多时候&#xff0c;我们使用Python编写好的各种脚本&#xff0c;会有定时运行的需求&#xff0c;希望脚本能每天在指定的时间点定时运行&#xff0c;比如&#xff1a;定时发送消息、定时发送邮件、定时执行自动化测试脚本等。 定时运行脚本常用的有2种方式&#x…

LeetCode:30. 串联所有单词的子串

30. 串联所有单词的子串 1&#xff09;题目2&#xff09;思路3&#xff09;代码4&#xff09;结果 1&#xff09;题目 给定一个字符串 s 和一个字符串数组 words。 words 中所有字符串 长度相同。 s 中的 串联子串 是指一个包含 words 中所有字符串以任意顺序排列连接起来的子…

CentOS 7安装redis

一、概述 1、redis介绍 Redis 全称 Remote Dictionary Server&#xff08;即远程字典服务&#xff09;&#xff0c;它是一个基于内存实现的键值型非关系&#xff08;NoSQL&#xff09;数据库 2、redis的特点 支持数据持久化 redis支持数据的持久化&#xff0c;可以将内存中的…