第5.8章: C 风格字符串

English

概述

C 风格字符串是以特殊字符 '\0'(空终止符)结尾的 C 风格字符数组。这个约定源自 C 语言,并且为了 兼容性而保留在现代 C++ 中。

在本章中,你将学到:

1. 什么是 C 风格字符串?

C 风格字符串就是一个以 '\0' 结尾的 char C 风格数组。


char s[6]{'H', 'e', 'l', 'l', 'o', '\0'};

内存布局:


Address: 2000  2001  2002  2003  2004  2005
Memory: [ H ][ e ][ l ][ l ][ o ][ \0 ]

2. 为什么 C 风格字符串必须以 '\0' 结尾

C 风格字符串不存储自己的长度。 相反,处理它们的代码会扫描字符,直到找到 '\0'。

如果缺少空终止符:


char bad[5]{'H', 'e', 'l', 'l', 'o'};  // ❌ no '\0'

3. 字符串字面量

像 "Hello" 这样的字符串字面量被存储为一个 常量 char 的 C 风格数组,并带有一个空终止符。


char const *p = "Hello";

内存布局:


"Hello" → [ H ][ e ][ l ][ l ][ o ][ \0 ]

重要规则:


// char *p = "Hello";   // allowed to compile, but dangerous
// p[0] = 'h';          // ❌ undefined behavior

指向 C 风格字符串的指针请始终写成 char const*。

4. C 风格字符串就是 C 风格数组

C 风格字符串只是一个 char 的 C 风格数组。 因此,前面章节中的所有规则都适用:


char s[6]{"Hello"};

char const *p = s;   // decay
char const *q = p + 1; // points to 'e'

5. 为什么 fast_io 能打印字符串字面量,却不能打印指向它的指针

字符串字面量本质上是C 风格数组。 fast_io 能识别字符的 C 风格数组,并把它们当字符串打印。


print("hello world\n");   // ✔ works

然而,一旦数组退化为指针,fast_io 就无法知道该指针是否指向 一个有效的 C 风格字符串。 它拒绝直接打印裸指针。


char const *ptr = "hello world\n";

// print(ptr);   // ❌ does NOT work

fast_io 必须假定:

这就是为什么:


print("hello");     ✔ works (array)
print(ptr);         ❌ does not work (pointer)

要把指针作为 C 风格字符串打印,请使用:


println(os_c_str(ptr));

6. strlen 与 strnlen

strlen 通过扫描直到 '\0' 来计算 C 风格字符串的 长度。


char s[6]{"Hello"};

std::size_t n = strlen(s);   // returns 5

重要: strlen 假定字符串已正确以空字符结尾。 如果缺少 '\0',它就会读到末尾之外 → 未定义行为。

strnlen

strnlen 类似,但在达到最大字符数后停止。


std::size_t n = strnlen(s, 6);   // safe upper bound

strnlen 更安全,因为它能防止扫描无界的内存。

7. 用于 C 风格字符串的 fast_io 操纵器

fast_io 提供了用于打印指针与 C 风格字符串的操纵器。

pointervw(ptr)

把指针值作为地址打印。


char const *p = "Hello";
println(pointervw(p));   // prints address of p

os_c_str(ptr)

打印一个以空字符结尾的 C 风格字符串。


println(os_c_str(p));    // prints "Hello"

os_c_str(ptr, n)

打印 C 风格字符串,但最多在 n 个字符之后停止。


println(os_c_str(p, 3));   // prints "Hel"

8. 宽字符与 Unicode C 风格字符串

C++ 支持多种字符类型,每种都有自己的 C 风格字符串形式:

每种都使用自己的空终止符:


wchar_t  ws[]{L"Hello"};
char16_t u16s[]{u"Hello"};
char32_t u32s[]{U"Hello"};
char8_t  u8s[]{u8"Hello"};

它们都遵循同样的规则:

要点