c++从入门到跑路——string类

作者:小肝一下日期:2026/4/14

c++从入门到跑路——string类

在这里插入图片描述

1.为什么学习string类?

1.1 C语言中的字符串

C语言中,字符串是以’\0’结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列 的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户 自己管理,稍不留神可能还会越界访问。

1.2 两个面试题(暂不做讲解)

把字符串转换成整数_牛客题霸_牛客网

415. 字符串相加 - 力扣(LeetCode)

在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、 快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。讲白了,就是c语言那套面向过程的思想已经跟不上社会发展的变化了,而c++创新地采用了面向对象思想,利用封装,多态,继承,极大的提高了开发效率,以及开发潜力。

2.标准库中的string类

2.1 string类(了解)

cplusplus.com/reference/string/string/?kw=string

这个链接是关于c++里面string类的文档介绍

注意:在使用string类时,必须包含#include头文件以及using namespace std;

2.2 auto和范围for

auto关键字

​ 在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,后来这个 不重要了。C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型 指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期 推导而得。

​ 用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&

​ 当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际 只对第一个类型进行推导,然后用推导出来的类型定义其他变量。

在这里补充2个C++11的小语法,方便我们后面的学习。

(1)auto不能作为函数的参数,可以做返回值,但是建议谨慎使用
(2) auto不能直接用来声明数组

1#include<iostream>
2using namespace std;
3int func1()
4{
5    return 10;
6}
7// 不能做参数
8void func2(auto a)
9{}
10
11// 可以做返回值,但是建议谨慎使用
12auto func3()
13{
14return 3;
15}
16int main()
17{
18int a = 10;
19auto b = a;
20auto c = 'a';
21auto d = func1();
22// 编译报错:rror C3531: “e”: 类型包含“auto”的符号必须具有初始值设定项
23auto e;
24cout << typeid(b).name() << endl;
25cout << typeid(c).name() << endl;
26cout << typeid(d).name() << endl;
27int x = 10;
28auto y = &x;
29auto* z = &x;
30auto& m = x;
31cout << typeid(x).name() << endl;
32cout << typeid(y).name() << endl;
33cout << typeid(z).name() << endl;
34
35auto aa = 1, bb = 2;
36// 编译报错:error C3538: 在声明符列表中,“auto”必须始终推导为同一类型
37auto cc = 3, dd = 4.0;
38
39// 编译报错:error C3318: “auto []”: 数组不能具有其中包含“auto”的元素类型
40auto array[] = { 4, 5, 6 };
41
42return 0;
43
44}
45
1#include<iostream>
2#include <string>
3#include <map>
4using namespace std;
5auto aa = 1, bb = 2;
6// 编译报错:error C3538: 在声明符列表中,“auto”必须始终推导为同一类型
7auto cc = 3, dd = 4.0;
8// 编译报错:error C3318: “auto []”: 数组不能具有其中包含“auto”的元素类型
9auto array[] = { 4, 5, 6 };
10int main()
11{
12std::map<std::string, std::string> dict = { { "apple", "苹果" },{ "orange", 
13"橙子" }, {"pear","梨"} };
14// auto的用武之地
15//std::map<std::string, std::string>::iterator it = dict.begin();
16auto it = dict.begin();
17while (it != dict.end())
18{
19cout << it->first << ":" << it->second << endl;
20++it;
21}
22
23return 0;
24}
25

范围for

对于一个有范围的集合=而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此 C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围 内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束 范围for可以作用到数组和容器对象上进行遍历 范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到

1#include<iostream>
2#include <string>
3#include <map>
4using namespace std;
5int main()
6{
7	int array[] = { 1, 2, 3, 4, 5 };
8	// C++98的遍历
9	for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
10	{
11		array[i] *= 2;
12	}
13	for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
14	{
15		cout << array[i] << endl;
16	}
17	// C++11的遍历
18	for (auto& e : array)
19		e *= 2;
20	for (auto e : array)
21		cout << e << " " << endl;
22	string str("hello world");
23	for (auto ch : str)
24	{
25		cout << ch << " ";
26	}
27	cout << endl;
28	return 0;
29}
30
31

2.3 string类的常用接口说明(注意下面我只讲解最常用的接口)

1.string类对象的常见构造

构造函数链接:

http://www.cplusplus.com/reference/string/string/string/

在这里插入图片描述

一、string () 无参构造函数(重点)
1. 形参类型解释

无输入形参,为无参构造函数。

2. 函数的功能与代码实现

核心功能:构造一个空的std::string对象,字符串长度为 0,无有效字符,仅初始化底层基础内存结构,无字符数据存储。

1#include <iostream>
2#include <string>
3using namespace std;
4
5int main() {
6    // 基础用法:构造空字符串
7    string empty_str;
8    cout << "空字符串长度:" << empty_str.size() << endl; // 输出 0
9    cout << "是否为空:" << empty_str.empty() << endl;     // 输出 1(true)
10
11    // 典型场景:先构造空串,后续赋值
12    string str;
13    str = "hello world";
14    cout << "赋值后内容:" << str << endl; // 输出 hello world
15    return 0;
16}
17
3. 开发中的注意事项
  • 空 string 对象是合法、可安全操作的对象,不是空指针,调用empty()size()、赋值等成员函数不会触发崩溃;
  • 空串的底层内存会在后续赋值 / 追加内容时自动扩容分配,无需手动管理。
二、string (const char* s) 构造函数(重点)
1. 形参类型解释

形参为const char* s:指向\0结尾的 C 风格字符串的只读指针。

__为什么必须用 const char(一针见血)_*:

  1. 兼容字符串字面量:双引号包裹的"xxx"本质是const char[N],只能绑定到const char*,非 const 的char*无法接收,会直接编译报错;
  2. 保证入参安全:const 承诺函数内部不会修改原字符串内容,避免误改只读内存导致程序崩溃;
  3. 通用性最强:可同时接收const char*char*、字符数组、字符串字面量,覆盖所有 C 风格字符串场景。
2. 函数的功能与代码实现

核心功能:读取入参s指向的 C 风格字符串(从起始地址到\0为止),将内容全量拷贝到新构造的std::string对象中,生成与原 C 字符串内容完全一致的 C++ 字符串。

1#include <iostream>
2#include <string>
3using namespace std;
4
5int main() {
6    // 最常用:用字符串字面量直接构造
7    string str1("hello C++");
8    cout << "str1: " << str1 << endl; // 输出 hello C++
9
10    // 用char数组(C风格字符串)构造
11    char c_arr[] = "C style string";
12    string str2(c_arr);
13    cout << "str2: " << str2 << endl; // 输出 C style string
14
15    // 用const char*指针构造
16    const char* c_ptr = "const char string";
17    string str3(c_ptr);
18    cout << "str3: " << str3 << endl; // 输出 const char string
19    return 0;
20}
21
3. 开发中的注意事项
  • 入参s必须以 **\0空字符结尾 **,否则会触发内存越界读取,导致程序崩溃或乱码;
  • 禁止传入nullptr、野指针、未初始化的指针,会触发未定义行为,直接导致程序崩溃;
  • 空串""是合法入参,会构造一个空的 string 对象,与无参构造效果一致。

三、string (size_t n, char c) 构造函数
1. 形参类型解释
  • size_t n:无符号整数类型,指定最终生成字符串的固定长度(字符个数)
  • char c:基础字符类型,用于填充字符串的单个字符。
2. 函数的功能与代码实现

核心功能:构造一个长度为n的 string 对象,字符串的每一个字符都统一为入参的字符c

1#include <iostream>
2#include <string>
3using namespace std;
4
5int main() {
6    // 基础用法:生成5个'a'组成的字符串
7    string str1(5, 'a');
8    cout << "str1: " << str1 << endl; // 输出 aaaaa
9
10    // 实用场景:生成分隔线
11    string split_line(20, '-');
12    cout << split_line << endl; // 输出 --------------------
13
14    // 给已有字符串赋值
15    string str2;
16    str2 = string(4, '9');
17    cout << "str2: " << str2 << endl; // 输出 9999
18    return 0;
19}
20
3. 开发中的注意事项
  • size_t是无符号类型,禁止传入负数,负数会被隐式转换为极大的无符号数,导致内存分配失败、程序崩溃;
  • 字符c必须用单引号' '包裹,双引号包裹的是字符串,会编译报错;
  • n=0时,会构造一个空的 string 对象。
四、string (const string& s) 拷贝构造函数(重点)
1. 形参类型解释

形参为const string& sstd::string类型的只读左值引用

