阅读前置:本文适配C++11及以上所有标准,覆盖面试高频考点、工程实战用法、底层核心机制,告别只会简单拼接字符串的入门误区,全面吃透std::string。
核心定位:std::string是C++ STL中最常用的字符容器,完全兼容C风格字符串,自动管理内存,不仅支持基础字符串操作,还具备移动语义、内存复用、高效查找、编码处理等进阶特性,是后端、算法、客户端开发的必备核心知识点。
一、String类全面概述
1.1 什么是std::string?
std::string 是 C++ 标准模板库(STL)提供的字符串类 ,专门用于处理字符序列,本质是一个封装了字符数组、动态内存、增删查改方法的容器,定义在**<string>**头文件中。
它是对 C 语言原生 char* 字符字符串的现代化、安全封装,彻底解决了C风格字符串内存不安全、操作繁琐、容易越界、无法直接赋值比较的痛点。
1.2 string的核心作用与优势
相比于C语言 char[] / char* 字符串,std::string 拥有绝对的工程优势,也是C++开发强制推荐使用字符串类型:
-
自动内存管理:无需手动malloc/free,自动扩容、自动释放内存,杜绝内存泄漏
-
操作极简:支持直接赋值、加减拼接、大小比较,无需手写工具函数
-
安全性高:提供越界检查、有效长度统计,极大减少野指针、内存越界bug
-
功能丰富:原生支持查找、截取、删除、替换、遍历、类型转换等全套操作
-
兼容C语言 :通过
c_str()可无缝对接所有C风格接口 -
支持现代C++特性:支持移动语义、右值引用、内存预分配,性能极致优化
1.3 string适用场景
几乎覆盖C++所有开发场景,是通用字符串处理首选:
-
算法竞赛:字符串模拟、字符串匹配、子串处理、字符统计
-
后端服务开发:报文解析、参数拼接、日志格式化、URL处理
-
客户端开发:界面文本展示、文件路径处理、配置读取
-
文件与IO操作:读取文本内容、拼接文件路径、解析文本数据
-
数据类型转换:数字与字符串互转、格式化数据输出
-
高频动态字符串拼接:支持内存预分配,高效处理动态变长字符串
1.4 不适用场景
-
超高频、超极致性能的底层内核开发(少量场景用原生char*)
-
固定长度、永不变化的静态字符串(可直接用字符串常量)
二、String类基础核心
2.1 必备头文件与命名空间
string不属于基本数据类型,是STL封装的类,必须手动引入头文件,且位于std命名空间中。
cpp
#include <iostream>
#include <string> // 必须引入,cin/cout不包含string
using namespace std; // 工程简易写法,大型项目建议用std::
2.2 五种初始化方式
日常开发常用五种构造方式,其中重复字符构造、拷贝构造为高频用法,禁止直接用字符数组赋值非常规格式。
cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
// 1. 空字符串初始化
string s1;
// 2. 字符串常量初始化
string s2 = "Hello C++";
// 3. 拷贝构造
string s3(s2);
// 4. 指定个数重复字符初始化
string s4(6, 'a');
// 5. 截取C风格字符串前n个字符初始化
string s5("123456", 3);
cout << "s1: [" << s1 << "]" << endl; // 输出:s1: []
cout << "s2: " << s2 << endl; // 输出:s2: Hello C++
cout << "s3: " << s3 << endl; // 输出:s3: Hello C++
cout << "s4: " << s4 << endl; // 输出:s4: aaaaaa
cout << "s5: " << s5 << endl; // 输出:s5: 123
return 0;
}
2.3 基础属性函数(长度/判空/清空)
基础三剑客:size、length、empty、clear,其中size和length完全等价,统一推荐使用size(兼容STL容器规范)。
cpp
int main() {
string s = "String Study";
// 获取字符长度,二者完全一致
cout << "size(): " << s.size() << endl; // 输出:size(): 12
cout << "length(): " << s.length() << endl; // 输出:length(): 12
// 判空:空返回true,非空返回false
if (!s.empty()) {
cout << "字符串非空" << endl; // 输出:字符串非空
}
// 清空字符串
s.clear();
cout << "清空后长度:" << s.size() << endl; // 输出:清空后长度:0
return 0;
}
2.4 字符访问([]与at的核心区别)
两种访问方式核心差异:[]不做越界检查,效率高;at()严格越界检查,抛出异常,安全性高。
cpp
int main() {
string s = "ABCDE";
// 正常访问
cout << "[]访问:" << s[0] << endl; // 输出:[]访问:A
cout << "at()访问:" << s.at(1) << endl; // 输出:at()访问:B
// 修改字符
s[2] = 'X';
cout << "修改后:" << s << endl; // 输出:修改后:ABXDE
// 越界测试(重点)
// s[100]; // 无报错,非法内存访问,程序隐患
// s.at(100); // 直接抛出out_of_range异常,程序终止
return 0;
}
三、String常用核心函数
3.1 拼接函数:operator+ / append(多场景用法)
operator+ 适合简单拼接,append支持精准拼接(截取子串、重复字符拼接),功能更强大。
cpp
int main() {
string s1 = "Hello ";
string s2 = "C++";
// 方式1:运算符拼接(最常用)
string res1 = s1 + s2;
cout << res1 << endl; // 输出:Hello C++
// 方式2:append拼接完整字符串
s1.append(s2);
// 方式3:append截取子串拼接(拼接s2前2个字符)
string s3 = "123456";
s1.append(s3, 0, 2);
// 方式4:拼接多个相同字符
s1.append(3, '!');
cout << s1 << endl; // 输出:Hello C++12!!!
return 0;
}
3.2 比较函数:运算符比较 / compare
string支持字典序直接比较(>、<、==、!=),compare(同C语言中的strcmp)适合批量判断,返回值规则:相等返回0、小于返回负数、大于返回正数。
cpp
int main() {
string a = "apple";
string b = "banana";
// 运算符比较(直观)
if (a < b) cout << "apple 字典序更小" << endl; // 输出:apple 字典序更小
// compare比较(同C语言中的strcmp)
cout << a.compare(b) << endl; // 输出:-1
cout << b.compare(a) << endl; // 输出:1
cout << a.compare("apple") << endl; // 输出:0
return 0;
}
3.3 查找函数:find / rfind / find_first_of
基础find:从左往右查找;rfind:从右往左查找;未找到返回string::npos(核心判断条件,无符号整型)。
cpp
int main() {
string s = "C++ Java C++ Python";
// 1. find:正向查找第一个匹配位置
size_t pos1 = s.find("C++");
if (pos1 != string::npos) {
cout << "正向第一个C++位置:" << pos1 << endl; // 输出:正向第一个C++位置:0
}
// 2. rfind:反向查找最后一个匹配位置
size_t pos2 = s.rfind("C++");
if (pos2 != string::npos) {
cout << "反向最后一个C++位置:" << pos2 << endl; // 输出:反向最后一个C++位置:9
}
// 3. find_first_of:查找任意匹配字符(匹配字符集)
size_t pos3 = s.find_first_of("JP");
cout << "首次匹配J/P的位置:" << pos3 << endl; // 输出:首次匹配J/P的位置:4
return 0;
}
3.4 截取、删除、替换:substr / erase / replace
三大修改神器,支撑绝大多数字符串处理场景,参数格式:起始下标+操作长度。
cpp
int main() {
string s = "123456789";
// 1. substr截取:pos(起始), len(长度,默认到末尾)
string sub1 = s.substr(2, 4);
string sub2 = s.substr(5);
cout << "截取子串1:" << sub1 << endl; // 输出:截取子串1:3456
cout << "截取子串2:" << sub2 << endl; // 输出:截取子串2:6789
// 2. erase删除:从pos删除len个字符
s.erase(3, 3);
cout << "删除后:" << s << endl; // 输出:删除后:123789
// 3. replace替换:pos+len+新字符串
s.replace(0, 2, "ABC");
cout << "替换后:" << s << endl; // 输出:替换后:ABC3789
return 0;
}
3.5 类型转换函数
C++11新增标准转换函数,替代传统sscanf/sprintf,安全高效。
cpp
int main() {
// 字符串转数字
string num_str = "12345";
int int_num = stoi(num_str);
long long ll_num = stoll(num_str);
double dou_num = stod("3.1415");
// 数字转字符串
string s1 = to_string(int_num);
string s2 = to_string(dou_num);
cout << "int转字符串:" << s1 << endl; // 输出:int转字符串:12345
cout << "double转字符串:" << s2 << endl; // 输出:double转字符串:3.141500
return 0;
}
传统的sscanf与sprintf函数
cpp
//sprintf函数:将数字转为字符串
int main() {
char buf[100];
// 整数转字符串
sprintf(buf, "%d", 123);
cout << buf << endl; // 输出:123
// 浮点数转字符串
sprintf(buf, "%.2f", 3.14159);
cout << buf << endl; // 输出:3.14
// 格式化拼接
sprintf(buf, "姓名:%s,年龄:%d", "小明", 20);
cout << buf << endl; // 输出:姓名:小明,年龄:20
return 0;
}
//sscanf函数:将字符串转为数字
int main() {
const char* str = "123 45.67";
int a;
double b;
// 解析字符串
sscanf(str, "%d %lf", &a, &b);
cout << a << endl; // 输出:123
cout << b << endl; // 输出:45.67
return 0;
}
四、String进阶核心知识点
4.1 容量与内存机制:capacity / reserve / resize
很多开发者混淆size(有效字符数) 和capacity(内存容量):size是实际字符长度,capacity是系统分配的内存空间,不会随字符删除缩小。
-
reserve(n) :预分配内存,只扩容不缩容,提升大量拼接效率(避免频繁扩容)
-
resize(n):修改有效字符长度,扩容补空字符,缩容截断末尾字符
cpp
int main() {
string s;
cout << "初始size:" << s.size() << " capacity:" << s.capacity() << endl;
// 输出:初始size:0 capacity:15(不同编译器略有差异)
// 预分配100字节内存
s.reserve(100);
cout << "reserve后size:" << s.size() << " capacity:" << s.capacity() << endl;
// 输出:reserve后size:0 capacity:100
// 修改有效长度为10,不足补\0
s.resize(10);
cout << "resize后size:" << s.size() << " capacity:" << s.capacity() << endl;
// 输出:resize后size:10 capacity:100
// 清空字符,容量不变(进阶坑点)
s.clear();
cout << "clear后size:" << s.size() << " capacity:" << s.capacity() << endl;
// 输出:clear后size:0 capacity:100
return 0;
}
工程优化技巧:需要大量循环拼接字符串时,提前reserve预分配内存,可减少80%以上的内存扩容开销。
4.2 C++11移动语义与string临时对象优化
C++11引入移动构造/移动赋值,string临时对象不会拷贝内存,而是直接转移内存所有权,大幅提升效率。
cpp
string getStr() {
return "Hello Move Semantic"; // 返回临时对象
}
int main() {
// 移动赋值:无内存拷贝,直接接管临时对象内存
string s = getStr();
cout << s << endl; // 输出:Hello Move Semantic
// std::move强制移动,避免拷贝
string s1 = "Test Move";
string s2 = move(s1);
cout << "s2:" << s2 << endl; // 输出:s2:Test Move
cout << "移动后s1为空:" << s1.empty() << endl; // 输出:移动后s1为空:1
return 0;
}
核心考点:move后的原string对象变为空字符串,不可继续使用!
4.3 string::npos 底层原理与避坑
npos是string类的静态常量,类型为size_t(无符号整型),值为-1(无符号最大值)。
经典坑点:禁止用int接收find返回值,会导致符号异常!必须用size_t接收。
cpp
int main() {
string s = "Test";
// 错误写法:int pos = s.find("abc"); 无符号转有符号,逻辑错误
size_t pos = s.find("abc");
if (pos == string::npos) {
cout << "未找到目标子串" << endl; // 输出:未找到目标子串
}
return 0;
}
4.4 c_str() 与 data() 进阶区别
二者均可返回C风格const char*字符串,C++17之前:data()不保证末尾补\0,c_str()一定补\0;C++17之后二者完全等价。
工程兼容C函数(fopen、printf、socket接口)必须优先使用c_str()。
cpp
int main() {
string s = "C++ String";
const char* c1 = s.c_str();
const char* c2 = s.data();
cout << c1 << endl; // 输出:C++ String
cout << c2 << endl; // 输出:C++ String
return 0;
}
4.5 字符串遍历三种方式
cpp
int main() {
string s = "C++ Study";
// 1. 下标遍历:效率最高,适合随机访问
for (int i = 0; i < s.size(); i++) {
cout << s[i] << " ";
}
cout << endl; // 输出:C + + S t u d y
// 2. 范围for遍历(C++11推荐):代码简洁,只读首选
for (char ch : s) {
cout << ch << " ";
}
cout << endl; // 输出:C + + S t u d y
// 3. 迭代器遍历:兼容所有STL容器,适合通用算法
for (string::iterator it = s.begin(); it != s.end(); ++it) {
cout << *it << " ";
}
// 输出:C + + S t u d y
return 0;
}
4.6 高频面试:string内存扩容机制
string不会按需扩容,而是指数扩容,避免频繁内存申请:
-
Windows(VS):扩容倍数 1.5倍
-
Linux(GCC):扩容倍数 2倍
-
扩容流程:申请新内存 - 拷贝旧数据 - 释放旧内存 - 更新指针
优化方案:已知最终字符串长度时,提前调用reserve()预分配内存,零扩容开销。
五、工程易错坑点总结
5.1 越界访问隐患
\]运算符不检查越界,非法访问会导致内存脏数据、程序崩溃、隐式bug;读写未知下标字符,优先使用at()捕获异常。
#### 5.2 clear只清空字符,不释放内存
clear()仅将size置0,capacity保持不变,大量字符串处理后会造成内存占位;如需释放内存,可使用**`string().swap(s)` 强制收缩内存**,或使用C++11提供的 shrink_to_fit()。
* string() 创建一个**临时空字符串**(容量 = 0)
* .swap(s) 把空字符串和 s 的内部内存指针交换
* 临时对象销毁,带走原来的大内存,s 变成空且容量为 0
```cpp
// 强制释放string多余内存(完整可运行)
#include