std::string:会自我管理的文本
在上一页中,你看到了智能指针如何让一个对象拥有资源并自动清理。std::string 就是把同样的思路用在文本上:它拥有一块字符缓冲区,追加内容时让它增长,字符串离开作用域时释放它。你永远不用调用 new,不用数字节数,也不用担心漏掉空终止符。
使用它需要包含 <string>。这个类位于 std 命名空间中。
那个 + 运算符可是在干实事:它分配一块足够容纳两部分的新缓冲区,再把它们拷贝进去。如果用裸 char*,也就是一个指向字符缓冲区的普通指针,你就得动用 strcpy 和 strcat,还得祈祷缓冲区足够大。std::string 让这一整类错误都消失了。
创建和拼接字符串
你可以用 + 拼接,但原地增长字符串的主力是 +=。它追加到已有的缓冲区上,而不是每次都生成一个全新的字符串,这在循环里很重要。
一个常见的意外:+ 需要至少有一个操作数已经是 std::string。两个字符串字面量只是 const char*,所以 "a" + "b" 无法编译:两个裸指针之间没有 operator+。先把其中一个变成 string:
string s = "a" + "b"; // error: can't add two const char*
string s = string("a") + "b"; // fine - left side is a std::string
string s = "a"s + "b"; // fine in C++14+, the "s" literal suffix
注意最后一种写法用的是 <string> 中的 s 后缀(using namespace std::string_literals;),它直接把字面量变成 std::string。
访问字符串内部
std::string 的行为就像一个 char 容器,所以你可以按下标访问它、遍历它,也可以取出其中的片段。
substr(pos, len) 返回一个从原字符串拷贝出来的全新字符串,不会修改原字符串。注意边界:对一个 5 个字符的字符串访问 word[10] 是未定义行为,它不会抛出异常,只会读到垃圾值。如果你想要会抛出 std::out_of_range 的带检查访问,就用 word.at(10) 代替 word[10]。
另一个经典陷阱:.size() 返回的是无符号类型(size_t)。像 for (size_t i = word.size() - 1; i >= 0; --i) 这样写倒序循环永远不会结束,因为无符号的 i 永远不会小于零,它会回绕成一个巨大的数。需要倒序遍历字符串时,用有符号的下标,或者调整循环条件。
查找和替换
find 定位一个子串或字符,返回它的起始下标。找不到目标时,它返回特殊常量 std::string::npos:始终和它比较,而不要以为 find 会返回 -1。
要原地修改文本,replace(pos, len, text) 把一段内容换成新内容(长度可以不同),insert/erase 则用来添加或删除片段:
字符串与数字
文本和数字不会自己互相转换:"42" 是三个字符,而不是整数 42。标准库提供了双向的转换函数。用 stoi、stod 等把文本解析成数字,用 to_string 反向转换。
这里有两个陷阱。第一,stoi("abc") 会抛出 std::invalid_argument,所以要用 try/catch 块保护不可信的输入。第二,to_string(3.99) 给出的是完整的 3.990000:如果你需要控制精度和格式,那是字符串流的活儿,也正是我们接下来要去的地方。
try {
int n = std::stoi(userInput);
} catch (const std::invalid_argument&) {
std::cout << "That wasn't a number.\n";
}
下一节:输入与输出
你一直在用 cout 打印字符串,但从用户那里把它们读回来另有一些意外:cin >> name 遇到第一个空格就停下,所以像“Ada Lovelace”这样的一整行需要改用 std::getline。下一页会好好讲解 C++ 的输入与输出:流运算符,读取整行,以及如何混用 >> 和 getline 而不被残留的换行符绊倒。
常见问题
C++ 中 std::string 和 char* 有什么区别?
std::string 是一个类,它拥有并管理自己的内存,按需增长,并自动清理。char* 只是一个指向字符的裸指针,字符以 '\0' 结尾:缓冲区、长度和生命周期都得你自己管理。几乎所有场合都优先用 std::string,它能消除整类的缓冲区溢出和悬空指针 bug。
C++ 中如何获取字符串的长度?
对 std::string 调用 .size() 或它的别名 .length():name.size() 以 size_t 返回字符个数。两者返回的值相同;size() 更常见,因为它和其他所有 STL 容器保持一致。
C++ 中如何把字符串转换成数字?
int 用 std::stoi,double 用 std::stod,还有类似的 stol、stof。例如:int n = std::stoi("42");。如果文本不是数字,它们会抛出 std::invalid_argument,所以当输入不可信时,要用 try/catch 把它们包起来。