为什么用 const string&

  1. 提升效率:传引用避免拷贝整个字符串的全量数据,仅传递地址,极大降低性能开销;
  2. 保证原对象安全:const 承诺函数不会修改原字符串对象,同时可接收 const / 非 const 的 string 对象、临时 string 对象;
  3. 符合 C++ 语法规范:拷贝构造函数的标准签名要求,是实现值语义的核心,保证拷贝的合法性。
2. 函数的功能与代码实现

核心功能:用一个已存在的 string 对象s,深拷贝构造出一个内容、长度完全一致的新 string 对象;新对象拥有独立的内存空间,与原对象互不影响,修改其中一个不会改变另一个。

1#include <iostream>
2#include <string>
3using namespace std;
4
5int main() {
6    // 基础用法:拷贝构造
7    string origin("origin string");
8    string copy_str(origin); // 调用拷贝构造
9    cout << "原字符串:" << origin << endl;    // 输出 origin string
10    cout << "拷贝字符串:" << copy_str << endl; // 输出 origin string
11
12    // 验证深拷贝:修改拷贝对象,原对象不受影响
13    copy_str = "modified string";
14    cout << "修改后拷贝字符串:" << copy_str << endl; // 输出 modified string
15    cout << "原字符串:" << origin << endl;            // 仍输出 origin string
16    return 0;
17}
18
3. 开发中的注意事项
  • 该函数是深拷贝,新对象有独立的内存空间,与原对象生命周期完全解耦;
  • 当 string 对象以值传递的方式传入函数、或以值的方式返回时,编译器会自动调用该拷贝构造函数;
  • 标准库已处理自赋值场景,无需额外判断,但开发中应避免string s(s);这类无意义的自拷贝写法。
2.string类对象的容量操作

在这里插入图片描述

链接如下:

cplusplus.com/reference/string/string/?kw=string

注意:

1.size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接 口保持一致,一般情况下基本都是用size()。

2.clear()只是将string中有效字符清空,不改变底层空间大小。

3.resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不 同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数 增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。

4.reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参 数小于string的底层空间总大小时,reserver不会改变容量大小

一、size () 函数(重点)
1. 函数应用场景

日常开发中获取字符串有效字符个数的核心函数,用于循环遍历字符串的边界控制、用户输入长度合规校验、字符串截取的边界判断等所有需要获取字符串实际有效长度的场景,是 string 类最高频使用的函数之一。

2. 函数的功能与代码实现

核心功能:返回 string 对象中有效字符的个数,不包含底层自动添加的\0结束符,返回值类型为无符号整数size_t

1#include <iostream>
2#include <string>
3using namespace std;
4
5int main() {
6    string str = "hello C++";
7    // 核心用法:获取有效长度
8    size_t valid_len = str.size();
9    cout << "有效字符长度:" << valid_len << endl; // 输出9
10
11    // 典型场景:长度合规校验
12    if (str.size() > 5) {
13        cout << "字符串长度符合要求" << endl;
14    }
15    return 0;
16}
17
3. 开发中的注意事项
  • 返回值是size_t无符号整数,禁止与负数做比较(无符号数会将负数转为极大值,导致判断逻辑永久失效);
  • 仅统计有效字符,不含底层自动添加的\0结束符;
  • 空字符串调用size()返回 0,是安全操作,不会触发崩溃。

二、length () 函数
1. 函数应用场景

size()功能完全等价,是 C++ string 类的历史兼容设计,仅用于老项目代码维护,新开发代码不推荐优先使用。

2. 函数的功能与代码实现

核心功能:和size()完全一致,返回 string 对象的有效字符个数,返回值类型为size_t,底层共用同一套实现逻辑。

1#include <iostream>
2#include <string>
3using namespace std;
4
5int main() {
6    string str = "test string";
7    cout << "length()结果:" << str.length() << endl; // 输出11
8    cout << "size()结果:" << str.size() << endl;     // 输出11,结果完全一致
9    return 0;
10}
11
3. 开发中的注意事项
  • 功能、性能、返回值与size()完全无差异;
  • 新开发代码优先使用size(),符合 STL 容器的统一命名规范(vector、list 等容器均用size()获取长度),提升代码可读性;
  • 同样需注意返回值是无符号类型,避免与有符号数错误比较。

三、capacity () 函数
1. 函数应用场景

用于查看 string 底层已分配的内存容量,分析字符串自动扩容的性能损耗,配合reserve()做内存预分配优化,高频用于循环拼接字符串、大数据量字符串处理等需要性能调优的场景。

2. 函数的功能与代码实现

核心功能:返回 string 对象底层已分配的内存空间可容纳的最大字符个数,不含\0结束符的占用空间,capacity的值始终大于等于size

1#include <iostream>
2#include <string>
3using namespace std;
4
5int main() {
6    string str = "hello";
7    cout << "有效长度size:" << str.size() << endl;
8    cout << "已分配容量capacity:" << str.capacity() << endl;
9
10    // 追加字符,观察自动扩容
11    str += "12345678901234567890";
12    cout << "追加后size:" << str.size() << endl;
13    cout << "追加后capacity:" << str.capacity() << endl; // 容量自动扩容,常见1.5/2倍增长
14    return 0;
15}
16
3. 开发中的注意事项
  • capacity已分配的内存上限,不是有效字符长度,有效长度始终以size为准;
  • 不同编译器的 string 扩容策略不同,capacity的值不具备跨编译器一致性;
  • capacity不会自动缩小,即使clear()清空字符串,已分配的内存通常也不会释放。

四、empty () 函数(重点)
1. 函数应用场景

高频用于字符串判空,比如用户输入合法性校验、接口入参空值判断、循环终止条件判断,是比if(str.size() == 0)更高效、可读性更强的写法。

2. 函数的功能与代码实现

核心功能:检测 string 是否为空串(有效字符长度为 0),空则返回true,非空返回false,返回值类型为bool

1#include <iostream>
2#include <string>
3using namespace std;
4
5int main() {
6    string str1 = "";
7    string str2 = "hello";
8    // 核心用法:判空
9    cout << "str1是否为空:" << str1.empty() << endl; // 输出1(true)
10    cout << "str2是否为空:" << str2.empty() << endl; // 输出0(false)
11
12    // 典型场景:输入校验
13    string user_input;
14    if (user_input.empty()) {
15        cout << "输入不能为空,请重新输入" << endl;
16    }
17    return 0;
18}
19
3. 开发中的注意事项
  • empty()O (1) 时间复杂度,部分编译器底层直接判断标志位,比size() == 0性能更优,判空优先使用empty()
  • 仅判断有效字符是否为 0,与capacity无关,即使capacity不为 0,size为 0 时empty()也返回true
  • 空 string 对象调用empty()是安全操作,无崩溃风险。

五、clear () 函数(重点)
1. 函数应用场景

清空字符串的有效内容,重置为空串,用于循环复用 string 对象、处理完字符串后重置状态、清空无效内容,是重置字符串最常用的方法。

2. 函数的功能与代码实现

核心功能:清空 string 对象的所有有效字符,将size置为 0,不会释放底层已分配的capacity内存,后续复用无需重新分配内存,执行效率更高。

1#include <iostream>
2#include <string>
3using namespace std;
4
5int main() {
6    string str = "hello world";
7    cout << "清空前size:" << str.size() << endl;
8    cout << "清空前capacity:" << str.capacity() << endl;
9
10    // 核心用法:清空字符串
11    str.clear();
12
13    cout << "清空后size:" << str.size() << endl;       // 输出0
14    cout << "清空后capacity:" << str.capacity() << endl; // 容量保持不变
15    return 0;
16}
17
3. 开发中的注意事项
  • clear()仅清空有效字符,不释放底层内存,适合后续复用该 string 对象的场景;
  • 如需清空同时释放内存,需在clear()后调用shrink_to_fit()
  • 空字符串调用clear()是安全操作,无副作用。

六、reserve () 函数(重点)
1. 函数应用场景

预分配内存,避免字符串频繁自动扩容导致的性能损耗,是高频字符串拼接、大数据量字符串处理的核心优化手段,比如循环拼接大量字符、日志内容拼接、文件内容读取到 string 中,提前知道最终长度时使用。

2. 函数的功能与代码实现

核心功能:为 string 对象预分配至少能容纳 n 个字符的内存空间,仅修改capacity的值,不会改变size(有效字符长度),也不会修改字符串的实际内容。

