【STL 】C++ 字符串处理利器:string 保姆级入门教程

【STL 】C++ 字符串处理利器:string 保姆级入门教程

前言

string是 C++ 中处理字符串的核心工具,比 C 语言char*更安全高效,但多数人仅会基础用法。

这份内容从string的基础概念出发,聚焦构造、访问、容量、修改等核心接口,搭配测试、案例(如分割网址)讲清用法与场景,最后补充编译器差异,帮你系统掌握string的高效使用。

📚 C++ 初阶

【……】

【 C++const成员与日期类 】

【 类和对象(下篇)】

【 C/C++内存管理 】

【 C++模版初阶 】


目录

一、string介绍

1、string 类是什么?

2、string 类的核心特点

3、使用前的准备

二、string常用接口的介绍和使用

☆ 构造函数

1、子串构造

【小测试】:string对应字符串后面有没有 '\0'

2、截取 C 串前 n 个字符构造

3、重复字符填充构造

4、迭代器范围构造

5、单参数构造的隐式类型转换 

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

1、operator[]

2、begin+ end

3、rbegin + rend

☆ string类对象的容量操作

1、size

2、length

3、capacity

4、empty

5、clear

6、reserve(重要)

【问题】:string对象在容量不足时,不是会自动扩容,为啥会设计个扩容接口呢?

7、resize(重要)

三、string类对象的修改操作

1、push_back

2、append

3、operator+=(重要)

4、c_str

5、find(重点)

6、rfind

7、substr

【find+substr 小案例:分割网址】

【+= 小案例:所有空格替换成%20】

8、insert

9、erase

四、string类非成员函数

1、operator+

2、operator>>

3、getline

4、operator<<

5、relational operators

五、vs和g++下string结构的说明

☆ vs 中string的结构

© g++ 下string的结构

六、写时拷贝与引用计数(了解)


一、string介绍

1、string 类是什么?

C++ 中string是专门表示字符串的类,本质是basic_string模板类以char实例化后的别名,底层定义为:

typedef basic_string<char> string;

2、string 类的核心特点

1、接口兼容 + 专属扩展它的接口和 C++ 标准容器(比如数组)类似,但额外加了很多字符串专属操作(比如拼接、查找子串),用起来更贴合字符串场景。

2、单字节字符限制注意:string是按字节处理数据的,不适合操作多字节 / 变长字符(比如 UTF-8 编码的中文、 emoji(比如😀、😂、👍))—— 这类场景下,它的长度、迭代器等功能会按字节计算,而不是实际的字符数。

也就是说上述字符每个占2个字节,当然具体的根据编码不同,所占字节数会有所不同,但是ASCLL中的所有字符都只占一个字节

3、使用前的准备

string类时,代码里必须加这两句:

#include <string> // 包含头文件 using namespace std; // 引入std命名空间

二、string常用接口的介绍和使用

☆ 构造函数

(constructor) 函数名称功能说明
string ()(无参默认构造)构造空的 string 类对象,即空字符串
string (const string& str)(拷贝构造)拷贝构造函数,用已有的 string 对象构造新对象
string (const string& str, size_t pos, size_t len = npos)(子串构造)从已有 string 对象的 pos 位置开始,截取 len 个字符构造新对象(len 默认取到末尾)
string (const char* s)(C 风格字符串构造)用 C 风格字符串(char*)构造 string 类对象
string (const char* s, size_t n)(截取 C 串前 n 个字符构造)用 C 风格字符串的前 n 个字符构造 string 类对象
string (size_t n, char c)(重复字符填充构造)构造包含 n 个字符 c 的 string 类对象
template <class InputIterator>string (InputIterator first, InputIterator last)(迭代器范围构造)用迭代器 [first, last) 区间内的字符构造 string 类对象

简单的我就不详细介绍了,我只介绍几个复杂一点的:

1、子串构造

string (const char* s)用于通过 C 语言风格的字符串构造 string 对象;string (const string& str, size_t pos, size_t len = npos) 则是从已有 string 对象对应的字符串中,截取从 pos 下标开始的 len 个字符来构造新的 string 对象。

