当前位置: 首页 > news >正文

【C++】string 核心难点:STL、编码、迭代器、auto、范围 for 彻底搞懂

🫧个人主页:小年糕是糕手

💫个人专栏:《C++》《Linux》《数据结构》《C语言》

🎨你不能左右天气,但你可以改变心情;你不能改变过去,但你可以决定未来!



目录

一、STL简介

1.1、什么是STL

1.2、STL的版本

1.3、STL的六大组件

二、编码

2.1、不同的编码方式

2.2、GBK

三、初识string类

3.1、初始化

3.2、断言 / 抛异常

3.3、内部逻辑

3.4、遍历 / 修改

四、迭代器

4.1、初识迭代器

4.2、迭代器的优势

4.3、const迭代器

4.4、反向迭代器

4.5、总结

五、算法find

六、auto

6.1、初识auto

6.2、迭代器中的auto

七、范围for

7.1、概述范围for

7.2、范围for中的修改


一、STL简介

1.1、什么是STL

STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的 组件库,而且是一个包罗数据结构与算法的软件框架。

1.2、STL的版本

  • 原始版本Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本,本着开源精神,他们声明允许任何人任意运用、拷贝、修改、传播、商业使用这些代码,无需付费。唯一的条件就是也需要向原始版本一样做开源使用。HP 版本 -- 所有 STL 实现版本的始祖。

  • P.J. 版本由 P.J. Plauger 开发,继承自 HP 版本,被 Windows Visual C++ 采用,不能公开或修改,缺陷:可读性比较低,符号命名比较怪异。

  • RW 版本由 Rouge Wage 公司开发,继承自 HP 版本,被 C++ Builder 采用,不能公开或修改,可读性一般。

  • SGI 版本由 Silicon Graphics Computer Systems, Inc 公司开发,继承自 HP 版本。被 GCC (Linux) 采用,可移植性好,可公开、修改甚至贩卖,从命名风格和编程风格上看,阅读性非常高。我们后面学习 STL 要阅读部分源代码,主要参考的就是这个版本。

1.3、STL的六大组件

大家先简单了解一下即可,后面会为大家分开细致讲解!

二、编码

大家有没有思考过一个问题,我们经常会用sizeof去计算一个字符 / 字符串所占的字节数,但是大家有没有想过一个汉字所占的字节数是多少?他又是怎么算的?

这时候我们就不得不提到另一个话题 --编码

2.1、不同的编码方式

关于汉字所占的字节数,答案取决于具体的编码方式,不同编码下字节数不同。以下是常见情况的总结:

1. UTF-8 编码

  • 大多数汉字占 3 个字节(常用汉字范围:U+4E00到U+9FFF)。

  • 扩展的罕见汉字、繁体字或历史用字可能占4 个字节(如部分CJK扩展区的汉字)。

2. GBK / GB2312 编码(常见于中文Windows系统)

  • 每个汉字固定占2 个字节

3. UTF-16 编码

  • 基本多文种平面(BMP)的汉字占2 个字节(如常用汉字)。

  • 扩展平面(如表情符号、罕见字)占4 个字节(代理对形式)。

4. UTF-32 编码

  • 每个字符固定占4 个字节

5. ASCII 编码

  • 仅支持英文字符,无法表示汉字

简单总结:

编码方式汉字字节数(常见情况)备注
UTF-83字节现代Web和Linux系统主流
GBK2字节旧版Windows中文环境常用
UTF-162字节(基本汉字)Java内部内存表示、Windows API
UTF-324字节固定长度,较少直接使用

2.2、GBK

我们常见的就是GBK,他一个汉字所占的字节数就是2,下面我们用代码来验证一下:

#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; int main() { char buff1[] = "abcA"; buff1[0]++; char buff2[] = "字节"; //GBK下一个汉字占俩个字节,字符串末尾有个\0,所以是五个字节 //CBK也兼容ASCII cout << sizeof(buff2) << endl; cout << buff2 << endl; buff2[1]++; cout << buff2 << endl; buff2[1]++; cout << buff2 << endl; buff2[3]--; cout << buff2 << endl; buff2[3]--; cout << buff2 << endl; return 0; }