1#include <iostream>
2#include <string>
3using namespace std;
4
5int main() {
6    string str;
7    cout << "初始capacity:" << str.capacity() << endl;
8
9    // 核心用法:预分配1000个字符的容量
10    str.reserve(1000);
11    cout << "reserve后capacity:" << str.capacity() << endl; // 容量≥1000
12    cout << "reserve后size:" << str.size() << endl;         // 输出0,有效长度不变
13
14    // 优化场景:循环拼接,避免多次扩容
15    for (int i = 0; i < 1000; i++) {
16        str += 'a';
17    }
18    cout << "拼接后capacity:" << str.capacity() << endl; // 容量不会再次扩容
19    return 0;
20}
21
3. 开发中的注意事项
  • reserve(n)预分配至少 n 的容量,编译器可能分配比 n 更大的空间,保证capacity≥n
  • 当 n 小于当前capacity时,reserve()不会缩容,不会修改capacity,属于无操作;
  • 仅修改内存容量,不改变有效字符和size,不会修改字符串内容;
  • 仅当提前知道字符串最终长度时使用,否则无优化意义,过度预分配会造成内存浪费。

七、resize () 函数(重点)
1. 函数应用场景

修改字符串的有效字符长度,用于截断过长的字符串、扩容字符串并填充指定字符、固定长度字符串的初始化,需要同时修改有效长度和字符串内容的场景。

2. 函数的功能与代码实现

核心功能:修改 string 对象的有效字符个数size为 n,分两种核心场景,提供两个重载版本:void resize(size_t n);void resize(size_t n, char c);

  1. n < 当前size:截断字符串,保留前 n 个有效字符,size变为 n,capacity不变;
  2. n > 当前size:扩容有效长度到 n,多出的位置用指定字符c填充(不指定则默认用\0填充),若 n 超过capacity会自动扩容。
1#include <iostream>
2#include <string>
3using namespace std;
4
5int main() {
6    string str = "hello";
7    cout << "初始内容:" << str << " | size:" << str.size() << endl; // hello | 5
8
9    // 用法1:截断字符串
10    str.resize(3);
11    cout << "截断后内容:" << str << " | size:" << str.size() << endl; // hel | 3
12
13    // 用法2:扩容并指定填充字符
14    str.resize(6, 'x');
15    cout << "扩容后内容:" << str << " | size:" << str.size() << endl; // helxxx | 6
16
17    // 用法3:扩容不指定填充字符,默认用'\0'填充
18    str.resize(8);
19    cout << "默认扩容后size:" << str.size() << endl; // 8
20    return 0;
21}
22
3. 开发中的注意事项
  • resize()直接修改有效字符长度size,会改变字符串的实际内容,这是和reserve()最核心的区别;
  • 当 n 超过当前capacity时,会自动扩容capacity,扩容策略由编译器决定;
  • 截断字符串时,仅修改size,不会释放底层capacity内存;
  • 填充字符仅在扩容时生效,截断时不会修改保留的字符内容。
3.string类对象的访问及遍历操作

在这里插入图片描述

链接:

cplusplus.com/reference/string/string/?kw=string

一、operator [] 运算符重载(重点)
1. 函数应用场景

string 类单个字符的随机访问与修改的核心方式,支持数组式的下标操作,O (1) 时间复杂度,是日常开发中最高频的字符访问方式,适用于指定位置字符读写、下标循环遍历、单字符修改等场景。

2. 函数的功能与代码实现

核心功能:重载 [] 运算符,返回字符串中下标pos位置字符的引用;非 const 对象调用返回可写引用,支持修改字符;const 对象调用返回 const 只读引用,仅支持读操作,不可修改字符。

1#include <iostream>
2#include <string>
3using namespace std;
4
5int main() {
6    string str = "hello";
7    // 1. 读操作:获取指定下标字符
8    cout << "下标1的字符:" << str[1] << endl; // 输出 e
9    // 2. 写操作:修改指定下标字符
10    str[0] = 'H';
11    cout << "修改后字符串:" << str << endl; // 输出 Hello
12    // 3. 下标循环遍历
13    for (size_t i = 0; i < str.size(); ++i) {
14        cout << str[i] << " ";
15    }
16    // const对象只读访问
17    const string const_str = "const test";
18    cout << "\nconst对象下标0:" << const_str[0] << endl; // 合法只读
19    // const_str[0] = 'C'; // 非法,编译报错,const引用不可修改
20    return 0;
21}
22
3. 开发中的注意事项
  • 下标合法范围为 0 ≤ pos < str.size(),越界访问会触发未定义行为,大概率导致程序崩溃,operator []不做边界检查
  • 如需带边界安全检查的访问,使用at()成员函数,越界会抛出out_of_range异常;
  • 空字符串访问下标 0 行为未定义,访问前必须先判空;
  • const 对象仅能调用 const 版本的 operator [],不可修改字符内容。

二、begin () + end () 正向迭代器对
1. 函数应用场景

string 类STL 兼容的正向遍历、区间操作的标准方式,是所有 STL 容器通用的遍历规范,适配 sort、reverse 等所有 STL 算法,适用于全量 / 区间正向遍历、容器通用代码编写、配合 STL 算法做字符串处理等场景。

2. 函数的功能与代码实现

核心功能

  • begin():返回指向字符串第一个有效字符的正向迭代器;
  • end():返回指向字符串最后一个有效字符的下一个位置的尾后迭代器(不指向任何有效字符,不可解引用);
  • 两者组成左闭右开区间[begin(), end()),完整覆盖所有有效字符;const 对象调用返回 const 迭代器,仅可读不可修改。
1#include <iostream>
2#include <string>
3#include <algorithm>
4using namespace std;
5
6int main() {
7    string str = "abcdef";
8    // 1. 正向迭代器遍历
9    string::iterator it;
10    for (it = str.begin(); it != str.end(); ++it) {
11        cout << *it << " "; // 解引用获取字符
12    }
13    cout << endl;
14    // 2. 迭代器修改字符
15    for (it = str.begin(); it != str.end(); ++it) {
16        *it = toupper(*it); // 转大写
17    }
18    cout << "转大写后:" << str << endl; // 输出 ABCDEF
19    // 3. 配合STL算法:反转字符串
20    reverse(str.begin(), str.end());
21    cout << "反转后:" << str << endl; // 输出 FEDCBA
22    return 0;
23}
24
3. 开发中的注意事项
  • end()是尾后迭代器,绝对禁止解引用、自增操作,否则触发未定义行为;
  • 字符串发生扩容(如 push_back、append)、删除操作后,原迭代器会失效,不可继续使用;
  • const 对象只能获取 const 迭代器,无法通过 const 迭代器修改字符;
  • 迭代器遍历是 STL 容器通用规范,代码可无缝适配 vector、list 等其他 STL 容器。

三、rbegin () + rend () 反向迭代器对
1. 函数应用场景

string 类反向遍历、逆序区间操作的专用方式,无需手动倒序控制下标,适用于从后往前查找字符、逆序输出字符串、逆序区间处理等场景,代码更简洁安全。

2. 函数的功能与代码实现

核心功能

  • rbegin()(reverse begin):返回指向字符串最后一个有效字符的反向迭代器;
  • rend()(reverse end):返回指向字符串第一个有效字符的前一个位置的反向尾后迭代器,不可解引用;
  • 两者组成左闭右开的反向区间[rbegin(), rend()),完整覆盖所有有效字符;反向迭代器执行++操作时,会向字符串头部移动,实现逆序遍历。
1#include <iostream>
2#include <string>
3using namespace std;
4
5int main() {
6    string str = "hello";
7    // 1. 反向遍历
8    string::reverse_iterator rit;
9    for (rit = str.rbegin(); rit != str.rend(); ++rit) {
10        cout << *rit << " "; // 输出 o l l e h
11    }
12    cout << endl;
13    // 2. 反向修改字符
14    for (rit = str.rbegin(); rit != str.rend(); ++rit) {
15        *rit = toupper(*rit);
16    }
17    cout << "转大写后:" << str << endl; // 输出 HELLO
18    // 3. const对象只读反向遍历
19    const string const_str = "reverse test";
20    string::const_reverse_iterator crit;
21    for (crit = const_str.rbegin(); crit != const_str.rend(); ++crit) {
22        cout << *crit << " ";
23    }
24    return 0;
25}
26
3. 开发中的注意事项
  • rend()是反向尾后迭代器,禁止解引用、自增操作,否则触发未定义行为;
  • 反向迭代器的++操作是向字符串头部移动,与正向迭代器移动方向相反;
  • 字符串扩容、删除操作后,原反向迭代器会失效,不可继续使用;
  • 如需完整反转整个字符串,优先使用reverse(str.begin(), str.end()),比反向遍历赋值性能更优。