参数规则:

        若 pos 超过字符串的最大下标,会触发断言错误;

        若原字符串 str 长度不足,或 len 取值为string::npos,则会从 pos 位置开始复制到字符串末尾。

pos越界:


使用npos缺省值:


【小测试】:string对应字符串后面有没有 '\0'

运行后控制台无输出,且未触发断言警告。

这个现象可以说明:string 内部会在存储的字符串末尾维护\0,但由于\0是不可打印字符,所以输出时不会显示;而我们从原字符串的末尾位置(下标 11)截取字符时,刚好取到了这个\0,因此控制台没有打印出可见内容。


【npos成员变量】

string::npos 定义为 -1,在赋值给 size_t 类型时,触发有符号 int 到无符号 size_t 的算数转换-1的补码(全 1)被解释为size_t的最大值。

若平台是 32 位系统,size_t是 32 位无符号整数,此时npos的值为2³² - 1

若平台是 64 位系统,size_t是 64 位无符号整数,此时npos的值为2⁶⁴ - 1

我之所以能直接用cout打印string对象,是因为标准库中为string重载了<<(配合cout)和>>(配合cin)这两个输入输出运算符(具体接口介绍后面讲)。


2、截取 C 串前 n 个字符构造


3、重复字符填充构造


4、迭代器范围构造

核心用法(先会用,细节后面讲):

用两个类似指针的迭代器,划定原字符串的字符范围,构造新string

s.begin()类比 “指向字符串第一个字符的指针”;s.end()类比 “指向字符串最后一个字符的下一个位置的指针”;迭代器支持+/-偏移(如s.begin() + 5),就像指针偏移一样。
这里有同学就会有疑问了,如果向指针那样去使用,s.begin()+5不应该指向空格吗,为啥没打印空格呢?

这是因为容器迭代器构造均遵循左闭右开规则begin()+5是终止标记,仅拷贝[begin(), begin()+5)区间内的元素(共 5 个),不会访问终止标记指向的位置,因此只输出 5 个字符。

5、单参数构造的隐式类型转换 

单参数构造函数支持隐式类型转换

如果想禁止这种隐式转换,可以给单参数构造函数加explicit关键字(比如explicit string(const char* s)),这样就不能直接用string s = "张三",得显式写string s("张三")啦~


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

1、operator[]

功能:返回pos位置的字符


2、begin+ end

功能:begin获取第一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器

迭代器是 C++ 容器(如 string、vector、map)的 “通用访问工具”,本质是封装了指针的对象,作用是:✅ 遍历容器中的元素(读 / 写);✅ 屏蔽不同容器的底层实现差异(比如数组、链表遍历方式不同,但迭代器用法统一)。

核心特点:用法像指针:支持*it(取元素)、++it(移动)、it->(取成员)等操作;分类适配场景:比如begin()返回指向第一个元素的迭代器end()返回 “最后一个元素的下一个位置的迭代器”;注意失效:容器扩容 / 缩容 / 删除元素时,迭代器可能失效(指向无效内存),需重新获取(后面实现时讲解)。
实际上在string这个容器中迭代器就是指针,begin返回的是指向第一个字符的指针,end返回的是指向最后一个字符下一个位置的指针,这是因为string容器存储的是字符串,可以直接通过指针对字符串进行接引用操作等


3、rbegin + rend

功能:和begin/end的区别是,rbegin/rend是反向遍历,rbegin指向的是最后一个数据的位置,rend指向的是第一个元素


<4> const迭代器

这个和上述介绍的迭代器没本质区别,不同的就是const迭代器指向的对象不能被修改


☆ string类对象的容量操作

1、size