我们运行这段代码:

在GBK下我们不但看出来了一个汉字占2个字节数我们还能发现一般谐音的汉字都是放到一块的,这也是我"净网行动"的依据,公司会将不文明的字找出来,同音字编码连续或相近,有利于批量生成谐音黑名单(比如一个脏字的拼音对应一片编码区域),规划了网络文明用语。

三、初识string类

3.1、初始化

我们首先来看string类的初始化操作:

光看文献肯定会有点晦涩,但是我们要习惯于去自己查阅这些资料,这有助于我们的学习,同时也有助于我们提高自己的英文水平,下面我们来看代码解释:

#include<iostream> #include<string>//string #include<algorithm>//算法头文件 #include<list>//链表头文件 using namespace std; //part2: //初始化 void test_string1() { string s1;//默认构造 string s2("hello world");//使用常量字符串数组来构造 string s3(s2); cout << s1 << endl; cout << s2 << endl; cout << s3 << endl; //从0位置开始取,取五个 string s4(s2, 0, 5); cout << s4 << endl; //从第五个位置开始取,超过的也不会报错,取完为止 string s6(s2, 5, 15); //从第六个位置开始一直取到结束 string s7(s2, 6); //这里有个字符串,我想取前六个 string s8("hello world", 6); //想用10个*来初始化 string s9(10, '*'); //赋值 s8 = "xxxx"; cout << s8 << endl; }

3.2、断言 / 抛异常

这时候我们就要再来看一下他内部的断言 / 抛异常:

这个我们结合下文的遍历 / 修改一起演示

3.3、内部逻辑

//string内部逻辑 class string { public: //引用返回,可以修改返回对象 char& operator[](size_t pos) { //返回的是对应字符的别名 return _str[pos]; } private: char* _str; size_t size; size_t capacity; };

3.4、遍历 / 修改

这里我们需要学习string内部的一个运算符重载:

代码如下(注意结合注释):

