C 风格字符串
我们一直在输出中使用一种语法:
cout << "Hello, world!" << endl;
这里面,我一直没有讲“双引号引起的东西”到底是什么。这一节的内容将解释它的本质。
字符数组
首先从字符数组说起。字符数组,指的就是由若干个字符类型变量构成的数组。即:
char a[5]{};
这里 a
就是一个字符数组。嗯,字符数组就是个数组,这没什么特殊的。比如下面的代码就带空格地输出了 a
这个数组中的元素 H e l l o
。
现在来看这样神奇的代码:
这里的初始化器里面放上了“双引号引起的一句话”——有点奇怪,但是这样的代码是没问题的。它的运行结果和之前也一样,也是 H e l l o
——聪明的你一定能大概猜到这种初始化方法做了什么:用引号引起的字符逐一初始化字符数组内的元素——但真的如此吗?
你会发现这段代码输出的是 6
,而不是 5
。也就是说 {"Hello"}
这个初始化器初始化了 6 个元素(因为一个 char
占 1 字节,sizeof(a)
为 6 说明有 6 个 char
)。明明引号里只有五个字符,为什么会有六个初始化值呢?
答案在于 "Hello"
确实包含六个字符。读者会说,你这是不识数啊。嗯——我们先来看看到底是哪 6 个字符:
元素 | a[0] | a[1] | a[2] | a[3] | a[4] | a[5] |
---|---|---|---|---|---|---|
值 | 'H' | 'e' | 'l' | 'l' | 'o' | '\0' |
问题出在结尾多了一个字符叫做 \0
。如果你足够细心,就能发现其实它是转义字符的一种。这个字符叫做空字符(Null character)。空字符是 char
类型的零值。
因为
'\0'
是零值,所以'\0'
转换为int
类型得到的是0
,转换为bool
类型得到的是false
。
下面重点来了:由字符数组中连续、相邻的若干个字符,且只有最后一个字符是空字符构成的序列称为 C 风格字符串(C string),这里简称为字符串。请看以下示例:
char a[6]{'H', 'e', 'l', 'l', 'o', '\0'}; // a 是一个字符串,因为它是 \0 结尾的
char b[5]{'H', 'e', 'l', 'l', 'o'}; // b 不是字符串,因为最后一个字符非空
char c[5]{'H', 'i', '\0', '\0', '\0'}; // c 的前三个元素构成字符串
char d[5]{'\0', '\0', '@', '\0', '\0'}; // d[2] 和 d[3] 两个元素构成字符串
char e[2]{'#', '\0'}; // e 是一个字符串
char f[1]{'$'}; // f 不是字符串(结尾非空)
char g[1]{'\0'}; // g 是字符串
字符串字面量
由双引号引起的转义或非转义字符
"转义或非转义字符"
称为字符串字面量(String literal)。这种字面量的值为 const char[N]
类型的数组,其中 N
为 转义或非转义字符
的个数 + 1,称为它的大小。数组中存储的元素分别为字面量中的字符,并追加一个空字符 '\0'
(因此字符串字面量是一个典型的 C 风格字符串)。比如:
"foobar"; // 这个字符串字面量完全等价于下面的数组 a:
char a[7]{'f', 'o', 'o', 'b', 'a', 'r', '\0'};
注意字符串字面量的元素是只读的,因为字面量都是常量,在编译期间确定,且运行期间的值不会发生改变。
特别地,字符数组可以用字符串字面量初始化:
char a[7]{"foobar"};
但要求数组的大小不得小于字符串字面量。比如:
char a[6]{"Hello"}; // OK,字符串字面量大小为 6,与数组大小一致
char b[9]{"Hello"}; // OK,数组大小更大
char c[5]{"Hello"}; // 错误,数组大小不够
对于更大的那种情况,它和之前用较少值的列表初始化数组的效果是一致的:
那么剩下的 a[6]
到 a[8]
采用零初始化。刚刚提到过,char
类型的零值正是 '\0'
,故而零初始化将剩下的元素也都初始化为 '\0'
。
// 上例中 a 等价于:
char a[9]{'H', 'e', 'l', 'l', 'o', '\0', '\0', '\0', '\0'};
类似地,用字符串字面量初始化字符数组时,可以省略数组大小:
输出字符串
字符串字面量可以直接输出。比如 cout << "Hello, world!";
这样,我们已经见过很多次了。其实,如果一个数组存放的内容恰为一个字符串,则这个字符串也可以被输出。具体来讲是这样的:
正如预期那样,输出了
abcd
hello
当你将一个字符数组放在 cout <<
后面的时候,程序会从头挨个输出这个数组内的元素,直到空字符为止。这就是为什么输出数组 a
的时候,只会将前四个元素输出;因为第五个元素是空字符。
这样输出字符串的时候,空字符并不会被输出。然而,即便你强制输出空字符,你也看不到任何变化——因为空字符是不可见字符,输出之后你一般看不见。(但是这些不可见字符可能会在评测系统中引起麻烦,所以要避免输出它们。)
注意事项
字符数组和普通数组无异,只是多了一些使用方法而已。但是字符数组同样不能被赋值:
当 a
是一个字符串时,由于它是空字符结尾的,所以可以这样赋值:
但这个方法只能用于字符串;如果字符数组中不含空字符,就会发生意外的错误。略感幸运的是,我们有一些更简便的方法来操作 C 风格字符串,参见稍微靠后的章节。