前言

std::string 是C++ STL中最常用的类之一,它封装了C风格的字符数组,提供了安全、便捷的字符串操作。本文将全面讲解 string 的构造、拼接、查找、替换、子串以及数值转换等功能,并指出常见陷阱。

构造与初始化

std::string 提供了多种构造方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <string>

int main() {
std::string s1; // 空字符串 ""
std::string s2("hello"); // 从C字符串构造
std::string s3("hello", 3); // 从C字符串的前3个字符构造 -> "hel"
std::string s4(5, 'a'); // 5个'a' -> "aaaaa"
std::string s5 = "world"; // 拷贝初始化
std::string s6(s2); // 拷贝构造
std::string s7(s2, 1, 3); // 从s2的第1个位置开始取3个字符 -> "ell"

std::cout << "s1: [" << s1 << "]" << std::endl;
std::cout << "s2: " << s2 << std::endl;
std::cout << "s3: " << s3 << std::endl;
std::cout << "s4: " << s4 << std::endl;
std::cout << "s5: " << s5 << std::endl;
std::cout << "s6: " << s6 << std::endl;
std::cout << "s7: " << s7 << std::endl;

return 0;
}

基本操作

获取长度与判断空

1
2
3
4
5
6
7
std::string s = "hello";

s.size(); // 5(返回size_t,无符号)
s.length(); // 5(与size()相同)
s.empty(); // false
s.capacity(); // 容量(可能大于size)
s.max_size(); // 系统允许的最大长度

陷阱size() 返回的是无符号类型 size_t,与有符号整数比较时可能导致意外结果:

1
2
3
4
std::string s = "abc";
if (s.size() - 1 > -1) { // size()-1是size_t类型,永远不会小于-1
std::cout << "永远为真!" << std::endl;
}

访问字符

1
2
3
4
5
6
7
8
std::string s = "hello";

s[0]; // 'h'(不检查越界)
s.at(0); // 'h'(越界时抛出std::out_of_range异常)
s.front(); // 'h'(首字符)
s.back(); // 'o'(尾字符)
s.c_str(); // 返回const char*(C风格字符串)
s.data(); // 同c_str(),C++17后也可写

拼接与追加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include <string>

int main() {
std::string s = "Hello";

// 使用 += 运算符(推荐)
s += " World";
s += '!';

// 使用 append
s.append(" C++");

// 使用 push_back 追加单个字符
s.push_back('!');

std::cout << s << std::endl;
// 输出: Hello World! C++!

// 使用 + 拼接(会创建临时对象)
std::string result = "Name: " + s + " END";
std::cout << result << std::endl;

return 0;
}

性能提示:在循环中拼接字符串时,优先使用 +=append 而非 +,因为 + 会产生临时对象。

查找

string 提供了丰富的查找函数,找不到时返回 std::string::npos

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include <string>

int main() {
std::string s = "Hello, World! Hello, C++!";

// find - 从前往后找
std::cout << s.find("Hello") << std::endl; // 0
std::cout << s.find("Hello", 5) << std::endl; // 14(从位置5开始找)
std::cout << s.find("xyz") << std::endl; // npos(未找到)

// rfind - 从后往前找
std::cout << s.rfind("Hello") << std::endl; // 14

// find_first_of - 查找任意一个匹配的字符
std::cout << s.find_first_of("aeiou") << std::endl; // 1('e')

// find_last_of
std::cout << s.find_last_of("aeiou") << std::endl; // 24('u')

// find_first_not_of
std::cout << s.find_first_not_of("Hello, !") << std::endl; // 7('W')

return 0;
}

安全查找的惯用写法

1
2
3
4
5
6
7
std::string s = "hello world";
auto pos = s.find("world");
if (pos != std::string::npos) {
std::cout << "找到于位置: " << pos << std::endl;
} else {
std::cout << "未找到" << std::endl;
}

替换与子串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <string>

int main() {
std::string s = "Hello, World!";

// substr - 取子串
std::cout << s.substr(7) << std::endl; // "World!"
std::cout << s.substr(7, 5) << std::endl; // "World"

// replace - 替换
s.replace(7, 5, "C++"); // 将位置7开始的5个字符替换为"C++"
std::cout << s << std::endl; // "Hello, C++!"

// 删除(erase)
s.erase(5, 2); // 删除位置5开始的2个字符
std::cout << s << std::endl; // "HelloC++!"

// 插入(insert)
s.insert(5, ", "); // 在位置5插入
std::cout << s << std::endl; // "Hello, C++!"

return 0;
}

实战:替换所有出现的子串

string::replace 只替换第一个匹配。替换全部需要循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <string>

void replaceAll(std::string& s, const std::string& from, const std::string& to) {
if (from.empty()) return;
size_t pos = 0;
while ((pos = s.find(from, pos)) != std::string::npos) {
s.replace(pos, from.length(), to);
pos += to.length(); // 跳过已替换的部分,避免死循环
}
}

int main() {
std::string s = "aaa bbb aaa ccc aaa";
replaceAll(s, "aaa", "XXX");
std::cout << s << std::endl; // "XXX bbb XXX ccc XXX"
return 0;
}

数值转换

C++11提供了方便的数值与字符串转换函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
#include <string>

int main() {
// 数值 -> 字符串
std::string s1 = std::to_string(42); // "42"
std::string s2 = std::to_string(3.14); // "3.140000"
std::string s3 = std::to_string(255); // "255"

std::cout << s1 << ", " << s2 << ", " << s3 << std::endl;

// 字符串 -> 数值(C++11)
int i = std::stoi("42"); // 42
long l = std::stol("1234567890"); // 1234567890L
double d = std::stod("3.14"); // 3.14
float f = std::stof("2.718"); // 2.718f

std::cout << i << ", " << l << ", " << d << ", " << f << std::endl;

// 带异常处理的转换
try {
int bad = std::stoi("not a number");
} catch (const std::invalid_argument& e) {
std::cout << "转换失败: " << e.what() << std::endl;
} catch (const std::out_of_range& e) {
std::cout << "超出范围: " << e.what() << std::endl;
}

return 0;
}

提示std::to_string 对浮点数的格式化不太灵活,如需精确控制格式,可以使用 std::ostringstream 或 C++20 的 std::format

字符串遍历与修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <string>
#include <cctype>

int main() {
std::string s = "Hello World 123";

// 遍历每个字符
for (char& c : s) {
if (std::isalpha(c)) {
c = std::toupper(c); // 转大写
}
}
std::cout << s << std::endl; // "HELLO WORLD 123"

// 只读遍历
int alphaCount = 0;
for (char c : s) {
if (std::isalpha(c)) alphaCount++;
}
std::cout << "字母个数: " << alphaCount << std::endl;

return 0;
}

与C风格字符串的对比

特性 char* std::string
内存管理 手动 自动
拼接 strcat(不安全) +=(安全)
长度 strlen .size()
比较 strcmp ==, <
拷贝 strcpy(不安全) 直接赋值
越界检查 .at()

结论:在C++中优先使用 std::string,避免使用裸字符数组。只有在需要与C API交互时才使用 .c_str() 转换。

小结

std::string 远比看起来强大。它不仅能处理基本的字符串操作,还提供了查找、替换、子串、数值转换等丰富功能。掌握这些操作,能让你在处理文本时游刃有余。下一篇我们将深入 STL 中最常用的容器——vector