Skip to content

22.1 - std::string 和 std::wstring

Key Takeaway

C++标准库提供了很多有用的类,但是其中最有用的,可能非 std::string 莫属了。std::string (和 std::wstring) 是一个字符串类,提供了字符串相关的多种操作,包括赋值、比较和修改,在本章中,我们将会一起深入学习字符串类。

注意:我们将C语言中的字符串称为C语言风格字符串,而将 std::string (和 std::wstring 称为字符串)。

作者注

注意,本章节的内容稍微有些过时,而且很可能会在将来进行更新。你可以浏览一下本章节,重点关注有关字符串的核心思想和例子。但如果是作为参考文档,专门的参考网站(例如 cppreference) 会使更好的选择,在那里你可以找到更新、更准确的信息。

设计字符串类的初衷

在之前的课程中,我们介绍了C 语言风格字符串,它的本质就是一个字符数组,存放了组成字符串的字符。如果你曾经使用过C风格字符串,你肯定也会觉得它用起来很困难、容易出错而且不容易调试。

C语言风格字符串有很多缺陷,这主要是因为你必须自己管理它的内存。比方说,如果你需要将字符串"hello"赋值到一个缓存中,则你必须首先分配一个长度正确的缓存:

1
char* strHello { new char[7] };

不要忘记在计算长度时多加一个结束符的长度!

然后你需要将值拷贝到这块缓存中:

1
strcpy(strHello, "hello!");

只有当你计算的缓存长度正确时,才不会出现缓冲区溢出。

当然,因为字符串是动态分配的,所以你还必须记得在使用完成后正确地释放它:

1
delete[] strHello;

不要忘记使用数组delete而不是普通delete。

此外,C语言提供的许多用于处理数字的操作符,例如赋值和比较,根本无法用于处理C风格的字符串。有时,这些方法看似有效,但实际上会产生不正确的结果——例如,使用==比较两个C风格字符串时,实际上比较的是两个指针,而非字符串本身。使用operator=将一个C风格字符串赋值给另一个C风格字符串,乍一看似乎可行,但实际上是在进行指针的浅拷贝,这通常不是我们想要的结果。这类事情会导致程序崩溃,而且很难发现和调试!

归根结底,使用C风格字符串需要记住许多关于安全/不安全的规则,记住一堆具有有趣名称的函数,如strcat()strcmp(),而不是使用直观的操作符,而且还需要进行大量的手动内存管理。

幸运的是,C++和标准库提供了处理字符串的更好的方法:std::stringstd::wstring类。通过使用构造函数、析构函数和操作符重载等C++概念,std::string 使我们可以以一种直观和安全的方式创建和操作字符串!不再需要内存管理,不再需要奇怪的函数名,也大大降低了发生问题的可能性。

字符串简介

所有标准库字符串函数都位于头文件中,使用时只需要包含该头文件即可:

1
#include <string>

在字符串头文件中实际上有3个不同的字符串类。第一个是名为basic_string的模板基类:

1
2
3
4
5
namespace std
{
    template<class charT, class traits = char_traits<charT>, class Allocator = allocator<charT> >
        class basic_string;
}

我们并不会直接使用这个类,所以暂时不要担心traitAllocator这些乱七八糟的情况。在几乎所有可以想象到的情况下,默认值就足够了。

标准库提供了两种类型的 basic_string

1
2
3
4
5
namespace std
{
    typedef basic_string<char> string;
    typedef basic_string<wchar_t> wstring;
}

这是你将实际使用的两个类。string用于标准的ASCII和utf-8字符串。wstring用于宽字符/unicode (utf-16)字符串。但是并没有用于utf-32字符串的内置类(不过,如果需要的话,您应该能够从basic_string自己扩展)。

尽管你会直接使用的是std::stringstd::wstring,但所有的字符串功能都是在basic_string类中实现的。stringwstring能够直接访问这些功能,因为它们是模板化的。因此,所提供的所有函数都适用于stringwstring。但是,由于basic_string是一个模板化类,这也意味着当你对stringwstring进行语法错误操作时,编译器将产生可怕的模板错误。不要被这些错误吓倒:它们只是看起来比较吓人罢了。

这是string类中所有函数的列表。这些函数中的大多数都有多种变种用于处理不同类型的输入,我们将在接下来的课程中更深入地讨它们的具体用法。

构建和析构

Function Effect
(constructor) 创建一个字符串的拷贝
(destructor) 销毁字符串

长度和容量

Function Effect
capacity() 在不重新分配内存情况下,能够存放的最多的字符个数
empty() 返回布尔值表示字符串是否为空字符串
length(), size() 返回字符串中字符的个数
max_size() 返回可以分配的最长的字符串大小
reserve() 扩大或缩小字符串的容量

元素访问

Function Effect
[], at() 使用索引访问字符串中的字符

修改字符串

Function Effect
=, assign() 字符串赋值
+=, append(), push_back() 字符串末尾添加
insert() 在任意索引位置插入
clear() 删除字符串中的所有字符
erase() 清除字符
replace() 替换字符串中的一部分
resize() 调整字符串大小(在末尾截断或添加)
swap() 交换两个字符串

输入输出

Function Effect
>>, getline() 从输入流中获取输入
<< 将字符串输出到输出流
c_str() 返回以空字符结尾的C语言风格字符串
copy() 拷贝字符串内容到字符数组(不包含结束符)
data() 类似c_str()。非const重载,允许写入返回的字符串。

字符串比较

Function Effect
==, != 比较两个字符串(返回布尔值)
<, <=, > ,>= 比较两个字符串大小关系(返回布尔值)
compare() 比较两个字符串是否相等(返回 -1, 0, 或 1)

子串和拼接

Function Effect
+ 连接两个字符串
substr() 返回一个子串

搜索

Function Effect
find() 搜索字符、子串第一次出现的索引位置
find_first_of() 找到字符第一次出现的索引位置
find_first_not_of() 找到第一个不在字符集中的字符出现的位置
find_last_of() 找到字符集中最后一个字符出现的索引位置
find_last_not_of() 找到最后一个不在字符集中的字符出现的位置
rfind() 搜索字符、子串最后一次出现的索引位置

迭代器和内存分配

Function Effect
begin(), end() 正向迭代器,开头/结尾
get_allocator() 返回分配器
rbegin(), rend() 反向迭代器,开头/结尾

虽然标准库字符串类提供了很多功能,但有一些明显的遗漏:

  • 对于正则表达式的支持;
  • 从数字创建字符串的构造函数;
  • 大小写字母转换函数;
  • 忽略大小写的字符串比较方法;
  • 字符串分词(Tokenization)后存入数组;
  • 获取字符串左半部分或右半部分的函数;
  • 空白移除函数(trimming);
  • sprintf风格的字符串格式化函数;
  • utf-8 和 utf-16 转换。

对于其中的大多数功能,你必须编写自己的函数,或者将字符串转换为C风格的字符串(使用c_str()),并使用提供此功能的C函数。

在接下来的课程中,我们将更深入地研究字符串类的各种功能。尽管我们将在示例中使用string,但所有内容都同样适用于wstring