在上一章《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。