四、范围 for 循环(C++11 及以上)
1. 函数应用场景

C++11 新增的极简全量遍历语法,无需关心下标、迭代器边界,代码可读性极强,适用于无特殊区间要求的字符串全量遍历,是日常开发中无特殊需求时的首选遍历方式。

2. 函数的功能与代码实现

核心功能:底层基于begin()+end()迭代器实现,自动遍历字符串的全部有效字符,自动处理迭代器的起止与移动,无需手动管理边界;支持只读遍历与可写遍历。

1#include <iostream>
2#include <string>
3using namespace std;
4
5int main() {
6    string str = "hello world";
7    // 1. 只读遍历:值拷贝,不修改原字符串
8    for (char ch : str) {
9        cout << ch << " ";
10    }
11    cout << endl;
12    // 2. 可写遍历:引用传递,直接修改原字符串
13    for (char& ch : str) {
14        ch = toupper(ch);
15    }
16    cout << "转大写后:" << str << endl; // 输出 HELLO WORLD
17    // 3. 高效只读遍历:const引用避免拷贝
18    const string const_str = "const test";
19    for (const char& ch : const_str) {
20        cout << ch << " ";
21    }
22    return 0;
23}
24
3. 开发中的注意事项
  • 范围 for 仅支持全量遍历整个字符串,无法指定区间、无法反向遍历,有区间要求的场景需使用普通迭代器;
  • 遍历过程中,若对字符串执行扩容、删除操作,会导致底层迭代器失效,遍历行为未定义,禁止在范围 for 循环内修改字符串容量;
  • 只读遍历推荐使用const char&,避免字符拷贝,提升性能;需要修改原字符串必须使用char&,值拷贝的修改不会影响原字符串;
  • 仅支持 C++11 及以上标准,老版本编译器需开启对应编译标准。
4.string类对象的修改操作

在这里插入图片描述

注意: 1. 在string尾部追加字符时,s.push_back© / s.append(1, c) / s += 'c’三种的实现方式差 不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可 以连接字符串。

  1. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。
一、push_back 函数
1. 函数应用场景

单字符粒度的字符串尾部追加,是最小单位的字符串拼接操作,适用于循环逐个读取字符、单字符增量拼接的场景,比如从输入流逐字符读取、字符过滤后追加存储。

2. 函数的功能与代码实现

核心功能:在 string 对象的尾部追加 1 个 char 类型字符,自动维护字符串有效长度 size,容量不足时触发自动扩容,无返回值。

1#include <iostream>
2#include <string>
3using namespace std;
4
5int main() {
6    string str = "hello";
7    // 核心用法:尾部追加单个字符
8    str.push_back(' ');
9    str.push_back('w');
10    cout << "基础追加后:" << str << endl; // 输出 hello w
11
12    // 典型场景:循环追加字符
13    for (char c = 'o'; c <= 'd'; c++) {
14        str.push_back(c);
15    }
16    cout << "循环追加后:" << str << endl; // 输出 hello world
17    return 0;
18}
19
3. 开发中的注意事项
  • 仅支持追加单个 char 字符,传入字符串、多个字符会直接编译报错;
  • 频繁调用会触发多次自动扩容,有性能损耗,提前知晓总长度建议先用reserve()预分配内存;
  • 追加触发扩容后,原字符串的迭代器、指针、引用会全部失效,不可继续使用。

二、append 函数
1. 函数应用场景

批量字符 / 字符串的尾部追加,比 push_back 效率更高,支持多类型入参的灵活拼接,适用于整串追加、指定区间片段追加、批量重复字符追加等多字符拼接场景。

2. 函数的功能与代码实现

核心功能:在 string 尾部追加指定的字符序列,提供多版本重载,支持 string 对象、C 风格字符串、指定个数的重复字符、字符串区间片段等入参形式,返回字符串自身的引用,支持链式调用。

1#include <iostream>
2#include <string>
3using namespace std;
4
5int main() {
6    string str = "hello";
7    // 1. 追加完整string对象
8    str.append(" world");
9    cout << "追加整串:" << str << endl; // 输出 hello world
10
11    // 2. 追加n个相同字符
12    str.append(3, '!');
13    cout << "追加重复字符:" << str << endl; // 输出 hello world!!!
14
15    // 3. 追加字符串的指定区间
16    string temp = "abcdef";
17    str.append(temp, 1, 3); // 从temp下标1开始,截取3个字符追加
18    cout << "追加区间片段:" << str << endl; // 输出 hello world!!!bcd
19    return 0;
20}
21
3. 开发中的注意事项
  • 注意重载参数顺序:append(n, c)是追加 n 个 c 字符,不要和append(const char*, n)的参数顺序搞反;
  • 追加 C 风格字符串时,必须保证字符串以\0结尾,否则会触发内存越界读取;
  • 返回自身引用,支持链式调用,如str.append("a").append("b")
  • 大段内容频繁追加,建议先通过reserve()预分配内存,避免多次扩容。

三、operator+= 运算符重载(重点)
1. 函数应用场景

日常开发中首选的字符串追加方式,语法极简、可读性极强,底层封装 append 逻辑,支持追加 string 对象、C 风格字符串、单个字符,适用于绝大多数无特殊区间要求的字符串拼接场景。

2. 函数的功能与代码实现

核心功能:重载 += 运算符,在 string 尾部追加指定的字符 / 字符串,兼容多种入参类型,返回字符串自身的引用,支持链式拼接,是 string 类最常用的语法糖。

1#include <iostream>
2#include <string>
3using namespace std;
4
5int main() {
6    string str = "hello";
7    // 1. 追加C风格字符串
8    str += " world";
9    cout << "追加字符串:" << str << endl; // 输出 hello world
10
11    // 2. 追加单个字符
12    str += '!';
13    cout << "追加字符:" << str << endl; // 输出 hello world!
14
15    // 3. 追加string对象
16    string add_str = " 123456";
17    str += add_str;
18    cout << "追加string对象:" << str << endl; // 输出 hello world! 123456
19
20    // 4. 链式调用
21    string s;
22    s += "a" += "b" += "c";
23    cout << "链式拼接:" << s << endl; // 输出 abc
24    return 0;
25}
26
3. 开发中的注意事项
  • 日常开发优先使用,仅特殊区间 / 批量字符场景用 append,无性能差异,可读性更强;
  • 拼接触发扩容后,原字符串的迭代器、引用会失效,不可继续使用;
  • 多段长字符串拼接,建议先reserve()预分配内存,避免多次扩容带来的性能损耗。

四、c_str () 函数(重点)
1. 函数应用场景

C++ string 与 C 语言接口的兼容桥梁,用于将 C++ string 对象转换为 C 风格的const char*字符串,适配所有接收 C 风格字符串的系统函数、第三方库、C 标准库接口,是跨语言 / 接口兼容的核心函数。

2. 函数的功能与代码实现

核心功能:返回一个指向const char类型的只读指针,指向 string 底层存储的、以\0结尾的 C 风格字符串,指针内容与 string 当前内容完全一致,生命周期与 string 对象强绑定。

1#include <iostream>
2#include <string>
3#include <cstdio>
4#include <cstring>
5using namespace std;
6
7int main() {
8    string str = "hello c_str";
9    // 1. 适配C标准库printf函数
10    printf("C风格输出:%s\n", str.c_str()); // 输出 hello c_str
11
12    // 2. 适配C语言字符串函数
13    int len = strlen(str.c_str());
14    cout << "strlen获取长度:" << len << endl; // 输出 11
15
16    // 3. 适配系统接口(如文件操作)
17    string file_path = "./test.txt";
18    FILE* fp = fopen(file_path.c_str(), "r");
19    if (fp) {
20        cout << "文件打开成功" << endl;
21        fclose(fp);
22    }
23    return 0;
24}
25
3. 开发中的注意事项
  • 返回的是const char*只读指针,绝对禁止通过该指针修改字符串内容,否则触发未定义行为;
  • 指针生命周期与 string 绑定,string 对象销毁、内容修改(追加、赋值、清空)后,原指针会立即失效,不可继续使用;
  • 不可长期保存返回的指针,仅用于临时调用 C 接口,避免指针失效后非法访问;
  • 返回的字符串保证以\0结尾,这是 C++11 前与data()函数的核心区别。

五、find () + npos 常量(重点)
1. 函数应用场景

