Skip to content

C++ 字符串

C++ 提供了两种主要的方式来处理字符序列(字符串):

  • std::string:标准的 C++ 字符串类(在 <string> 头文件中)。这是在 C++ 中处理字符串的首选、现代、更安全、更方便的方式。
  • C 风格字符字符串:继承自 C 语言的以空字符 (\0) 结尾的字符数组(char* 或 char[])。虽然在与 C API 或旧代码交互时必不可少,但在现代 C++ 中直接操作它容易出错,通常不推荐使用。

定义在 <string> 头文件中的 std::string 类提供了强大且功能丰富的字符串管理方式。它自动处理内存管理,避免了许多与 C 风格字符串相关的常见错误。

  • 初始化:可以从字符串字面量、其他 std::string 对象或字符串的一部分进行初始化。
  • 赋值:使用 = 运算符。
  • 连接:使用 + 或 += 运算符。
  • 比较:使用标准比较运算符(==, !=, <, >, <=, >=)。
  • 长度/大小:使用 .length() 或 .size() 方法(它们是等价的)。
  • 访问字符:使用 [] 运算符(不进行边界检查)或 .at() 方法(如果索引无效则抛出 std::out_of_range 异常)。
  • 追加:使用 .append() 或 +=。
  • 查找子字符串:使用 .find()、.rfind()、.find_first_of()、.find_last_of() 等方法。
  • 子字符串:使用 .substr() 提取字符串的一部分。
  • 修改:使用 .insert()、.erase()、.replace() 等方法。
  • 转换为 C 风格:使用 .c_str() 获取一个 const char* 指针(以空字符结尾),用于与 C 函数兼容。

使用 std::string 的示例:

#include <iostream>
#include <string>
#include <stdexcept> // 用于 std::out_of_range
int main() {
// 初始化
std::string greeting = "Hello";
std::string name("World"); // 构造函数初始化
std::string message; // 空字符串
// 连接和赋值
message = greeting + ", " + name + "!"; // 使用 +
greeting += ", again!"; // 使用 +=
std::cout << "Greeting: " << greeting << std::endl;
std::cout << "Message: " << message << std::endl;
// 长度
std::cout << "Message length: " << message.length() << std::endl;
// 访问字符
std::cout << "First character: " << message[0] << std::endl; // 'H'
try {
std::cout << "Character at index 6: " << message.at(6) << std::endl; // 'W'
// 这会抛出 std::out_of_range 异常
// std::cout << message.at(100);
} catch (const std::out_of_range& oor) {
std::cerr << "Out of Range error: " << oor.what() << std::endl;
}
// 查找子字符串
size_t commaPos = message.find(',');
if (commaPos != std::string::npos) { // npos 表示“未找到”
std::cout << "Comma found at index: " << commaPos << std::endl;
}
// 子字符串
std::string sub = message.substr(7, 5); // 从索引 7 开始提取 5 个字符
std::cout << "Substring (name): " << sub << std::endl; // "World"
// 转换为 C 风格字符串(用于 C API)
printf("Message as C-string: %s\n", message.c_str());
return 0;
}

预期输出:

Greeting: Hello, again!
Message: Hello, World!
Message length: 13
First character: H
Character at index 6: W
Comma found at index: 5
Substring (name): World
Message as C-string: Hello, World!

C 风格字符串本质上是以特殊空字符 (\0) 结尾的字符数组。它们使用指针 (char*) 管理,如果动态分配则需要手动管理内存。

声明和初始化:

// 从字符串字面量初始化(大小自动包含空终止符)
char greeting[] = "Hello"; // 大小是 6 ('H','e','l','l','o','\0')
// 显式指定大小的数组(必须足够大以容纳字符串 + 空终止符)
char farewell[10] = "Bye"; // {'B','y','e','\0', ?, ?, ?, ?, ?, ?}
// 指针初始化(指向只读内存中的字面量 - 修改是未定义行为!)
const char* readOnlyStr = "Constant String";

greeting[] 的内存表示:

Index: 0 1 2 3 4 5
Value: 'H' 'e' 'l' 'l' 'o' '\0'

常用 C 字符串函数(<cstring> 头文件):

Section titled “常用 C 字符串函数(<cstring> 头文件):”

<cstring> 头文件(在 C 中是 <string.h>) 提供了用于操作 C 风格字符串的函数。警告: 这些函数中的许多是不安全的,因为它们不执行边界检查,如果目标缓冲区太小,可能导致缓冲区溢出。

序号函数用途和安全注意事项
1strcpy(dest, src)将字符串 src 复制到 dest。不安全! 可能导致 dest 溢出。
2strncpy(dest, src, n)最多复制 n 个字符。更安全,但如果达到 n 个字符,可能不会以空字符结尾。
3strcat(dest, src)将字符串 src 追加到 dest。不安全! 可能导致 dest 溢出。
4strncat(dest, src, n)最多从 src 追加 n 个字符。更安全,总是以空字符结尾。
5strlen(str)返回 str 的长度(不包括空终止符)。
6strcmp(s1, s2)比较 s1 和 s2。如果相等返回 0,如果 s1<s2 返回 <0,如果 s1>s2 返回 >0。
7strncmp(s1, s2, n)最多比较 n 个字符。
8strchr(str, ch)返回字符 ch 在 str 中第一次出现的指针,如果未找到则返回 nullptr。
9strstr(haystack, needle)返回字符串 needle 在 haystack 中第一次出现的指针,如果未找到则返回 nullptr。

使用 C 风格字符串的示例(仅作演示):

#include <iostream>
#include <cstring> // 在 C++ 中使用 <cstring>
int main() {
char str1[20] = "Hello"; // 确保缓冲区足够大!
char str2[] = "World";
char str3[20];
int len;
// 将 str1 复制到 str3(使用更安全的 strncpy 或可用的 strcpy_s)
// strcpy(str3, str1); // 不安全版本
strncpy(str3, str1, sizeof(str3) - 1); // 更安全的复制
str3[sizeof(str3) - 1] = '\0'; // 确保以空字符结尾
std::cout << "strncpy(str3, str1, ...): " << str3 << std::endl;
// 将 str2 连接到 str1(使用更安全的 strncat 或 strcat_s)
// strcat(str1, str2); // 不安全版本
strncat(str1, " ", sizeof(str1) - strlen(str1) - 1);
strncat(str1, str2, sizeof(str1) - strlen(str1) - 1); // 更安全的连接
std::cout << "strncat(str1, ...): " << str1 << std::endl;
// 连接后 str1 的总长度
len = strlen(str1);
std::cout << "strlen(str1): " << len << std::endl;
// 比较
if (strcmp(str2, "World") == 0) {
std::cout << "str2 is equal to \"World\"" << std::endl;
}
return 0;
}

预期输出:

strncpy(str3, str1, ...): Hello
strncat(str1, ...): Hello World
strlen(str1): 11
str2 is equal to "World"

总结: 虽然理解 C 风格字符串对于 C++ 开发者很重要(尤其是在处理 C 库或低级代码时),但在现代 C++ 中进行通用字符串操作时,始终首选 std::string。它更安全、更易于使用,并且不易出错。

在需要只读访问字符串数据而无需创建新的 std::string(例如,作为函数参数)的情况下,可以考虑使用 std::string_view(来自 <string_view> 头文件)。它提供了一个对现有字符序列(可以是 std::string、C 风格字符串或字符串字面量)的非拥有视图。