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

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

"作者注"

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

设计字符串类的初衷

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

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

char* strHello { new char[7] };

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

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

strcpy(strHello, "hello!");

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

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

delete[] strHello;

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

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

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

幸运的是,C++和标准库提供了处理字符串的更好的方法:std::stringstd::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;
}

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

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

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

构建和析构

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

长度和容量

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

元素访问

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

修改字符串

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

输入输出

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

字符串比较

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

子串和拼接

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

搜索

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

迭代器和内存分配

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

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

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

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

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