字符串正向查找匹配的核心函数,用于查找指定字符 / 子串在目标字符串中第一次出现的位置,适用于关键词匹配、子串存在性判断、字符定位、字符串分割等高频业务场景。

2. 函数的功能与代码实现

核心功能

  • find():从指定 pos 位置(默认 0,字符串开头)开始,正向查找指定的字符 / 字符串,找到则返回第一次匹配的起始下标(size_t无符号类型);找不到则返回string::npos
  • string::npos:string 类内置的静态常量,值为 - 1(转为size_t是无符号最大值),专门用于标记查找失败的场景。
1#include <iostream>
2#include <string>
3using namespace std;
4
5int main() {
6    string str = "hello world, hello C++";
7    // 1. 查找子串第一次出现的位置
8    size_t pos1 = str.find("hello");
9    if (pos1 != string::npos) {
10        cout << "hello首次出现下标:" << pos1 << endl; // 输出 0
11    }
12
13    // 2. 从指定位置开始查找
14    size_t pos2 = str.find("hello", 5);
15    if (pos2 != string::npos) {
16        cout << "下标5后hello首次出现:" << pos2 << endl; // 输出 13
17    }
18
19    // 3. 查找单个字符
20    size_t pos3 = str.find('w');
21    if (pos3 != string::npos) {
22        cout << 'w' << "出现下标:" << pos3 << endl; // 输出 6
23    }
24
25    // 4. 查找失败判断
26    size_t pos4 = str.find("java");
27    if (pos4 == string::npos) {
28        cout << "未找到java子串" << endl;
29    }
30    return 0;
31}
32
3. 开发中的注意事项
  • 必须通过== string::npos/!= string::npos判断查找结果,禁止直接和 - 1 比较,避免无符号类型转换导致的逻辑错误;
  • 如需查找所有匹配项,可循环从「上次找到的位置 + 1」开始继续查找;
  • 查找大小写敏感,"Hello""hello"会被判定为不匹配,不区分大小写需自行处理;
  • 查找空串时,直接返回 pos 起始位置(默认 0)。

六、rfind () 函数
1. 函数应用场景

字符串反向查找匹配,从字符串尾部向前查找,用于获取指定字符 / 子串最后一次出现的位置,适用于文件后缀名提取、路径最后一级目录提取、最后一次关键词定位等反向匹配场景。

2. 函数的功能与代码实现

核心功能:从指定 pos 位置(默认string::npos,字符串最后一个字符)开始,从后往前反向查找指定的字符 / 字符串,找到则返回最后一次匹配的起始下标;找不到则返回string::npos

1#include <iostream>
2#include <string>
3using namespace std;
4
5int main() {
6    string str = "hello world, hello C++";
7    // 1. 查找子串最后一次出现的位置
8    size_t pos1 = str.rfind("hello");
9    if (pos1 != string::npos) {
10        cout << "hello最后一次出现下标:" << pos1 << endl; // 输出 13
11    }
12
13    // 2. 从指定位置向前查找
14    size_t pos2 = str.rfind("hello", 10);
15    if (pos2 != string::npos) {
16        cout << "下标10前hello最后一次出现:" << pos2 << endl; // 输出 0
17    }
18
19    // 3. 实用场景:提取文件后缀名
20    string file_name = "test.tar.gz";
21    size_t dot_pos = file_name.rfind('.');
22    if (dot_pos != string::npos) {
23        string suffix = file_name.substr(dot_pos + 1);
24        cout << "文件后缀:" << suffix << endl; // 输出 gz
25    }
26    return 0;
27}
28
3. 开发中的注意事项
  • 查找方向为从后往前,返回的是匹配内容的起始下标,不是结束下标,与 find () 返回值规则一致;
  • 同样必须用string::npos判断查找是否失败,禁止直接和 - 1 比较;
  • 当 pos 参数超过字符串长度时,默认从整个字符串的末尾开始查找;
  • 反向查找匹配的是完整子串,不是反向子串,如rfind("ab")查找的是"ab"最后一次出现的位置,不是"ba"

七、substr () 函数
1. 函数应用场景

字符串子串截取,从原字符串中提取指定区间的片段,适用于字符串分割、内容提取、固定长度截取、关键词提取等场景,常和 find ()/rfind () 配合完成复杂字符串处理。

2. 函数的功能与代码实现

核心功能:从原字符串的 pos 下标(默认 0)开始,截取长度为 n 的子串,返回一个全新的 string 对象;若 n 不指定、或 n 超过剩余字符长度,会自动截取到字符串末尾。

函数原型:string substr(size_t pos = 0, size_t n = npos) const;

1#include <iostream>
2#include <string>
3using namespace std;
4
5int main() {
6    string str = "hello world";
7    // 1. 从指定位置截取到末尾
8    string sub1 = str.substr(6);
9    cout << "下标6截取到末尾:" << sub1 << endl; // 输出 world
10
11    // 2. 截取指定位置、指定长度的子串
12    string sub2 = str.substr(0, 5);
13    cout << "0开始截取5个字符:" << sub2 << endl; // 输出 hello
14
15    // 3. 配合find提取区间内容
16    string info = "name:zhangsan;age:20";
17    size_t name_start = info.find(':') + 1;
18    size_t name_end = info.find(';');
19    string name = info.substr(name_start, name_end - name_start);
20    cout << "提取姓名:" << name << endl; // 输出 zhangsan
21    return 0;
22}
23
3. 开发中的注意事项
  • 起始 pos 必须在合法范围0 ≤ pos < str.size(),越界会直接抛出out_of_range异常;
  • 截取长度 n 超过剩余字符数时,不会报错,会自动截取到字符串末尾,无越界风险;
  • substr () 返回的是新的 string 对象,原字符串不会被修改;
  • 频繁截取长字符串会产生大量临时对象,有性能损耗,大段内容处理建议用迭代器区间操作。
5. string类非成员函数

在这里插入图片描述

上面的几个接口大家了解一下,下面的OJ题目中会有一些体现他们的使用。string类中还有 一些其他的操作,这里不一一列举,大家在需要用到时不明白了查文档即可。

一、operator+ 加法运算符重载
1. 函数应用场景

仅用于简单、少量字符串的临时拼接,生成全新字符串对象,不修改原字符串,适合一次性短串拼接场景,不推荐高频、长串拼接使用。

2. 函数的功能与代码实现

核心功能:string 类非成员函数,重载 + 运算符,实现两个可转为 string 的对象(string 对象、C 风格字符串、单个字符)的拼接,返回一个全新的 string 深拷贝对象,原左右操作数均不被修改,支持多类型组合的链式拼接。

1#include <iostream>
2#include <string>
3using namespace std;
4
5int main() {
6    string str1 = "hello";
7    string str2 = " world";
8    // 1. 两个string对象拼接
9    string res1 = str1 + str2;
10    cout << "两个string拼接:" << res1 << endl; // 输出 hello world
11
12    // 2. string与C风格字符串拼接
13    string res2 = str1 + " C++";
14    cout << "string+字面量:" << res2 << endl; // 输出 hello C++
15
16    // 3. 链式多段拼接
17    string res3 = str1 + " " + "world" + "!";
18    cout << "链式拼接:" << res3 << endl; // 输出 hello world!
19    return 0;
20}
21
3. 开发中的注意事项
  • 核心性能问题:传值返回会触发深拷贝,每次拼接都会生成临时对象,高频 / 长串拼接性能极低,优先使用 operator+=、append 替代;
  • 语法限制:拼接的两个操作数中,至少有一个必须是 string 对象,直接两个 C 风格字符串字面量用 + 拼接会编译报错(如 “hello”+“world” 非法);
  • 不会修改原操作数,所有修改仅体现在返回的新对象中,原字符串内容保持不变。

二、operator>> 输入运算符重载(重点)
1. 函数应用场景

从标准输入流(cin)读取字符串到 string 对象,适用于读取无空格、无换行的单个单词 / 短字符串,是简单无空格输入场景的首选方式。

2. 函数的功能与代码实现

核心功能:string 类非成员函数,重载 >> 运算符,从输入流中自动跳过开头的空白字符(空格、换行、制表符),直到遇到下一个空白字符停止读取,将读取内容赋值给 string 对象,自动扩容适配内容长度,返回输入流引用,支持链式输入。

