C 风格字符串符号常量

在上一章《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 拥有。由于 nameconst 且从不修改,这份复制显得多余。

  • 情形 2:具体行为由实现定义。通常编译器将 "Orange" 放入只读内存,然后把指针初始化为该字符串的地址。

出于优化目的,多个值相同的字符串字面量可能被合并。例如:

const char* name1{ "Alex" };
const char* name2{ "Alex" };

二者虽为不同的字面量,但因内容相同且不可变,编译器可能把它们合并为同一段共享字符串,name1name2 指向同一地址。

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_castconst 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

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"