17-11 C 风格字符串符号常量
在上节课(17.10 -- C 风格字符串)中,我们讨论了如何创建和初始化 C 风格字符串对象:
#include <iostream>
int main()
{
char name[]{ "Alex" }; // C-style string
std::cout << name << '\n';
return 0;
}
C++支持两种不同的方式来创建C风格字符串符号常量:
#include <iostream>
int main()
{
const char name[] { "Alex" }; // case 1: const C-style string initialized with C-style string literal
const char* const color{ "Orange" }; // case 2: const pointer to C-style string literal
std::cout << name << ' ' << color << '\n';
return 0;
}
这将输出:

虽然上述两种方法产生相同的结果,但C++对内存分配的处理略有不同。
在情况1中,“Alex”被存入某个(可能是只读的)内存位置。随后程序为长度为5的C风格数组分配内存(四个显式字符加空终止符),并将字符串“Alex”初始化到该内存区域。最终形成两个“Alex”副本——一个位于全局内存某处,另一个由name持有。由于name是const类型(且永不修改),创建副本效率低下。
在情况2中,编译器的处理方式取决于具体实现。通常编译器会将字符串“Orange”存入某个只读内存区域,再用该字符串的地址初始化指针。
出于优化目的,多个字符串常量可能被合并为单一值。例如:
const char* name1{ "Alex" };
const char* name2{ "Alex" };
这两个字符串常量具有相同的值。由于它们是常量,编译器可能会选择将它们合并为一个共享的字符串常量以节省内存,使得 name1 和 name2 都指向相同的地址。
使用常量C风格字符串进行类型推断
使用C风格字符串字面量进行类型推断相当直接:
auto s1{ "Alex" }; // type deduced as const char*
auto* s2{ "Alex" }; // type deduced as const char*
auto& s3{ "Alex" }; // type deduced as const char(&)[5]
输出指针和C风格字符串
你可能注意到std::cout处理不同类型指针的方式存在一个有趣现象。
请看以下示例:
#include <iostream>
int main()
{
int narr[]{ 9, 7, 5, 3, 1 };
char carr[]{ "Hello!" };
const char* ptr{ "Alex" };
std::cout << narr << '\n'; // narr will decay to type int*
std::cout << carr << '\n'; // carr will decay to type char*
std::cout << ptr << '\n'; // name is already type char*
return 0;
}
在作者的机器上,这段代码输出如下:

为什么整型数组输出的是地址,而字符数组输出的是字符串?
答案在于输出流(如 std::cout)会对你的意图做出某些假设。若传递非字符指针,它会直接输出该指针的内容(即指针所指向的地址)。然而,若传递的是 char* 或 const char* 类型的对象,输出流会默认你意图打印字符串。因此它不会输出指针值(地址),而是输出被指向的字符串内容!
虽然这种行为在多数情况下符合预期,但也可能导致意外结果。请考虑以下情况:
#include <iostream>
int main()
{
char c{ 'Q' };
std::cout << &c;
return 0;
}
在此情况下,程序员本意是打印变量 c 的地址。然而 &c 的类型为 char*,因此 std::cout 试图将其作为字符串输出!由于 c 未以空字符终止,导致出现未定义行为。
在作者的机器上,这段代码输出:

为什么会这样?首先,它将&c(类型为char*)视为C风格字符串。因此它先输出‘Q’,然后继续执行。内存中接下来是一堆乱码。最终,它遇到存储0值的内存区域,将其解释为空终止符,于是停止了输出。你看到的输出可能因变量c之后的内存内容而不同。
这种情况在实际应用中不太可能发生(毕竟你通常不会想打印内存地址),但它生动地展示了底层机制如何运作,以及程序如何因疏忽而偏离正轨。
若你确实需要打印字符指针的地址,请将其静态转换为 const void* 类型:
#include <iostream>
int main()
{
const char* ptr{ "Alex" };
std::cout << ptr << '\n'; // print ptr as C-style string
std::cout << static_cast<const void*>(ptr) << '\n'; // print address held by ptr
return 0;
}

相关内容:
我们在第19.5节——空指针中讲解了void*。在此处使用它时无需了解其工作原理。
优先使用 std::string_view 替代 C 风格字符串符号常量
在现代 C++ 中使用 C 风格字符串符号常量的理由已不充分。建议改用 constexpr std::string_view 对象,其运行速度通常相当(甚至更快),且行为更一致。
最佳实践:
避免使用 C 风格字符串符号常量,优先采用 constexpr std::string_view。

浙公网安备 33010602011771号