字符序列

在之前的章节中,我们已经简要介绍过 string 类。它是一个非常强大的类,用于处理和操作字符串。然而,因为字符串实际上是字符序列,我们也可以用普通字符类型的数组来表示它们。

例如,下面的数组:

1
char foo [20];

是一个可以存储最多 20 个 char 类型元素的数组。它可以表示为:


因此,这个数组有能力存储最多 20 个字符的序列。但这个容量不必完全用尽:该数组也可以容纳较短的序列。例如,在程序的某个时刻,序列 "Hello" 或序列 "Merry Christmas" 都可以存储在 foo 中,因为它们都适合容量为 20 个字符的序列。

按照约定,用字符序列表示的字符串的末尾由一个特殊字符来标记:空字符 (null character),其字面值可以写作 '\0'(反斜杠,零)。

在这种情况下,这个名为 foo 的、包含 20 个 char 类型元素的数组,可以表示为存储了 "Hello""Merry Christmas" 字符序列的样子:


请注意,在字符串内容本身之后,如何添加一个空字符 ('\0') 来表示序列的结束。灰色面板代表值不确定的 char 元素。

以空字符结尾的字符序列的初始化

因为字符数组是普通数组,所以它们遵循与普通数组相同的规则。例如,要用某个预定的字符序列来初始化一个字符数组,我们可以像处理任何其他数组一样:

1
char myword[] = { 'H', 'e', 'l', 'l', 'o', '\0' };

上述代码声明了一个包含 6 个 char 类型元素的数组,并用构成单词 "Hello" 的字符以及末尾的一个空字符 '\0' 来初始化它。

但是,字符元素数组还有另一种初始化方式:直接使用字符串字面量

在前面章节的一些示例所使用的表达式中,字符串字面量已经出现过好几次了。它们通过将文本括在双引号 (") 中来指定。例如:

1
"the result is: "

这是一个字符串字面量,可能在之前的某个示例中使用过。

用双引号 (") 括起来的字符序列是字面常量。它们的类型实际上就是以空字符结尾的字符数组。这意味着字符串字面量的末尾总是会自动附加一个空字符 ('\0')。

因此,名为 myword 的 char 元素数组可以通过以下两种语句中的任意一种来初始化为一个以空字符结尾的字符序列:

1
2
char myword[] = { 'H', 'e', 'l', 'l', 'o', '\0' };
char myword[] = "Hello";

在这两种情况下,字符数组 myword 都被声明为大小为 6 个 char 元素的数组:构成单词 "Hello" 的 5 个字符,外加一个最后的空字符 ('\0')。这个空字符指定了序列的结束,在第二种使用双引号 (") 的情况下,它是自动附加的。

请注意,我们这里谈论的是在声明字符数组时进行初始化,而不是在它们被声明之后再给它们赋值。事实上,因为字符串字面量是常规数组,它们具有与常规数组相同的限制,不能被赋值。

(在 myword 已经被如上声明之后)像下面这样的表达式:

1
2
myword = "Bye";
myword[] = "Bye";

无效的,就像下面这个表达式也无效一样:

1
myword = { 'B', 'y', 'e', '\0' };

这是因为数组不能被赋值。但请注意,它的每个元素可以被单独赋值。例如,下面这样是正确的:

1
2
3
4
myword[0] = 'B';
myword[1] = 'y';
myword[2] = 'e';
myword[3] = '\0';

字符串和以空字符结尾的字符序列

以空字符结尾的普通字符序列数组是 C 语言中用来表示字符串的典型类型(因此它们也被称为 C-风格字符串)。在 C++ 中,尽管标准库为字符串定义了一个特定的类型(string 类),但以空字符结尾的普通字符序列数组(C-风格字符串)仍然是该语言中表示字符串的一种自然方式;事实上,字符串字面量仍然总是生成以空字符结尾的字符序列,而不是 string 对象。

在标准库中,两种表示字符串的方式(C-风格字符串和库字符串)共存,并且大多数需要字符串的函数都被重载以支持这两种方式。

例如,cincout 直接支持以空字符结尾的序列,允许它们像字符串一样直接从 cin 中提取或插入到 cout 中。例如:

// strings and NTCS:
#include <iostream>
#include <string>
using namespace std;

int main ()
{
  char question1[] = "What is your name? ";
  string question2 = "Where do you live? ";
  char answer1 [80];
  string answer2;
  cout << question1;
  cin >> answer1;
  cout << question2;
  cin >> answer2;
  cout << "Hello, " << answer1;
  cout << " from " << answer2 << "!\n";
  return 0;
}
What is your name? Homer
Where do you live? Greece
Hello, Homer from Greece!

在这个例子中,同时使用了以空字符结尾序列的字符数组和字符串。它们在与 cincout 结合使用时几乎可以互换,但它们的声明有一个显著的区别:数组具有固定大小,需要在声明时显式或隐式地指定;question1 的大小正好是 20 个字符(包括结尾的空字符),answer1 的大小是 80 个字符;而字符串就是字符串,没有指定大小。这是因为字符串具有在运行时确定的动态大小,而数组的大小在编译时、程序运行之前就已经确定了。

无论如何,以空字符结尾的字符序列和字符串可以很容易地相互转换:

以空字符结尾的字符序列可以隐式地转换为字符串,而字符串可以通过使用 string 的成员函数 c_strdata 转换为以空字符结尾的字符序列:

1
2
3
4
char myntcs[] = "some text";
string mystring = myntcs;  // convert c-string to string
cout << mystring;          // printed as a library string
cout << mystring.c_str();  // printed as a c-string 

(注意:stringc_strdata 成员函数是等效的)
Index
目录