1#include <iostream>
2#include <string>
3using namespace std;
4
5int main() {
6    string name;
7    string age;
8    // 1. 基础单值输入
9    cout << "请输入你的姓名:";
10    cin >> name;
11    cout << "你输入的姓名:" << name << endl;
12
13    // 2. 链式多值输入,空格分隔
14    cout << "请输入姓名和年龄,空格分隔:";
15    cin >> name >> age;
16    cout << "姓名:" << name << ",年龄:" << age << endl;
17    return 0;
18}
19
3. 开发中的注意事项
  • 核心限制:遇到空格、换行、制表符立即停止读取,无法读取包含空格的完整句子 / 带空格字符串,该场景必须使用 getline;
  • 自动跳过输入开头的所有空白字符,正常场景下不会读取到空串;
  • 输入流异常(如 EOF)时,会停止读取,string 对象保持原有内容不变;
  • 自动扩容适配输入内容,无需手动处理内存,安全性远高于 C 语言字符数组。

三、operator<< 输出运算符重载(重点)
1. 函数应用场景

将 string 对象内容输出到标准输出流(cout),是 string 内容控制台打印、流输出的核心方式,适用于所有需要将字符串内容输出到流的场景。

2. 函数的功能与代码实现

核心功能:string 类非成员函数,重载 << 运算符,将 string 对象的有效字符完整输出到输出流,无额外内容输出,返回输出流引用,支持链式输出,可与其他类型输出组合使用。

1#include <iostream>
2#include <string>
3using namespace std;
4
5int main() {
6    string str = "hello C++ string";
7    // 1. 基础字符串输出
8    cout << "字符串内容:" << str << endl;
9
10    // 2. 链式多类型组合输出
11    string name = "zhangsan";
12    int age = 20;
13    cout << "姓名:" << name << ",年龄:" << age << endl;
14
15    // 3. 空串输出
16    string empty_str;
17    cout << "空串输出:[" << empty_str << "]" << endl;
18    return 0;
19}
20
3. 开发中的注意事项
  • 仅输出 string 的有效字符(size 长度内的内容),不会输出底层 \0 结束符,也不会自动换行,换行需手动添加 endl 或 ‘\n’;
  • 完全兼容 const string 对象的输出,无使用限制;
  • 支持输出包含空格、特殊字符的完整字符串,无输入运算符的空白限制。

四、getline 函数(重点)
1. 函数应用场景

从输入流中读取一整行包含空格的完整字符串,是读取带空格用户输入、整行文本内容的唯一标准方式,适用于读取完整句子、带空格的地址 / 描述、整行文件内容等场景。

2. 函数的功能与代码实现

核心功能:string 类非成员函数,从指定输入流中读取字符,直到遇到换行符 ‘\n’(或自定义分隔符)为止,换行符会被读取并丢弃,不会存入目标 string 对象;读取内容(含中间空格、制表符)完整赋值给 string 对象,自动扩容适配长度,返回输入流引用。

常用原型:istream& getline(istream& is, string& str, char delim = '\n');

1#include <iostream>
2#include <string>
3using namespace std;
4
5int main() {
6    string address;
7    string content;
8    // 1. 基础用法:读取一整行,直到换行
9    cout << "请输入你的完整地址(含空格):";
10    cin.ignore(); // 清除之前可能残留的换行符,避免读空
11    getline(cin, address);
12    cout << "你输入的地址:" << address << endl;
13
14    // 2. 自定义分隔符:遇到','停止读取
15    cout << "请输入用逗号分隔的内容:";
16    getline(cin, content, ',');
17    cout << "逗号前的内容:" << content << endl;
18    return 0;
19}
20
3. 开发中的注意事项
  • 核心优势:可读取包含空格的完整行,这是与 cin>> 的核心区别;
  • 高频坑点:若之前用 cin>> 读取过内容,输入流会残留换行符,直接调用 getline 会读取到空串,必须先用cin.ignore()清除残留换行符;
  • 换行符 ‘\n’ 是默认终止符,会被读取并丢弃,不会存入目标 string;
  • 可通过第三个参数自定义终止分隔符,遇到该字符即停止读取;
  • 输入流触发 EOF 时,会停止读取,string 对象保留已读取的内容。

五、relational operators 关系运算符(重点)
1. 函数应用场景

两个字符串的相等性判断、字典序大小比较,适用于字符串内容校验、条件分支判断、字符串排序、字典序匹配等高频业务场景。

2. 函数的功能与代码实现

核心功能:string 类非成员函数,重载==、!=、<、<=、>、>=全套关系运算符,按照 ASCII 码值逐字符字典序比较,支持 string 与 string、string 与 C 风格字符串的直接比较,返回 bool 类型结果。

比较规则:

  1. 逐字符对比 ASCII 码值,第一个不同字符的大小,决定两个字符串的大小;
  2. 若前缀字符全部相同,长度更长的字符串判定为更大;
  3. 仅当长度和所有字符完全一致时,判定为相等(==)。
1#include <iostream>
2#include <string>
3using namespace std;
4
5int main() {
6    string str1 = "abc";
7    string str2 = "abd";
8    string str3 = "abc";
9    string str4 = "abcd";
10
11    // 1. 相等性判断
12    cout << "str1 == str3:" << (str1 == str3) << endl; // 输出 1(true)
13    cout << "str1 != str2:" << (str1 != str2) << endl; // 输出 1(true)
14
15    // 2. 字典序大小比较
16    cout << "str1 < str2:" << (str1 < str2) << endl; // 输出 1,'c' ASCII码 < 'd'
17    cout << "str1 < str4:" << (str1 < str4) << endl; // 输出 1,前缀相同,str4更长
18    cout << "str2 > str1:" << (str2 > str1) << endl; // 输出 1
19
20    // 3. 与C风格字符串直接比较
21    cout << "str1 == \"abc\":" << (str1 == "abc") << endl; // 输出 1
22    return 0;
23}
24
3. 开发中的注意事项
  • 比较大小写敏感,大写字母 ASCII 码小于小写字母(如 ‘A’ < ‘a’),因此 “ABC” < “abc”,不区分大小写比较需先统一转换大小写再对比;
  • 比较的是字符串内容,而非内存地址,两个不同的 string 对象,只要内容完全一致,就会判定为相等,与 C 语言 char * 指针比较完全不同;
  • 空串永远小于任何非空字符串,两个空串判定为相等;
  • 支持 string 对象与 C 风格字符串字面量直接比较,无需手动转换类型。
6.vs和g++下string结构的说明

注意:下述结构是在32位平台下进行验证,32位平台下指针占4个字节。

vs下string的结构 :

​ string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义 string中字符串的存储空间:

​ (1)当字符串长度小于16时,使用内部固定的字符数组来存放

​ (2)当字符串长度大于等于16时,从堆上开辟空间

在这里插入图片描述

这种设计也是有一定道理的,大多数情况下字符串的长度都小于16,那string对象创建 好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。 其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的 容量

最后:还有一个指针做一些其他事情。 故总共占16+4+4+4=28个字节

在这里插入图片描述

g++下string的结构

G++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个 指针,该指针将来指向一块堆空间,内部包含了如下字段:

空间总大小

字符串有效长度

引用计数
在这里插入图片描述

指向堆空间的指针,用来存储字符串。

3.与string相关的oj题练习

1.仅仅反转字母

https://leetcode-cn.com/problems/reverse-only-letters/submissions/

1class Solution {
2public:
3    bool isLetters(char ch)
4        {
5            if(ch >= 'a' && ch <='z')
6            return true;
7            
8            else if(ch >= 'A' && ch <='Z')
9            return true;
10            
11            return false;
12        }
13
14    string reverseOnlyLetters(string s) {
15        
16
17        size_t left = 0, right = s.size() - 1;
18        while(left < right)
19        {
20            while(left < right && !isLetters(s[left]))
21            {
22                left++;
23            }
24
25            while(left < right && !isLetters(s[right]))
26            {
27                right--;
28            }
29            swap(s[left++],s[right--]);
30
31        }
32        return s;
33    }
34};
35

2.找字符串中第一个只出现一次的字符

https://leetcode-cn.com/problems/first-unique-character-in-a-string/

1class Solution {
2public:
3    int firstUniqChar(string s) {
4        int count[250] = {0};
5        for(auto ch : s)
6        count[ch]++;
7        //for(int i=0;i<s.size();i++)
8        // {
9        //     count[s[i]]++;
10        // }
11        
12        for(int i=0; i < s.size(); i++)
13        {
14            if(1==count[s[i]])
15            return i;
16        }
17
18        return -1;
19    }
20};
21

3.字符串里面最后一个单词的长度

字符串最后一个单词的长度_牛客题霸_牛客网