功能:返回字符串有效大小(不包含末尾的\0


2、length

功能:返回字符串有效长度(不包含末尾的\0

注:lengthsize功能完全一致(返回结果相同),只是命名来源不同 ——length是早期为字符串设计的接口,size是后续为了统一所有容器(如vector)的接口风格新增的,二者在string中是等价的。


3、capacity

功能:返回空间总大小

string 的自动扩容规则在插入数据且当前空间不足时,string 会自动扩容,不同编译器的扩容策略不同:VS 环境下:第一次扩容为当前容量的 2 倍,后续扩容为当前容量的 1.5 倍;g++ 环境下:每次扩容均为当前容量的 2 倍。

4、empty

功能:检测字符串是否为空串,是返回true,否则返回false


5、clear

功能:清空有效字符


6、reserve(重要)

功能:提前给字符串分配好指定大小的内存空间(不会改变size)


【场景1】:reverse要求100个字节空间,但却开辟了111个字节空间,为啥呢?

这是 VS 编译器的内存对齐 / 容量对齐策略导致的:reserve(100)只是要求 “至少 100 字节”,VS 会在满足需求的基础上,额外多分配一些空间(比如按特定规则对齐),所以实际开辟了 111 字节。

核心:reserve(n)保证容量≥n,具体大小由编译器的内存管理策略决定。

【场景2】:reserve 请求空间小于当前容量时的容量变化(缩容)

reserve(n)n小于当前字符串容量时:标准将 “缩小容量” 定义为非约束请求—— 编译器 / 容器实现可以自主决定是否缩容,但通常会选择 “不缩容”(比如本示例中当前容量 15>请求的 10,容量保持 15 不变),最终保证容量不小于n即可。

【场景3】:clear+reserve 组合操作下的缩容行为

在最新版vs中,即使先clear()reserve(n < 当前容量)reserve也不会触发缩容,字符串容量会保持原大小。

但是在老版本中,先clear()reserve(n < 当前容量)reserve也会触发缩容

reserve在新版 VS 里的行为:✅ 当 n > 当前容量:正常扩容(满足容量≥n);❌ 当 n < 当前容量:完全不缩容,容量保持原样(仅保证不低于 n,但不会主动减小)。

【问题】:string对象在容量不足时,不是会自动扩容,为啥会设计个扩容接口呢?

当能提前确定字符串所需空间大小时,直接用reserve(n)一次性分配好n大小的内存,就能避免后续添加字符时因空间不足触发的多次自动扩容(每次扩容都会涉及内存重新申请和数据拷贝),从而减少性能损耗,提升程序运行效率。

7、resize(重要)

功能:将有效字符的个数改成n个,多出的空间用 "字符c" 或者 "/0" 填充

【场景1】:n > oldsize

字符串会被扩展到 n 个字符,新增的位置默认填充 '\0'(空字符);如果显式传第二个参数(比如 resize(n, 'a')),则用指定字符填充。


【场景2】:n < oldsize

字符串会被截断,只保留前 n 个字符,超出部分直接丢弃(不可逆)。


三、string类对象的修改操作

1、push_back

功能:在字符串后尾插字符c


2、append

功能:在字符串后追加一个字符串


3、operator+=(重要)

前面这两个接口虽说用着还行,但我感觉都不如这个接口好用

【功能】:在字符串后追加字符串str

我觉得后面这两种重载函数其实没必要单独实现 —— 第一个重载已经能兼容后两种场景了:不管传单个字符还是 C 风格字符串,都能通过单参数构造函数的隐式类型转换,自动生成对应的 string 对象来参与追加操作。


4、c_str

功能:返回C格式的字符串


5、find(重点)

功能:从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置

6、rfind

功能:从指定位置开始,从后往前搜索目标内容在字符串中最后一次出现的位置


7、substr

功能:在str中从pos位置开始,截取n个字符,然后将其返回


【find+substr 小案例:分割网址】

int main() { string url = "ftp://www.baidu.com/?tn=65081411_1_oem_dg"; // http://www.baidu.com/?tn=65081411_1_oem_dg // 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; //资源名 string uri; size_t pos2 = url.find('/', pos1 + 3); if (pos2 != string::npos) { domain = url.substr(pos1 + 3, pos2 - (pos1 + 3)); uri = url.substr(pos2 + 1); } cout << domain << endl; cout << uri << endl; return 0; }

【+= 小案例:所有空格替换成%20】

int main() { // 所有空格替换成20% string s2("hello world hello bit"); string s3; for (auto ch : s2) { if (ch != ' ') { s3 += ch; } else { s3 += "20%"; } } cout << s3; return 0; }

8、insert

功能:任意位置插入内容

对于非法插入操作,程序一般会抛出异常来提示错误。不过我现在还没学到异常处理的相关内容,所以这里就不写异常捕获的代码了(之前用到的几个插入接口,遇到非法插入时也会以抛异常的方式报错)。


9、erase

功能:删除字符串中任意位置的内容

这个接口用起来确实挺灵活,但得谨慎使用 —— 它底层的效率比较低,会涉及大量的数据移动操作。


四、string类非成员函数

1、operator+

功能:拼接字符串(不会修改原字符串)

尽量少用,因为传值返回,导致深拷贝效率低


2、operator>>

功能:流提取运算符重载

这个时候就有疑问了,为啥只打印出了前半段呢?

这是因为cin在从缓冲区读取字符串时,通常以换行和空格为结束标识,所以,只读取到hello


3、getline

如果想读取包含空格的整行字符串,得用getline(cin, s)

功能:获取一行字符串(和>>不同的是,它仅仅以换行为结束标识)


4、operator<<

功能:流插入运算符重载

5、relational operators

功能:大小比较

如果条件成立则返回真,否则返回假

以上是一些重要接口的使用案例,还有几个常用接口我暂未展开,等下篇讲解具体实现时,我会再详细介绍!!!

五、vs和g++下string结构的说明

注意:下述结构是在 32 位平台下验证的(32 位平台中指针占 4 个字节)。

☆ vs 中string的结构

vs 的string对象总占28 个字节,内部结构包含一个联合体(用于存储字符串),逻辑如下:

union _Bxty { // storage for small buffer or pointer to larger one value_type _Buf[_BUF_SIZE]; pointer _Ptr; char _Alias[_BUF_SIZE]; // to permit aliasing } _Bx; 
当字符串长度小于 16时:直接用内部固定的字符数组(联合体中的_Buf)存放,无需额外开辟堆空间,效率更高;

当字符串长度大于等于 16时:从堆上开辟空间(用联合体中的指针_Ptr指向堆内存)。

除此之外,还包含 3 个字段:

size_t字段:保存字符串的长度

size_t字段:保存堆空间的总容量

一个指针:用于其他辅助逻辑。

这几部分的内存占用计算:联合体(16 字节) + 长度(4 字节) + 容量(4 字节) + 指针(4 字节) = 28 字节


© g++ 下string的结构

g++ 的string是通过写时拷贝实现的,string对象本身只占4 个字节(仅包含一个指针)。

这个指针会指向一块堆空间,堆空间内部包含以下字段:

        空间总大小(_M_capacity);

        字符串有效长度(_M_length);

        引用计数(_M_refcount);

struct _Rep_base { size_type _M_length; size_type _M_capacity; _Atomic_word _M_refcount; };

        指向实际存储字符串的指针。

六、写时拷贝与引用计数(了解)

我们平时实现 string 的拷贝构造等时,一般会采用深拷贝 —— 这是因为浅拷贝存在两个问题:

  • 会导致同一块资源被两次析构(引发内存错误);
  • 修改其中一个对象的内容时,会连带影响另一个对象(破坏数据独立性)。

但深拷贝的开销(性能 / 内存成本)是比较高的,而写时拷贝与引用计数这两项技术,能够在一定程度上缓解(或解决)这一问题。

引用计数:s1 构造 s2 时,让两者共享内存并记录引用计数(初始 1→构造后 2)。只有计数归 0 才释放内存,解决了浅拷贝的两次析构问题

写时拷贝:若其中一个对象要修改共享数据,会为其单独拷贝一份新内存再修改,同时原内存计数减 1,解决了修改一个对象影响另一个的问题

Read more

用 Python 调用 Bright Data MCP Server:在 VS Code 中实现实时网页数据抓取

用 Python 调用 Bright Data MCP Server:在 VS Code 中实现实时网页数据抓取

用 Python 调用 Bright Data MCP Server:在 VS Code 中实现实时网页数据抓取,本文介绍了Bright Data的Web MCP Server,这是一款能实现实时、结构化网页数据访问的API,适用于AI应用等场景。其支持静态与动态网页,前3个月每月提供5000次免费请求,有远程托管和本地部署两种方式。文章以在VS Code中用Python调用其API抓取Google搜索结果为例,详解了准备工作、代码编写、参数说明等实战流程,还提及该工具免维护代理池等技术亮点及使用限制。 一、引言:为什么AI时代需要高效的网页数据访问工具? 在大语言模型(LLM)和智能代理(Agent)快速发展的今天,"实时性"成为AI应用落地的关键瓶颈。想象一下:当你的AI助手需要回答"今天上海的天气预警"或"某款产品的最新用户评价"时,它必须依赖实时网页数据才能给出准确答案—

By Ne0inhk
Python NumPy入门指南:数据处理科学计算的瑞士军刀

Python NumPy入门指南:数据处理科学计算的瑞士军刀

作者:唐叔在学习 专栏:唐叔学python 标签:Python NumPy、数据分析、科学计算、机器学习基础、数组操作、Python数据处理、人工智能基础、Python编程 摘要 NumPy是Python科学计算的基础库,提供了高性能的多维数组对象和工具。本文唐叔将带你从零开始了解NumPy的核心概念、常用操作和实际应用场景,助你在数据分析、机器学习等领域快速上手。无论你是Python初学者还是想提升数据处理能力,这篇文章都将成为你的实用指南。 文章目录 * 摘要 * 一、NumPy是什么?为什么它如此重要? * 二、NumPy安装与基础使用 * 2.1 安装NumPy * 2.2 导入NumPy * 2.3 创建第一个NumPy数组 * 三、NumPy核心功能详解 * 3.1 数组属性 * 3.2 创建特殊数组 * 3.3 数组索引与切片

By Ne0inhk
python搭建NPL模型的详细步骤和代码

python搭建NPL模型的详细步骤和代码

目录 * **一、环境准备** * **二、数据准备** * **三、文本预处理** * **1. 清理文本** * **四、特征工程** * **1. TF-IDF** * **2. Word2Vec** * **五、搭建 NLP 模型** * **1. 逻辑回归** * **2. LSTM 深度学习模型** * **六、使用预训练的 BERT 模型** * **七、模型评估** * **八、部署模型** * **总结** * 1. **人机交互的核心技术** * 2. **推动AI技术发展的动力** * 3. **广泛的应用场景** * 4. **多模态融合的关键环节** * 5. **行业数字化转型的加速器** * 6. **未来发展的潜力** 一、环境准备 在开始之前,我们需要安装 NLP

By Ne0inhk
当Python遇见高德:基于PyQt与JS API构建桌面三维地形图应用实战

当Python遇见高德:基于PyQt与JS API构建桌面三维地形图应用实战

摘要: 地图技术作为数字化世界的基石,其应用早已超越了传统的导航和位置服务。对于开发者而言,如何将强大的地图能力集成到不同形态的应用中,是一个充满挑战与机遇的课题。本文将详细阐述一个独特的实践案例:如何利用Python的PyQt5框架,结合高德开放平台强大的JavaScript API 2.1Beta,从零开始构建一个功能丰富的桌面端地图浏览器。项目不仅实现了二维、三维、卫星、地形等多种地图样式的动态切换,还集成了地点搜索(POI)、实时标记等核心功能。本文将深入探讨技术选型、架构设计、核心功能实现、Python与JavaScript双向通信机制,并在此基础上拓展实现“点击获取坐标与地址(逆地理编码)”及“路线规划”等高级功能,旨在为开发者提供一个将Web地图技术无缝融入桌面应用的完整解决方案,展现高德开放平台在跨技术栈融合应用中的卓越潜力。 一、 引言:为何选择在桌面端构建地图应用? 在移动互联网和Web应用大行其道的今天,探讨桌面地图应用的开发似乎有些“复古”。然而,在特定业务场景下,桌面应用依然拥有不可替代的优势。例如,在专业地理信息系统(GIS)、行业数据监控中心、复杂的

By Ne0inhk