第5.8章: C 风格字符串
概述
C 风格字符串是以特殊字符 '\0'(空终止符)结尾的
C 风格字符数组。这个约定源自 C 语言,并且为了
兼容性而保留在现代 C++ 中。
在本章中,你将学到:
- 什么是 C 风格字符串
- 以空字符结尾的数组如何工作
- 字符串字面量以及它们为何只读
- 为什么 C 风格字符串会退化为指针
- 为什么指向 C 风格字符串的指针应始终使用
char const* - 为什么 fast_io 能打印字符串字面量,却不能打印指向它的指针
strlen与strnlen如何工作- fast_io 提供了哪些用于打印 C 风格字符串的操纵器
- 宽字符与 Unicode 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 必须假定:
- 匹配
char_type的 C 风格数组是字符串字面量 - 指针不自动等同于 C 风格字符串
这就是为什么:
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 风格字符串形式:
char—— 窄字符串wchar_t—— 宽字符串char8_t—— UTF-8 码元char16_t—— UTF-16 码元char32_t—— UTF-32 码元
每种都使用自己的空终止符:
char→'\0'wchar_t→L'\0'char8_t→u8'\0'char16_t→u'\0'char32_t→U'\0'
wchar_t ws[]{L"Hello"};
char16_t u16s[]{u"Hello"};
char32_t u32s[]{U"Hello"};
char8_t u8s[]{u8"Hello"};
它们都遵循同样的规则:
- 它们是 C 风格数组
- 它们会退化为指针
- 它们必须以空终止符结尾
要点
- C 风格字符串是以
'\0'结尾的字符 C 风格数组。 - 字符串字面量是常量数组,不能被修改。
- 指向 C 风格字符串的指针请始终写成
char const*。 - C 风格字符串在表达式中会退化为指针。
strlen一直扫描到'\0',若缺少终止符就不安全。strnlen增加了最大边界,更安全。- fast_io 能打印 C 风格数组,但除非使用
os_c_str,否则不能打印指针。 - fast_io 提供了
pointervw、os_c_str与os_c_str(ptr,n)。 - 宽字符与 Unicode C 风格字符串遵循同样的规则,但使用不同的字符类型。
- C 风格字符串容易出错,因为它们不存储自己的长度。