1#include <iostream>
2#include <string>
3using namespace std;
4
5int main() {
6    string str;
7    
8    // 一直读取,直到没有单词可读(遇到 EOF)
9    // 每次读取的新单词都会覆盖掉旧的 str
10    while (cin >> str) {
11        // 循环里面什么都不用做!
12    }
13    
14    // 当循环结束时,str 里保存的就是输入的最后一个单词
15    cout << str.length() << endl;
16    
17    return 0;
18}
19

4.验证一个字符串是否是回文

125. 验证回文串 - 力扣(LeetCode)

1class Solution {
2public:
3    bool isPalindrome(string s) {
4        string clean_s = ""; // 准备一个新的字符串,只装有用的字符
5
6        // 1. 预处理阶段:清洗并收集字符
7        for(int i = 0, n = s.size(); i < n; i++) 
8        {
9            if(s[i] >= 'A' && s[i] <= 'Z') 
10            {
11                clean_s += (s[i] + 32); // 大写转小写,并加入新字符串
12            }
13            else if(s[i] >= 'a' && s[i] <= 'z') 
14            {
15                clean_s += s[i];        // 小写字母直接加入
16            }
17            else if(s[i] >= '0' && s[i] <= '9') 
18            {
19                clean_s += s[i];        // 把遗漏的数字也加上!
20            }
21            // 注意:如果是标点或空格,这里什么都不做。
22            // 这样自然就过滤掉了所有无效字符,新字符串紧凑无比。
23        }
24
25        // 2. 双指针阶段:对洗干净的字符串进行对比
26        // 注意这里要检查 clean_s 是否为空,虽然 size()-1 处理空串有时会溢出,
27        // 但如果 clean_s 为空,下面的 while 根本进不去,直接 return true,逻辑是安全的。
28        int left = 0, right = clean_s.size() - 1;
29        while(left < right) 
30        {
31            if(clean_s[left++] != clean_s[right--]) 
32            {
33                return false;
34            }
35        }
36        
37        return true;
38    }
39};
40

5.字符串相加

415. 字符串相加 - 力扣(LeetCode)

1class Solution {
2public:
3    string addStrings(string num1, string num2) 
4{
5        // 从后往前相加,相加的结果到字符串可以使用insert头插
6        // 或者+=尾插以后再reverse过来
7        int end1 = num1.size()-1;
8        int end2 = num2.size()-1;
9        int value1 = 0, value2 = 0, next = 0;
10        string addret;
11        while(end1 >= 0 || end2 >= 0)
12        {
13            if(end1 >= 0)
14                value1 = num1[end1--]-'0';
15            else
16                value1 = 0;
17            
18            if(end2 >= 0)
19                value2 = num2[end2--]-'0';
20            else
21                value2 = 0;
22            
23            int valueret = value1 + value2 + next;
24            if(valueret > 9)
25            {
26                next = 1;
27                valueret -= 10;
28            }
29            else
30            {
31                next = 0;
32            }
33            
34            //addret.insert(addret.begin(), valueret+'0');
35            addret += (valueret+'0');
36        }
37        
38        if(next == 1)
39        {
40            //addret.insert(addret.begin(), '1');
41            addret += '1';
42        }
43        
44        reverse(addret.begin(), addret.end());
45        return addret;
46    }
47};
48

6.反转字符串II

541. 反转字符串 II - 力扣(LeetCode)

1#include <algorithm>
2#include <string>
3
4using namespace std;
5
6class Solution {
7public:
8    string reverseStr(string s, int k) {
9        int n = s.size();
10        int left = 0; // left 代表当前这块 2k 字符的起始下标
11
12        // 只要左指针还没越界,就继续处理
13        while (left < n) {
14            
15            // 计算当前还剩下多少个字符没处理
16            int remain = n - left;
17
18            // 情况 1:如果剩余字符少于 k 
19            // 题目要求:将剩余字符全部反转
20            if (remain < k) {
21                reverse(s.begin() + left, s.end());
22            } 
23            // 情况 2:剩余字符 >= k 个(不管它是不是小于 2k)
24            // 题目要求:反转前 k 个字符
25            else {
26                reverse(s.begin() + left, s.begin() + left + k);
27            }
28
29            // 处理完当前这一批,left 指针往后跳 2k 步,进入下一个轮回
30            left += 2 * k; 
31        }
32
33        return s;
34    }
35};
36

7.反转字符串中的单词III

557. 反转字符串中的单词 III - 力扣(LeetCode)

1class Solution {
2public:
3    string reverseWords(string s) {
4      int sum = 0,left = 0;
5      int n = s.size();
6      int add = 0;
7      while(left <= n)
8      {
9        if((s[left] == ' ') || left == n)
10        {
11            
12            reverse(s.begin() + add,s.begin() + left );
13
14            add = left + 1;
15        }
16
17        left++;
18      }  
19      return s;
20    }
21};
22

8.字符串相乘

43. 字符串相乘 - 力扣(LeetCode)

1#include <vector>
2#include <string>
3
4using namespace std;
5
6class Solution {
7public:
8    string multiply(string num1, string num2) {
9        // 特殊情况:如果其中一个数是 0,直接返回 "0"
10        if (num1 == "0" || num2 == "0") {
11            return "0";
12        }
13
14        int m = num1.size();
15        int n = num2.size();
16        
17        // 准备一个长度为 m + n 的数组,初始化为 0
18        vector<int> res(m + n, 0);
19
20        // 从后往前,双层循环模拟竖式乘法
21        for (int i = m - 1; i >= 0; i--) {
22            for (int j = n - 1; j >= 0; j--) {
23                // 将字符转为数字并相乘
24                int mul = (num1[i] - '0') * (num2[j] - '0');
25                
26                // 乘积在结果数组中对应的两个位置
27                int p1 = i + j;     // 进位位置 (高位)
28                int p2 = i + j + 1; // 当前位位置 (低位)
29
30                // 累加上低位原本就有的数字
31                int sum = mul + res[p2];
32
33                // 更新当前位和进位
34                res[p2] = sum % 10;       // 取个位留在低位
35                res[p1] += sum / 10;      // 十位进上去 (注意这里是 +=,因为 p1 可能已经有值了)
36            }
37        }
38
39        // 此时 res 数组里存的就是最终结果的每一位数字
40        // 我们把它转回 string,要注意去掉开头的多余的 0
41        string ans = "";
42        for (int i = 0; i < res.size(); i++) {
43            // 如果 ans 是空的,且当前数字是 0,说明这是前导 0,跳过
44            if (ans.empty() && res[i] == 0) {
45                continue;
46            }
47            // 存入真实数字
48            ans += (res[i] + '0');
49        }
50
51        return ans;
52    }
53};
54

4.string类的模拟实现

4.1经典的string类问题

上面已经对string类进行了简单的介绍,大家只要能够正常使用即可。在面试中,面试官总喜欢让 学生自己来模拟实现string类,最主要是实现string类的构造、拷贝构造、赋值运算符重载以及析 构函数。大家看下以下string类的实现是否有问题?

1// 为了和标准库区分,此处使用String
2class String
3{
4public:
5    /*String()
6        :_str(new char[1])
7        {*_str = '\0';}
8    */
9    //String(const char* str = "\0") 错误示范
10    //String(const char* str = nullptr) 错误示范
11    String(const char* str = "")
12    {
13        // 构造String类对象时,如果传递nullptr指针,可以认为程序非
14        if (nullptr == str)
15        {
16            assert(false);
17            return;
18        }
19        _str = new char[strlen(str) + 1];
20        strcpy(_str, str);
21    }
22    ~String()
23    {
24        if (_str)
25        {
26            delete[] _str;
27            _str = nullptr;
28        }
29    }
30private:
31    char* _str;
32};
33// 测试
34void TestString()
35{
36    String s1("hello bit!!!");
37    String s2(s1);
38}
39
40

在这里插入图片描述

说明:上述String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认 的,当用s1构造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内 存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝。

4.2浅拷贝

浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致 多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该 资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。

就像一个家庭中有两个孩子,但父母只买了一份玩具,两个孩子愿意一块玩,则万事大吉,万一 不想分享就你争我夺,玩具损坏。

在这里插入图片描述

可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享。父 母给每个孩子都买一份玩具,各自玩各自的就不会有问题了。

在这里插入图片描述

4.3深拷贝

如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给 出。一般情况都是按照深拷贝方式提供。

在这里插入图片描述

