在上一章《C 风格字符串》中,我们讨论了如何创建并初始化 C 风格字符串对象:
#include <iostream>
int main()
{
char name[]{ "Alex" }; // C 风格字符串
std::cout << name << '\n';
return 0;
}
C++ 支持两种方式来创建 C 风格字符串符号常量:
#include <iostream>
int main()
{
const char name[] { "Alex" }; // 情形 1:用 C 风格字符串字面量初始化 const C 风格字符串
const char* const color{ "Orange" }; // 情形 2:指向 C 风格字符串字面量的 const 指针
std::cout << name << ' ' << color << '\n';
return 0;
}
输出:
Alex Orange
两种写法效果相同,但 C++ 在内存分配上略有差异。
情形 1:字面量
"Alex"
被置于(通常是只读的)全局存储区;随后程序再为长度为 5 的const char
数组(4 个显式字符加空字符终止符)分配内存并复制"Alex"
。于是出现两份"Alex"
:一份在全局区,一份由name
拥有。由于name
是const
且从不修改,这份复制显得多余。情形 2:具体行为由实现定义。通常编译器将
"Orange"
放入只读内存,然后把指针初始化为该字符串的地址。
出于优化目的,多个值相同的字符串字面量可能被合并。例如:
const char* name1{ "Alex" };
const char* name2{ "Alex" };
二者虽为不同的字面量,但因内容相同且不可变,编译器可能把它们合并为同一段共享字符串,name1
与 name2
指向同一地址。
const C 风格字符串的类型推导
使用 C 风格字符串字面量进行类型推导时:
auto s1{ "Alex" }; // 推导出 const char*
auto* s2{ "Alex" }; // 推导出 const char*
auto& s3{ "Alex" }; // 推导出 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 退化为 int*
std::cout << carr << '\n'; // carr 退化为 char*
std::cout << ptr << '\n'; // ptr 本身就是 char*
return 0;
}
作者机器输出:
003AF738
Hello!
Alex
为何 int 数组打印地址,而字符数组打印字符串?
原因在于输出流(如 std::cout
)会按类型推断意图:
- 若传入非
char*
指针,直接打印地址。 - 若传入
char*
或const char*
,则假定要输出字符串,于是打印所指向的字符序列而非地址。
这一行为通常符合预期,但也可能导致意外:
#include <iostream>
int main()
{
char c{ 'Q' };
std::cout << &c; // 意图打印地址,但 &c 为 char*,被当作字符串
return 0;
}
输出(示例):
Q╠╠╠╠╜╡4;¿■A
原因:std::cout
把 &c
当作 C 风格字符串,打印 Q
后继续读取后续内存,直到遇到 0 字节(空字符)才停止。实际输出取决于内存内容。
若要打印 char*
本身的地址,需将其 static_cast
为 const void*
:
#include <iostream>
int main()
{
const char* ptr{ "Alex" };
std::cout << ptr << '\n'; // 作为字符串输出
std::cout << static_cast<const void*>(ptr) << '\n'; // 输出指针地址
return 0;
}
相关内容:void*
将在《空指针》讲解,此处无需深究即可使用。
优先使用 std::string_view
作为 C 风格字符串符号常量
现代 C++ 几乎无须使用 C 风格字符串符号常量。应改用 constexpr std::string_view
,其性能通常等同甚至更优,且行为更为一致。
最佳实践
避免 C 风格字符串符号常量,优先使用 constexpr std::string_view
。