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;
}

image

在大多数情况下,即使初始化非整数类型时,使用无后缀的整数字面量也是可以的:

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;
}

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

在这种情况下,编译器会将整数字面量转换为适当的类型。

第一种情况中,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

image

由于 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

浮点数字面的科学记数法

浮点数字面有两种不同的书写方式。

  1. 在标准记数法中,我们用小数点表示数字:
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)
  1. 在科学记数法中,我们添加一个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 stringsC风格字符串C-style strings,因其继承自C语言。

关于C风格字符串常量,有两点非显而易见的特性值得了解:

  1. 所有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]的原因——隐藏的空终止符也计入字符数量。

使用空终止符同样具有历史渊源:它能精确定位字符串的结束位置。

  1. 与大多数其他字面量(它们是值而非对象)不同,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变量)。

posted @ 2026-02-15 14:35  游翔  阅读(0)  评论(0)    收藏  举报