5-2 字面量
字面量Literals是直接插入代码中的值。例如:
return 5; // 5 is an integer literal
bool myNameIsAlex { true }; // true is a boolean literal
double d { 3.4 }; // 3.4 is a double literal
std::cout << "Hello, world!"; // "Hello, world!" is a C-style string literal
字面量有时被称为字面常量literal constants,因为它们的含义无法被重新定义(5永远表示整数值5)。
字面量的类型
正如对象具有类型,所有字面量也具有类型。字面量的类型由其值推断得出。例如,整数字面量(如 5)会被推断为 int 类型。
默认情况下:
| Literal value | Examples | Default literal type | Note |
|---|---|---|---|
| integer value | 5, 0, -3 | int | |
| boolean value | true, false | bool | |
| floating point value | 1.2, 0.0, 3.4 | double (not float!) | |
| character | ‘a’, ‘\n’ | char | |
| C-style string | “Hello, world!” | const char[14] | see C-style string literals section below |
字面量后缀
若字面量的默认类型不符合预期,可通过添加后缀来修改其类型。以下是一些常见的后缀:
| Data type | Suffix | Meaning |
|---|---|---|
| integral | u or U | unsigned int |
| integral | l or L | long |
| integral | ul, uL, Ul, UL, lu, lU, Lu, LU | unsigned long |
| integral | ll or LL | long long |
| integral | ull, uLL, Ull, ULL, llu, llU, LLu, LLU | unsigned long long |
| integral | z or Z | The signed version of std::size_t (C++23) |
| integral | uz, uZ, Uz, UZ, zu, zU, Zu, ZU | std::size_t (C++23) |
| floating point | f or F | float |
| floating point | l or L | long double |
| string | s | std::string |
| string | sv | std::string_view |
我们将稍后详细讨论整数的和浮点数字面量及其后缀。
在大多数情况下,后缀并非必需(f除外)。
相关内容
使用 s 和 sv 后缀需要额外添加一行代码。我们将在第 5.7 节——std::string 介绍和第 5.8 节——std::string_view 介绍中进一步讲解这些内容。
另存在用于复数和 chrono(时间)字面量的附加(罕用)后缀,相关文档详见此处。
对于进阶读者
除 f 后缀外,其他后缀多用于涉及类型推导的场景。详见第 10.8 节——使用 auto 关键字的对象类型推导,以及第 13.14 节——类模板实参推导(CTAD)与推导指南。
后缀大小写规则
大多数后缀不区分大小写。例外情况如下:
- s 和 sv 必须为小写。
- 连续两个 l 或 L 字符必须保持统一大小写(lL 和 Ll 均不被接受)。
由于某些字体中小写 L 可能与数字 1 混淆,部分开发者倾向使用大写字母 L。另一些则采用小写后缀(除 L 外)。
最佳实践
建议优先使用大写字母 L 作为后缀,而非小写 l。
整数字面量
通常无需为整数字面量添加后缀,但以下是示例:
#include <iostream>
int main()
{
std::cout << 5 << '\n'; // 5 (no suffix) is type int (by default)
std::cout << 5L << '\n'; // 5L is type long
std::cout << 5u << '\n'; // 5u is type unsigned int
return 0;
}

在大多数情况下,即使初始化非整数类型时,使用无后缀的整数字面量也是可以的:
int main()
{
int a { 5 }; // ok: types match
unsigned int b { 6 }; // ok: compiler will convert int value 6 to unsigned int value 6
long c { 7 }; // ok: compiler will convert int value 7 to long value 7
return 0;
}

(两个方法:暂时取消"将警告视为错误", 在各自变量前添加[[myabe_unused]]关键字(C++17))

在这种情况下,编译器会将整数字面量转换为适当的类型。
第一种情况中,5默认已是整数类型,因此编译器可直接使用该值初始化整型变量a。在第二种情况下,int类型数值6与unsigned int类型变量b的类型不匹配。编译器会将int类型数值6转换为unsigned int类型数值6,再将其作为b的初始化值。在第三种情况下,int类型数值7与long类型变量c的类型不匹配。编译器会将int类型数值7转换为long类型数值7,再将其作为c的初始化值。
浮点数字面量
默认情况下,浮点数字面量的类型为double。若要将其转换为float类型,需添加f(或F)后缀:
#include <iostream>
int main()
{
std::cout << 5.0 << '\n'; // 5.0 (no suffix) is type double (by default)
std::cout << 5.0f << '\n'; // 5.0f is type float
return 0;
}
新手程序员常常困惑于以下代码为何会引发编译器警告:
float f { 4.1 }; // warning: 4.1 is a double literal, not a float literal

