在 C++ 编程中,字符串处理是一项基础且频繁的操作。相较于 C 语言中以'\0'结尾的字符数组,C++ 标准库提供的string类为我们带来了更便捷、更安全的字符串处理方式。本文将从string类的基本概念出发,详细介绍其常用接口及实战技巧,帮助你彻底掌握string类的使用。
一、为什么要学习 string 类?
C 语言中,字符串是以'\0'结尾的字符集合,虽然 C 标准库提供了str系列函数(如strlen、strcpy等),但这些函数存在明显缺陷:
- 函数与字符串分离,不符合面向对象(OOP)思想;
- 底层空间需要用户手动管理,容易出现越界访问、内存泄漏等问题。
而string类作为 C++ 标准库的重要组成部分,封装了字符串的存储与操作,无需手动管理内存,且提供了丰富的接口,极大提升了字符串处理的效率与安全性。
二、string 类的基本使用
使用string类前,需包含头文件并引入命名空间:
cpp
#include <string>
using namespace std;
1. string 对象的构造
string类提供了多种构造方式,满足不同场景的需求:
cpp
int main() {
// 1. 默认构造(空字符串)
string s1;
// 2. 用C风格字符串初始化
string s2("hello world");
// 3. 拷贝构造
string s3(s2);
// 4. 从指定位置开始拷贝(下标6到结尾)
string s4(s2, 6); // 结果:world
// 5. 从指定位置开始拷贝指定长度(下标6开始,拷贝3个字符)
string s5(s2, 6, 3); // 结果:wor
// 6. 拷贝长度超过剩余字符时,拷贝到结尾
string s6(s2, 6, 15); // 结果:world
// 7. 取C风格字符串的前n个字符
string s7("hello world", 3); // 结果:hel
// 8. 用n个相同字符初始化
string s8(5, 'x'); // 结果:xxxxx
return 0;
}
2. 字符串的访问与遍历
(1)通过operator[]访问
operator[]返回指定位置字符的引用,支持读写操作,越界访问会触发断言报错:
cpp
int main() {
string s("hello");
s[0] = 'H'; // 修改第一个字符
cout << s[1] << endl; // 输出:e
return 0;
}
(2)通过at访问
at与operator[]功能类似,但越界访问会抛出异常,更适合需要捕获错误的场景:
cpp
try
{
string s("hello");
cout << s.at(10) << endl; // 越界,抛出out_of_range异常
}
catch (const out_of_range& e)
{
cout << "错误:" << e.what() << endl;
}
(3)迭代器遍历
迭代器是容器的通用访问方式,string提供了 4 种迭代器:
| 迭代器类型 | 功能描述 |
|---|---|
iterator |
正向遍历,可读可写 |
const_iterator |
正向遍历,只读 |
reverse_iterator |
反向遍历,可读可写 |
const_reverse_iterator |
反向遍历,只读 |
示例代码:
cpp
string s("hello");
// 正向迭代器(可读可写)
for (string::iterator it = s.begin(); it != s.end(); ++it) {
*it += 1; // 每个字符后移一位(h→i, e→f, 等)
}
// 反向迭代器(只读)
const string cs("world");
for (string::const_reverse_iterator rit = cs.rbegin(); rit != cs.rend(); ++rit) {
cout << *rit << " "; // 输出:d l r o w
}
(4)范围 for 遍历
C++11 引入的范围 for 可简化遍历,底层基于迭代器实现,适合无需下标时使用:
cpp
string s("hello");
// 只读遍历
for (char ch : s) {
cout << ch << " ";
}
// 读写遍历(需加引用)
for (char& ch : s) {
ch = toupper(ch); // 转为大写
}
3. 字符串的长度与容量
string类提供了多个接口用于管理长度与容量:
size():返回字符串中字符的个数(推荐使用);length():与size()功能相同,仅为兼容历史代码;capacity():返回当前已分配的内存可容纳的最大字符数(不含'\0');max_size():返回字符串可容纳的最大字符数(受系统限制)。
示例:
cpp
string s("hello");
cout << s.size() << endl; // 5
cout << s.capacity() << endl; // 通常大于等于5(取决于编译器)
4. 字符串的修改与操作
(1)插入操作
insert可在指定位置插入字符或字符串(谨慎使用,频繁头插效率低):
cpp
string s("hello");
s.insert(1, "as"); // 在下标1前插入"as" → hasello
s.insert(1, 2, 'c'); // 在下标1前插入2个'c' → hccasello
(2)追加操作
优先使用以下高效的尾插方式:
push_back(char c):尾插单个字符;operator+=:追加字符或字符串(推荐);append(const string& str):追加字符串。
示例:
cpp
string s("hello");
s.push_back(' '); // hello
s += "world"; // hello world
s.append("!"); // hello world!
(3)删除操作
erase可删除指定范围的字符,pop_back()用于尾删:
cpp
string s("hello world");
s.erase(5, 1); // 从下标5开始删除1个字符 → helloorld
s.pop_back(); // 尾删最后一个字符 → helloorl
(4)清空操作
clear()用于清空字符串内容(不释放容量):
cpp
string s("hello");
s.clear();
cout << s.size() << endl; // 0
cout << s.capacity() << endl; // 仍为原容量(如5)
5. 字符串的查找与替换
(1)查找操作
find系列函数用于查找字符或子串,返回匹配的起始下标,未找到则返回string::npos(一个极大值):
find(str, pos):从pos开始正向查找str;rfind(str, pos):从pos开始反向查找str;find_first_of(str):查找str中任意字符的首次出现位置;find_last_of(str):查找str中任意字符的最后出现位置。
示例:获取文件后缀
cpp
string file("test.cpp");
size_t pos = file.rfind('.'); // 反向查找'.',避免文件名含多个'.'
string suffix = file.substr(pos); // 从pos开始取子串 → .cpp
(2)替换操作
replace(pos, len, str):从pos开始,替换len个字符为str:
cpp
string s("hello world");
s.replace(5, 1, "%%"); // 下标5开始的1个字符替换为"%%" → hello%%world
高效替换技巧:当需要大量替换时,建议用 "空间换时间",避免频繁修改原字符串:
cpp
string s("hello world hello");
string tmp;
for (char ch : s) {
if (ch == ' ') tmp += "%%"; // 空格替换为"%%"
else tmp += ch;
}
s.swap(tmp); // 交换两个字符串(高效,无内存拷贝)
6. 其他常用接口
substr(pos, len):获取从pos开始、长度为len的子串(默认取到结尾);c_str():返回 C 风格字符串(const char*),用于兼容 C 语言函数;stoi(str):将字符串转换为整型(类似的还有stol、stod等);to_string(val):将其他类型(如 int、double)转换为字符串;getline(cin, str, delim):读取一行字符串,默认以换行符结束,可指定分隔符delim。
三、string 类的内存管理
string类会自动管理内存,但合理控制容量可提升效率:
reserve(n):提前预留n个字符的空间(不含'\0'),避免频繁扩容;shrink_to_fit():请求将容量缩减至与长度匹配(编译器可能忽略)。
示例:提前预留空间
cpp
string s;
s.reserve(100); // 预留100个字符的空间
for (int i = 0; i < 100; ++i) {
s.push_back('a'); // 无需扩容,效率更高
}