4.3.1传统版写法的String类
1class String
2{
3public:
4String(const char* str = "")
5{
6// 构造String类对象时,如果传递nullptr指针,可以认为程序非
7if (nullptr == str)
8{
9assert(false);
10return;
11}
12_str = new char[strlen(str) + 1];
13strcpy(_str, str);
14}
15String(const String& s)
16: _str(new char[strlen(s._str) + 1])
17{
18strcpy(_str, s._str);
19}
20String& operator=(const String& s)
21{
22if (this != &s)
23{
24char* pStr = new char[strlen(s._str) + 1];
25strcpy(pStr, s._str);
26delete[] _str;
27_str = pStr;
28}
29return *this;
30}
31~String()
32{
33if (_str)
34{
35
36 delete[] _str;
37         _str = nullptr;
38     }
39 }
40private:
41 char* _str;
42};
43
4.3.2现代版写法的String类
1class String
2{
3public:
4 String(const char* str = "")
5 {
6     if (nullptr == str)
7     {
8         assert(false);
9         return;
10     }
11     _str = new char[strlen(str) + 1];
12     strcpy(_str, str);
13 }
14 String(const String& s)
15     : _str(nullptr)
16 {
17     String strTmp(s._str);
18     swap(_str, strTmp._str);
19 }
20 // 对比下和上面的赋值那个实现比较好?
21 String& operator=(String s)
22 {
23     swap(_str, s._str);
24     return *this;
25 }
26 /*
27 String& operator=(const String& s)
28 {
29     if(this != &s)
30     {
31        String strTmp(s);
32        swap(_str, strTmp._str);
33     }
34     return *this;
35 }
36 */
37 ~String()
38 {
39     if (_str)
40     {
41
42delete[] _str;
43_str = nullptr;
44}
45}
46private:
47char* _str;
48};
49

4.4 写时拷贝

在这里插入图片描述

写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。 引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该 资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源, 如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有 其他对象在使用该资源。

写时拷贝:

C++ STL string的Copy-On-Write技术 | 酷 壳 - CoolShell

写时拷贝在读取时的缺陷:

C++的std::string的“读时也拷贝”技术! | 酷 壳 - CoolShell

5.扩展阅读

C++面试中string类的一种正确写法 | 酷 壳 - CoolShell

STL 的string类怎么啦?_string类在stl里面吗-CSDN博客

看完是不是感觉有所收获呢?如果学有所获的话,麻烦给个三连支持一下呗。感谢观看,您的支持,将是我前进路上的重要动力。


c++从入门到跑路——string类》 是转载文章,点击查看原文


相关推荐


火爆全网的Seedance2.0 十万人排队,我2分钟就用上了
AI袋鼠帝2026/4/6

大家好,我是袋鼠帝。 之前我在B站看到一位AI视频创作者分享他的工作流。不可否认,那套流程做出来的视频确实很专业,画面精美,运镜流畅。但是,看完我只觉得头皮发麻。 原文档找不到了,我记得他先是用Gemini写剧本,接着用NanoBanana跑画面,然后再去另外的配音平台搞音频,中间穿插着使用ComfyUI来控制视频、图片生成。 ComfyUI这玩意儿我以前也折腾过几次,连线复杂就算了,每个节点的各种配置参数直接给我整懵逼了,我感觉比当初学敲代码还难,后面就再也没碰过了。 然后整个流程的最后一步,


Vue项目打包为WAR文件部署Tomcat完整指南
蒙眼过河2026/3/28

Vue项目打包为WAR文件部署Tomcat完整指南 前言 在Vue项目开发完成后,通常我们会将打包后的静态文件部署到Nginx等静态服务器上。但在某些企业环境中,我们需要将Vue项目部署到Tomcat这样的Java应用服务器中。本文将详细介绍如何将Vue项目的打包文件转换为标准的WAR包,以便部署到Tomcat服务器。 为什么需要将Vue打包为WAR包? 企业规范要求:很多企业使用统一的Tomcat应用服务器集群统一管理:便于与后端Java应用统一部署和管理历史遗留系统:部分老系统架构需


Django 基础入门教程(第四篇):Form组件、Auth认证、Cookie/Session与中间件
冉成未来2026/3/20

在前三篇中,我们完成了 Django 的环境搭建、模型设计、视图模板、Admin 后台以及 ORM 高级查询。本篇将带你深入 Django 的用户交互与安全机制:Form 组件、Auth 认证系统、Cookie/Session 和中间件。学完本篇,你将能够处理复杂的表单验证、实现用户注册登录、管理用户会话,并理解 Django 的请求/响应处理流程。 第一部分:Django Form 组件 1.1 为什么需要 Form 组件? 在 Web 开发中,处理表单是常见且复杂的任务。你需要:


AI时代的数据对比:DBA还需要盯着屏幕看差异吗?
NineData2026/3/12

当 AI 已经能写 SQL、辅助诊断、生成代码时,很多企业的数据对比却还停留在相对原始的阶段:任务跑完,DBA 需要面对动辄上百张表的差异报告,逐行核对的工作量极大。 这种场景在迁移、同步、数据备份演练里并不少见,到了国产化迁移场景下更是被进一步放大。数据库从 Oracle 迁到达梦、从 MySQL 迁到人大金仓,变化的不只是运行环境,更是数据库内核、数据类型、字符集规则和兼容语义。DBA 担心的往往不是任务失败,而是任务看起来已经完成,业务流量切换之后才发现数据并不一致。 AI 时代的数据对


ubuntu应用深度守护
字节逆旅2026/3/4

二、 定位分析:抽丝剥茧 1. 系统日志中的“启动死循环” 输入sudo grep "linux-myApp" /var/log/syslog调取 syslog 发现,系统曾多次尝试自动拉起应用,但均告失败。 报错核心:Exec binary ... does not exist: No such file or directory。 结论:系统预设的自动启动路径与实际安装路径不匹配,导致应用在服务器重启后无法“回家”。 2. 定位原因 上面的日志内容意味着我的应用可能已经被卸载、被移动了位


【C++】整数类型(Integer Types)避雷指南与正确使用姿势
PAK向日葵2026/2/24

背景 C++继承自C语言。作为一门以零开销抽象为主要特征的底层语言,不同于Python或JavaScript等高抽象层次的语言,C++拥有一套较为完整、但又包含有一定历史包袱的内建整数类型。 在实际开发中,如果对C++内建整数类型的机制不熟悉,或者不遵循一定的使用规范,则非常容易引入难以排查和调试的Bug。因此学习了解C++中内建整数类型的特性,以及一套行之有效的使用规范,是非常有必要的。 内建整数类型的坑 or 历史包袱 C++ 标准没有规定具体位数 虽然在实际实践中,我们知道在x64平台,对


百度 APP 正式接入 OpenClaw,所有人限时免费!
苍何2026/2/15

这是苍何的第 495 篇原创! 大家好,我是苍何。 最近被 OpenClaw 刷屏了吧? 3 周时间 GitHub Star 干到 19 万,比当年 DeepSeek 还猛。 我也发了好几篇文章了,然后还开源了个知识库,你别说,还挺多人用的。 基本上接入 QQ、微信、飞书、discord 等都写的比较全了。 但是说实话,OpenClaw 的部署使用过程并不算丝滑。 买服务器、配环境、装依赖,光是部署就需要折腾大半天。 好不容易跑起来了,还得通过 Telegram 来发指令。 就,怎么说呢,能用


主流模型对比-02
一诺滚雪球2026/2/6

前言 GPT-4、Claude、Llama、Qwen、DeepSeek... 面对层出不穷的大语言模型,你是否也曾感到迷茫? 选贵的 GPT-4,还是用免费的开源模型? 中文场景应该用什么模型? 本地部署和云端 API 各有什么优劣? 性价比最高的选择是什么? 选对模型,不仅能节省成本,还能获得更好的效果。今天我们来聊聊如何做出明智的选择。 1. 什么是模型选型 1.1 闭源模型 vs 开源模型 特点闭源模型开


langchain学习笔记(二):工具的调用
Shawn_Shawn2026/1/29

Tool Calling 定义简单tool 创建工具最简单的方法是使用 @tool 装饰器。 @tool(description="Returns the current time in yyyy-MM-dd HH:mm:ss format.") def get_current_time(*args, **kwargs) -> str: """ 获取当前系统时间。 格式为:yyyy-MM-dd HH:mm:ss """ now = datetime.dat


2025.12.17华为软开
ゞ 正在缓冲99%…2026/1/19

细胞增殖 import java.util.HashMap; import java.util.Map; import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); long n = scanner.nextLong();//n个观测值

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 XYZ博客