void test_string2() { //运算符重载operator[] string s1("hello world"); //我想去修改他第0个位置的字符 s1[0] = 'x'; //可以理解为让他变成了数组 //他的底层返回的是原字符的引用,这里是'h' cout << s1 << endl; cout << s1[0] << endl; }

我们可以加上断言 / 抛异常来完善这段代码:

#include<iostream> #include<string>//string #include<algorithm>//算法头文件 #include<list>//链表头文件 using namespace std; void test_string2() { //运算符重载operator[] string s1("hello world"); //我想去修改他第0个位置的字符 s1[0] = 'x'; //可以理解为让他变成了数组 //他的底层返回的是原字符的引用,这里是'h' cout << s1 << endl; cout << s1[0] << endl; //C++中数组越界有严格的断言检查 //这里越界读和写都会报错 s1[12];//断言 //如果你嫌断言太激烈了,可以用at s1.at(12);//越界,at会抛异常,抛异常是可以捕获的 //他们计算出的结果都是s1的长度(都是不包含\0) //这里的\0被认为是标识字符,并不是有效字符 cout << s1.size() << endl;// 推荐 cout << s1.length() << endl; //遍历 or 修改 for (size_t i = 0; i < s1.size(); i++) { s1[i]++; } cout << s1 << endl; } int main() { //test_string1(); try { test_string2(); } catch (const exception& e) { cout << e.what() << endl; } return 0; }

当我们简单学习string类后我们来了解另一个"杀器" --迭代器

四、迭代器

我们要知道俩点:迭代器就是行为像指针一样的东西,他在所有容器都是通用的

4.1、初识迭代器

我们结合上述string类遍历 / 修改的例子来进一步认识一下迭代器:

void test_string2() { //运算符重载operator[] string s1("hello world"); //我想去修改他第0个位置的字符 s1[0] = 'x'; //可以理解为让他变成了数组 //他的底层返回的是原字符的引用,这里是'h' cout << s1 << endl; cout << s1[0] << endl; //C++中数组越界有严格的断言检查 //这里越界读和写都会报错 s1[12];//断言 //如果你嫌断言太激烈了,可以用at s1.at(12);//越界,at会抛异常,抛异常是可以捕获的 //他们计算出的结果都是s1的长度(都是不包含\0) //这里的\0被认为是标识字符,并不是有效字符 cout << s1.size() << endl;// 推荐 cout << s1.length() << endl; //遍历 or 修改 for (size_t i = 0; i < s1.size(); i++) { s1[i]++; } cout << s1 << endl; //迭代器 -- 所有容器都是通用的 //行为像指针一样的东西 string::iterator it1 = s1.begin();//指向的是字符串的第一个位置的字符 while (it1 != s1.end())//这里的end指向的是\0(标识字符) { //迭代器的使用,使用解引用 (*it1)--; //修改 cout << *it1 << " " << endl; ++it1; //指向迭代器下一个位置 } cout << endl; }

这时候肯定有人就会吐槽我们老老实实使用数组 + [ ]形式不就好了,搞这个迭代器意义是什么,我们一般情况下是可以使用数组来解决,但是我们如果要使用链表,数组 + [ ]的形式就行不通了,这时候我们的迭代器就可以帮助我们很快的解决问题,大家要明白:每一个语法设计出来肯定都有它的意义,我们是站在先辈的肩膀上去前行的,当然先辈设计的东西有时候肯定也会存在缺陷,这就需要我们后辈不断地学习,去发现完善!

4.2、迭代器的优势

迭代器的优势体现在在链表等没有[]的数据中也可以遍历

//迭代器的优势体现在在链表等没有[]的数据中也可以遍历 void test() { string s1("hello world"); list<int> lt; //在一个链表中插入1 2 3 lt.push_back(1); lt.push_back(2); lt.push_back(3); list<int>::iterator lit = lt.begin(); while (lit != lt.end()) { //迭代器的使用,使用解引用 (*lit)--; //修改 cout << *lit << " "; ++lit; //指向迭代器下一个位置 } cout << endl; }

4.3、const迭代器

//只读不写记得加上const //const迭代器 //const对象不是使用const iterator去遍历 //因为这里的const修饰的是迭代器本身,不能++,完成不了遍历 void Print(const string& s) { //const string::iterator it1 = s.begin(); string::const_iterator it1 = s.begin(); while (it1 != s.end()) { //(*it1)--; //这里是const迭代器不能修改 cout << *it1 << endl; ++it1; } cout << endl; }

在 C++ 中,迭代器的 “const” 修饰分为两种情况

  1. const 容器::iterator:修饰的是迭代器本身(迭代器这个变量不能被修改),但迭代器指向的内容可以修改
  2. 容器::const_iterator:修饰的是迭代器指向的内容(内容不能被修改),但迭代器本身可以修改

4.4、反向迭代器

void Print(const string& s) { //const string::iterator it1 = s.begin(); string::const_iterator it1 = s.begin(); while (it1 != s.end()) { //(*it1)--; //这里是const迭代器不能修改 cout << *it1 << endl; ++it1; } cout << endl; //反向迭代器 -- 倒着遍历 //rbegin指向的是尾部的数据,rend是第一个数据的前一个数据 //假设有hello world\0,rbegin指向的是d,rend指向的是h的前一个位置 string::const_reverse_iterator it2 = s.rbegin(); while (it2 != s.rend()) { //*it2 = 'x' //不能修改 cout << *it2 << endl; ++it2; } cout << endl; }

4.5、总结

迭代器
1、提供统一的方式遍历修改容器
2、算法可以泛型化,借助迭代器处理容器的数据

五、算法find

这里我们就简单的举个例子给大家看看就好了,最主要的还是自己去写代码实践:

#include<iostream> #include<string>//string #include<algorithm>//算法头文件 #include<list>//链表头文件 using namespace std; //迭代器的优势体现在在链表等没有[]的数据中也可以遍历 void test() { string s1("hello world"); list<int> lt; //在一个链表中插入1 2 3 lt.push_back(1); lt.push_back(2); lt.push_back(3); list<int>::iterator lit = lt.begin(); while (lit != lt.end()) { //迭代器的使用,使用解引用 (*lit)--; //修改 cout << *lit << " " << endl; ++lit; //指向迭代器下一个位置 } cout << endl; //算法find(查找) //我们要查找一个字符串中是否有一个'x',可以采用迭代器 + 算法 string::iterator ret1 = find(s1.begin(), s1.end(), 'x'); if (ret1 != s1.end()) { cout << "找到了x" << endl; } //查找链表中是否有对应字符 list<int>::iterator ret2 = find(lt.begin(), lt.end(), 'x'); if (ret2 != lt.end()) { cout << "找到了x" << endl; } }

记住在算法里面的迭代器区间一定是左闭右开

六、auto

6.1、初识auto

auto:通过初始化值类型自动推导对象类型,C++11中提供的语法

int i = 0; //通过初始化类型自动推导对象类型 auto j = i; auto k = 10;

一般情况下我们发现确实没必须使用auto,还麻烦,但是当我们结合迭代器就会发现他的便捷之处

6.2、迭代器中的auto

下面我们直接通过代码来演示(注意看注释):

void Test() { string s1("hello world"); list<int> lt; //在一个链表中插入1 2 3 lt.push_back(1); lt.push_back(2); lt.push_back(3); string::iterator lt1 = s1.begin(); while (lt1 != s1.end()) { //迭代器的使用,使用解引用 (*lt1)--; //修改 cout << *lt1 << " " << endl; ++lt1; //指向迭代器下一个位置 } cout << endl; //我们要查找一个字符串中是否有一个'x',可以采用迭代器 + 算法 //当我们用表达式来初始化时候就可以使用auto,但是一定程度上降低了代码的可读性 auto ret1 = find(s1.begin(), s1.end(), 'x'); if (ret1 != s1.end()) { cout << "找到了x" << endl; } //查找链表中是否有对应字符 auto ret2 = find(lt.begin(), lt.end(), 'x'); if (ret2 != lt.end()) { cout << "找到了x" << endl; } int i = 0; //通过初始化类型自动推导对象类型 auto j = i; auto k = 10; //p1和p2均是指针 //下面俩种写法的区别是: auto p1 = &i;//这里右边可以传指针,也可以不传指针 auto* p2 = &i;//这里必须传指针 cout << p1 << endl; cout << p2 << endl; //引用 int& r1 = i; auto r2 = r1;//这里推导不出来引用,r1实际上是i的别名,推导出来的r2是int类型 auto& r3 = r1;//这里r3就是引用 }

auto的一大价值之一就是当我们通过很复杂的表达式去初始化时就可以使用auto,同时我们要注意auto当指针类型时的写法

七、范围for

7.1、概述范围for

范围for:是C++11中提出来的一种新语法,能简洁地遍历容器(如数组、string、vector 等)中的所有元素,无需手动管理下标或迭代器。

void Test() { string s1("hello world"); list<int> lt; //在一个链表中插入1 2 3 lt.push_back(1); lt.push_back(2); lt.push_back(3); string::iterator lt1 = s1.begin(); while (lt1 != s1.end()) { //迭代器的使用,使用解引用 (*lt1)--; //修改 cout << *lt1 << " " << endl; ++lt1; //指向迭代器下一个位置 } cout << endl; //我们要查找一个字符串中是否有一个'x',可以采用迭代器 + 算法 //当我们用表达式来初始化时候就可以使用auto,但是一定程度上降低了代码的可读性 auto ret1 = find(s1.begin(), s1.end(), 'x'); if (ret1 != s1.end()) { cout << "找到了x" << endl; } //查找链表中是否有对应字符 auto ret2 = find(lt.begin(), lt.end(), 'x'); if (ret2 != lt.end()) { cout << "找到了x" << endl; } int i = 0; //通过初始化类型自动推导对象类型 auto j = i; auto k = 10; //p1和p2均是指针 //下面俩种写法的区别是: auto p1 = &i;//这里右边可以传指针,也可以不传指针 auto* p2 = &i;//这里必须传指针 cout << p1 << endl; cout << p2 << endl; //引用 int& r1 = i; auto r2 = r1;//这里推导不出来引用,r1实际上是i的别名,推导出来的r2是int类型 auto& r3 = r1;//这里r3就是引用 //C++11 //范围for //自动取容器中的数据赋值给对象(s1 -> exo_m / exo_k) //自动++、自动判断结束 for (auto exo_m : s1) { cout << exo_m << ' '; } cout << endl; for (auto exo_k : lt) { cout << exo_k << ' '; } cout << endl; }

现在我们就有三种方式去遍历我们的链表、遍历我们的string
1、下标 + [ ]
2、迭代器
3、范围for

7.2、范围for中的修改

记住如果我们不需要去修改最好加上const

支持迭代器的容器,都可以用范围for
数组也支持(特殊处理)

#include<iostream> using namespace std; int main() { int a[10] = { 1,2,3 }; for (auto exo : a) { cout << exo << " "; } cout << endl; }


http://www.jsqmd.com/news/639297/

相关文章:

  • IAR网络同传后许可证失效的深度解析与离线激活方案
  • **发散创新:基于Python的自动化恢复演练框架设计与实战**在现代软件系统运维中,
  • 【AI】【内容安全防护】-----如何用Qwen3Guard做实时审核?Stream模式部署实战详解
  • Pixel Language Portal 加速计算实践:利用 .accelerate 库优化推理性能
  • 有实力的安邸上海房产顾问公司分析,口碑哪家更好 - 工业推荐榜
  • Noto字体完全指南:如何用开源方案解决全球900+语言显示问题
  • AIAgent决策引擎稳定性危机(Epsilon-Greedy已失效?):基于127个生产Agent的平衡参数实证分析
  • STL 3D模型体积计算终极指南:快速计算STL文件体积与重量
  • Ralph测试自动化:如何让AI代理编写可靠的测试用例
  • BGE-Large-Zh保姆级教程:交互式热力图配置、缩放、悬停查看分数
  • HackBGRT:深入解析UEFI启动画面定制技术与实践指南
  • 聊一聊防腐木,哪家国标达标、适合花箱花架,售后服务还完善 - mypinpai
  • 八大网盘直链下载神器:告别龟速下载的终极解决方案
  • 网盘直链解析工具:八大主流网盘文件直链获取完整指南
  • API-for-Open-LLM适配器架构揭秘:如何无缝支持新模型
  • explainerdashboard源码解析:深入理解可解释AI的实现原理
  • 终极指南:如何在浏览器中免费体验Windows 12操作系统
  • 千问3.5-2B在教育场景落地:学生作业图题识别、公式读取与解题提示
  • 3个关键技巧解决Blender与虚幻引擎模型动画转换难题:PSK/PSA插件实战指南
  • 2026年4月团建酒店品牌推荐,亲子酒店/奢华酒店/四合院酒店/情侣酒店/网红酒店/酒店/高端酒店,团建酒店品牌推荐 - 品牌推荐师
  • 告别抢票焦虑:大麦网自动化抢票终极指南
  • 力扣热门100题之在排序数组中查找元素的第一个和最后一个位置
  • Hermes和Harness的区别是什么?深度解析2026年企业级AI Agent架构演变与提效实战
  • 5分钟快速上手Jellyfin中文元数据插件MetaShark完整指南
  • RocketMQ消费者参数调优实战:从DefaultMQPushConsumer到高吞吐量配置
  • Pixel Dimension Fissioner 高并发架构设计:应对突发流量与任务队列管理
  • 深度调研:明火煤监测系统厂家口碑排行榜,揭秘用户满意度最高的三大品牌 - 品牌推荐大师
  • Mac Mouse Fix:10分钟让你的普通鼠标在macOS上超越苹果触控板体验!
  • 2026洛阳江浙菜宴请选型指南:诱江南官方联系方式+竞品深度横评+避坑秘笈 - 精选优质企业推荐榜
  • Jabba-IDEA工具配置使用Jabba管理的JDK21