C++ 字面量完全指南:类型、后缀与最佳实践

字面量基础概念

字面量的定义与特性

字面量 是直接插入到代码中的值。例如:

return 5;                     // 5 是一个整数字面量
bool myNameIsAlex { true };   // true 是一个布尔字面量
double d { 3.4 };             // 3.4 是一个双精度浮点字面量
std::cout << "Hello, world!"; // "Hello, world!" 是一个 C 风格字符串字面量

字面量有时被称为字面常量,因为它们的含义不能被重新定义(5 总是表示整数值 5)。

字面量类型系统

默认类型规则

就像对象有类型一样,所有字面量都有类型。字面量的类型是从字面量的值推断出来的。例如,一个整数形式的字面量(例如 5)被推断为 int 类型。

默认情况下:

字面量值示例默认字面量类型注意
整数值5, 0, -3int
布尔值true, falsebool
浮点值1.2, 0.0, 3.4double(不是 float!)
字符‘a’, ‘\n’char
C 风格字符串“Hello, world!”const char[14]见下面的 C 风格字符串字面量部分

字面量后缀系统

后缀类型与用法

如果字面量的默认类型不是你想要的,你可以通过添加后缀来改变字面量的类型。以下是一些更常见的后缀:

数据类型后缀含义
整数u 或 Uunsigned int
整数l 或 Llong
整数ul, uL, Ul, UL, lu, lU, Lu, LUunsigned long
整数ll 或 LLlong long
整数ull, uLL, Ull, ULL, llu, llU, LLu, LLUunsigned long long
整数z 或 Zstd::size_t 的有符号版本(C++23)
整数uz, uZ, Uz, UZ, zu, zU, Zu, ZUstd::size_t(C++23)
浮点数f 或 Ffloat
浮点数l 或 Llong double
字符串sstd::string
字符串svstd::string_view

后缀使用规范

大多数后缀不区分大小写。例外情况是:

  • ssv 必须是小写
  • 两个连续的 lL 字符必须具有相同的大小写

数值字面量详解

整数字面量

#include <iostream>

int main()
{
    std::cout << 5 << '\n';  // 5(无后缀)是 int 类型(默认)
    std::cout << 5L << '\n'; // 5L 是 long 类型
    std::cout << 5u << '\n'; // 5u 是 unsigned int 类型

    return 0;
}

浮点数字面量

float f { 4.1f }; // 使用 'f' 后缀,使字面量成为 float 类型
double d { 4.1 }; // 将变量类型改为 double,使其与字面量类型 double 匹配

字符串字面量系统

C风格字符串特性

所有C风格字符串字面量都有一个隐式的空终止符。考虑一个字符串,如 “hello”:

// "hello" 实际包含6个字符:
// 'h', 'e', 'l', 'l', 'o', '\0'(空终止符)

字符串字面量的生命周期

C风格字符串字面量是const对象,它们在程序开始时被创建,并在整个程序运行期间都存在。

魔法数字

魔法数字 是一个字面量(通常是数字),其含义不明确,或者可能需要在以后更改。

以下是两个包含魔法数字的语句示例:

const int maxStudentsPerSchool{ numClassrooms * 30 };
setMax(30);

在这些上下文中,字面量 30 是什么意思?在前者中,你可以猜到它是一个班的学生人数,但并不立即明显。在后者中,谁知道呢?我们需要去查看函数才知道它做什么。

在复杂的程序中,除非有注释解释,否则很难推断出字面量代表什么。

使用魔法数字通常被认为是不好的编程习惯,因为除了没有提供它们被用于什么的上下文之外,如果需要更改值,它们还会带来问题。假设学校购买了新的课桌,允许他们将班级人数从 30 增加到 35,我们的程序需要反映这一点。

为此,我们需要将一个或多个字面量从 30 更新为 35。但哪些字面量呢?maxStudentsPerSchool 初始化器中的 30 看起来很明显。但是作为 setMax() 参数的 30 呢?那个 30 与另一个 30 的含义相同吗?如果是,它应该被更新。如果不是,它应该保持不变,否则我们可能会在其他地方破坏我们的程序。如果你进行全局查找和替换,你可能会不小心更新了 setMax() 的参数,而它本不应该改变。因此,你需要查看所有代码中的每个 30 字面量实例(可能有数百个),然后分别判断它是否需要更改。这可能非常耗时(并且容易出错)。

幸运的是,上下文缺失和更新问题都可以通过使用符号常量轻松解决:

const int maxStudentsPerClass { 30 };
const int totalStudents{ numClassrooms * maxStudentsPerClass }; // 现在很明显这个 30 的含义是什么

const int maxNameLength{ 30 };
setMax(maxNameLength); // 现在很明显这个 30 是在不同的上下文中使用的

常量的名称提供了上下文,并且我们只需要在一个地方更新一个值,就可以在整个程序中更改该值。

注意,魔法数字不一定是数字——它们也可以是文本(例如名称)或其他类型:

int main()
{
    printAppWelcome("MyCalculator"); // 不好:应用程序名称可能在其他地方使用或在未来更改
}

在明显且不太可能改变的上下文中使用的字面量通常不被视为魔法数字。值 -1、0、0.0 和 1 经常在这样的上下文中使用:

int idGenerator { 0 };         // 好的:我们用值 0 启动我们的 ID 生成器
idGenerator = idGenerator + 1; // 好的:我们只是在递增我们的生成器

其他数字在上下文中也可能很明显(因此不被视为魔法数字):

int kmtoM(int km)
{
    return km * 1000; // 好的:很明显 1000 是一个换算因子
}

连续的整数 ID 通常也不被视为魔法数字:

int main()
{
    // 好的:这些只是连续的 ID/计数
    printPlayerInfo(1); // `1` 实际上从命名为 `player1` 中受益不大
    printPlayerInfo(2);
}

最佳实践

避免在代码中使用魔法数字,改用constexpr变量来提高代码的可维护性和可读性。

最佳实践与注意事项

避免魔法数字

// 不好的做法
const int maxStudentsPerSchool{ numClassrooms * 30 };

// 好的做法
const int maxStudentsPerClass { 30 };
const int totalStudents{ numClassrooms * maxStudentsPerClass };

合理使用字面量

在明显且不太可能改变的上下文中使用的字面量通常不被视为魔法数字:

int kmtoM(int km)
{
    return km * 1000; // 好的:很明显 1000 是一个换算因子
}

最佳实践

避免在代码中使用魔法数字,改用constexpr变量来提高代码的可维护性和可读性。

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

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

公众号二维码

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