由于 4.1 没有后缀,该字面量类型为 double,而非 float。编译器确定字面量类型时,不会考虑你对字面量的具体操作(例如本例中用其初始化 float 变量)。由于字面量的类型(double)与被初始化变量的类型(float)不匹配,必须将字面量值转换为float才能用于初始化变量f。将double值转换为float可能导致精度损失,因此编译器会发出警告。
解决方案如下:
float f { 4.1f }; // use 'f' suffix so the literal is a float and matches variable type of float
double d { 4.1 }; // change variable to type double so it matches the literal type double
浮点数字面的科学记数法
浮点数字面有两种不同的书写方式。
- 在标准记数法中,我们用小数点表示数字:
double pi { 3.14159 }; // 3.14159 is a double literal in standard notation
double d { -1.23 }; // the literal can be negative
double why { 0. }; // syntactically acceptable, but avoid this because it's hard to see the decimal point (prefer 0.0)
- 在科学记数法中,我们添加一个e来表示指数:
double avogadro { 6.02e23 }; // 6.02 x 10^23 is a double literal in scientific notation
double protonCharge { 1.6e-19 }; // charge on a proton is 1.6 x 10^-19
字符串常量
在编程中,字符串string是一组顺序排列的字符,用于表示文本(如名称、单词和句子)。
你编写的第一个C++程序可能类似于这样:
#include <iostream>
int main()
{
std::cout << "Hello, world!";
return 0;
}
“Hello, world!” 是一个字符串字面量。字符串字面量用双引号括起以标识其字符串属性(与用单引号括起的字符字面量相对)。
由于字符串在程序中使用频繁,大多数现代编程语言都包含基础的字符串数据类型。出于历史原因,字符串在C++中并非基本类型。相反,它们具有一种奇怪而复杂的类型,难以操作(我们将在后续课程中讲解其工作原理所需的基础知识后,详细说明原因)。这类字符串常被称为C字符串C strings或C风格字符串C-style strings,因其继承自C语言。
关于C风格字符串常量,有两点非显而易见的特性值得了解:
- 所有C风格字符串常量都隐含一个空终止符。以字符串“hello”为例,虽然这个C风格字符串看似只有五个字符,但实际上包含六个字符:‘h’、'e'、‘l’、'l'、‘o'以及’\0'(ASCII码为0的字符)。这个尾随的‘\0’字符是特殊字符,称为空终止符
null terminator,用于标记字符串结束位置。以空终止符结尾的字符串称为空终止字符串null-terminated string。
对于进阶读者
这正是字符串“Hello, world!”类型为const char[14]而非const char[13]的原因——隐藏的空终止符也计入字符数量。
使用空终止符同样具有历史渊源:它能精确定位字符串的结束位置。
- 与大多数其他字面量(它们是值而非对象)不同,C 风格字符串字面量是常量对象,在程序开始时创建并保证在整个程序运行期间存在。这一特性将在后续课程讨论 std::string_view 时显得尤为重要。
核心要点
C 风格字符串常量是程序启动时创建的 const 对象,其存在性在整个程序运行期间得到保证。
与 C 风格字符串常量不同,std::string 和 std::string_view 常量会创建临时对象。这些临时对象必须立即使用,因为它们将在创建它们的完整表达式结束时被销毁。
相关内容
我们在第5.7节——std::string介绍 和第5.8节——std::string_view介绍 中分别讨论了std::string和std::string_view字面量。
魔数
魔数是指含义不明确或可能需要后续修改的字面量(通常为数字)。
以下两条语句展示了魔数的示例:
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 }; // now obvious what this 30 means
const int maxNameLength{ 30 };
setMax(maxNameLength); // now obvious this 30 is used in a different context
常量名称提供了上下文信息,我们只需在单一位置更新值,即可在整个程序中同步修改该值。
需注意魔数并非总是数字——它们也可以是文本(如名称)或其他类型:
int main()
{
printAppWelcome("MyCalculator"); // bad: app name may be used in other places or change in the future
}
在明显上下文中使用的字面量,且不太可能发生变化时,通常不被视为魔数。值 -1、0、0.0 和 1 常用于此类上下文:
int idGenerator { 0 }; // okay: we're starting our id generator with value 0
idGenerator = idGenerator + 1; // okay: we're just incrementing our generator
其他数字在上下文中也可能显而易见(因此不被视为魔术数字):
int kmtoM(int km)
{
return km * 1000; // okay: it's obvious 1000 is a conversion factor
}
顺序整数标识符通常也不被视为魔法:
int main()
{
// okay: these are just sequential ids/counts
printPlayerInfo(1); // `1` would not really benefit from being named `player1` instead
printPlayerInfo(2);
}
最佳实践
避免在代码中使用魔数(改用constexpr变量,参见第5.6课——Constexpr变量)。

浙公网安备 33010602011771号