标准库包含许多有用的类——但或许最有用的是 std::string。std::string(和 std::wstring)是一个字符串类,它提供了许多操作来赋值、比较和修改字符串。在本章中,我们将深入研究这些字符串类。
注意:C 风格字符串将被称为“C 风格字符串”,而 std::string(和 std::wstring)将简称为“字符串”。
作者注
本章有些过时,可能会在未来的更新中进行精简。您可以随意浏览材料以获取想法和有用的示例,但建议优先使用技术参考网站(例如 cppreference)以获取最新信息。
字符串类的动机
在之前的课程中,我们介绍了C 风格字符串,它使用字符数组来存储字符串。如果您尝试使用 C 风格字符串做任何事情,您会很快得出结论,它们难以处理,容易出错,并且难以调试。
C 风格字符串有许多缺点,主要围绕着您必须自己进行所有内存管理这一事实。例如,如果您想将字符串“hello!”赋值到一个缓冲区中,您必须首先动态分配一个正确长度的缓冲区
char* strHello { new char[7] };
不要忘记为 null 终止符多算一个字符!
然后您必须实际复制值
strcpy(strHello, "hello!");
希望您的缓冲区足够大,这样就不会发生缓冲区溢出!
当然,由于字符串是动态分配的,因此您必须记住在使用完毕后正确地释放它
delete[] strHello;
不要忘记使用数组删除而不是普通删除!
此外,C 提供了许多直观的运算符来处理数字,例如赋值和比较,但它们根本不适用于 C 风格字符串。有时这些会看起来有效,但实际上会产生不正确的结果——例如,使用 == 比较两个 C 风格字符串实际上会进行指针比较,而不是字符串比较。使用 operator= 将一个 C 风格字符串赋值给另一个 C 风格字符串最初看起来会有效,但实际上是进行指针复制(浅拷贝),这通常不是您想要的。这些情况可能导致程序崩溃,而且很难找到和调试!
总之,使用 C 风格字符串需要记住许多关于安全/不安全的繁琐规则,记住一堆名称奇怪的函数,如 strcat() 和 strcmp(),而不是使用直观的运算符,以及进行大量的手动内存管理。
幸运的是,C++ 和标准库提供了一种更好的处理字符串的方法:std::string 和 std::wstring 类。通过利用 C++ 的概念,如构造函数、析构函数和运算符重载,std::string 允许您以直观和安全的方式创建和操作字符串!不再需要内存管理,不再有奇怪的函数名称,并且大大降低了发生灾难的可能性。
请注册我!
字符串概述
标准库中的所有字符串功能都位于
#include <string>
字符串头文件中实际上有 3 个不同的字符串类。第一个是名为 basic_string<> 的模板基类
namespace std
{
template<class charT, class traits = char_traits<charT>, class Allocator = allocator<charT> >
class basic_string;
}
您不会直接使用此课程,因此暂时无需担心 traits 或 Allocator 的作用。在几乎所有可想象的情况下,默认值就足够了。
标准库提供了两种 basic_string<> 的变体
namespace std
{
typedef basic_string<char> string;
typedef basic_string<wchar_t> wstring;
}
这些是您实际将使用的两个类。std::string 用于标准 ascii 和 utf-8 字符串。std::wstring 用于宽字符/unicode (utf-16) 字符串。没有内置的 utf-32 字符串类(尽管如果需要,您应该能够从 basic_string<> 扩展自己的)。
虽然您将直接使用 std::string 和 std::wstring,但所有字符串功能都在 basic_string<> 类中实现。String 和 wstring 能够通过模板化的特性直接访问该功能。因此,所有呈现的函数都将适用于 string 和 wstring。但是,由于 basic_string 是一个模板类,这也意味着当您对 string 或 wstring 进行语法不正确的操作时,编译器将产生可怕的模板错误。不要被这些错误吓倒;它们看起来比实际情况糟得多!
以下是字符串类中所有函数的列表。这些函数中的大多数都有多种形式来处理不同类型的输入,我们将在下一课中更深入地介绍。
函数 | 效果 |
---|---|
(构造函数) (析构函数) | 创建或复制字符串 销毁字符串 |
capacity() empty() length(), size() max_size() reserve() | 返回在不重新分配的情况下可以容纳的字符数 返回一个布尔值,指示字符串是否为空 返回字符串中的字符数 返回可以分配的最大字符串大小 扩展或缩小字符串的容量 |
[], at() | 访问特定索引处的字符 |
=, assign() +=, append(), push_back() insert() clear() erase() replace() resize() swap() | 为字符串分配新值 将字符连接到字符串末尾 在字符串中的任意索引处插入字符 删除字符串中的所有字符 删除字符串中任意索引处的字符 将任意索引处的字符替换为其他字符 扩展或缩小字符串(截断或在字符串末尾添加字符) 交换两个字符串的值 |
>>, getline() << c_str() copy() data() | 从输入流中读取值到字符串中 将字符串值写入输出流 将字符串内容作为以 NULL 结尾的 C 风格字符串返回 将内容(非 NULL 结尾)复制到字符数组 与 c_str() 相同。非 const 重载允许写入返回的字符串。 |
==, != <, <=, > >= compare() | 比较两个字符串是否相等/不相等(返回 bool) 比较两个字符串是小于还是大于彼此(返回 bool) 比较两个字符串是否相等/不相等(返回 -1, 0 或 1) |
+ substr() | 连接两个字符串 返回一个子字符串 |
find() find_first_of() find_first_not_of() find_last_of() find_last_not_of() rfind() | 查找第一个字符/子字符串的索引 查找字符集中第一个字符的索引 查找字符集中第一个不属于该字符集的字符的索引 查找字符集中最后一个字符的索引 查找字符集中最后一个不属于该字符集的字符的索引 查找最后一个字符/子字符串的索引 |
begin(), end() get_allocator() rbegin(), rend() | 字符串开头/结尾的正向迭代器支持 返回分配器 字符串开头/结尾的反向迭代器支持 |
虽然标准库字符串类提供了许多功能,但仍有一些显著的遗漏
- 从数字创建字符串的构造函数
- 大小写转换/大写/小写函数
- 不区分大小写的比较
- 分词/将字符串拆分为数组
- 轻松获取字符串左侧或右侧部分的函数
- 空白字符修剪
- sprintf 风格的字符串格式化
- utf-8 到 utf-16 或反向转换
对于其中大多数,您必须要么自己编写函数,要么将字符串转换为 C 风格字符串(使用 c_str())并使用提供此功能的 C 函数。
在接下来的课程中,我们将更深入地研究字符串类的各种函数。尽管我们将使用字符串作为示例,但所有内容同样适用于 wstring。