章节目录

第五章总结

本节阅读量:

章节回顾

常量是在程序执行期间不能更改的值。C++ 支持两种类型的常量:命名常量和字面值常量。

命名常量是与某个标识符相关联的常量值。常变量就是一种命名常量,带替换文本的类对象宏也是。

字面值常量则是与标识符无关的常量值。

值不能更改的变量称为常变量。const 关键字可用于将变量声明为常量。常变量必须被初始化。按值传递或按值返回时应避免使用 const。

类型限定符是用于修改类型行为的关键字。截至 C++23,C++ 仅支持 const 和 volatile 作为类型限定符。

常量表达式是可以在编译时求值的表达式。必须在运行时求值的表达式有时被称为运行时表达式。

编译时常量的值在编译时就已知;运行时常量则是初始化值要到运行时才能确定的常量。

constexpr 变量必须是编译时常量,并用常量表达式来初始化。函数参数不能声明为 constexpr。

字面值是直接写在代码中的值。字面值具有类型,并且可以通过字面值后缀将默认类型更改为对应的其他类型。

魔数是指那些字面含义不明确或未来可能需要修改的字面值(通常是数字)。不要在代码中使用魔数,应改用符号常量。

在日常生活中,我们使用十进制计数,十进制使用 0 到 9 这 10 个数字。计算机使用二进制,只使用 0 和 1 两个数字。C++ 还支持八进制(基数为 8)和十六进制(基数为 16)。

条件运算符(? :)(有时也称为算术 if 运算符)是一个三元运算符(接受 3 个操作数的运算符)。其形式为:条件表达式 c ? x : y,如果条件 c 的结果为 true,则计算 x,否则计算 y。条件运算符通常需要用括号括起来:

  1. 如果条件运算符作为子表达式使用,请用圆括号括起整个条件运算符。
  2. 为了提高可读性,若条件表达式中包含任何运算符(函数调用运算符除外),请用括号将其括起来。

函数的内联展开会在调用点将函数调用替换为函数体中的代码。使用 inline 关键字声明的函数称为内联函数。

内联函数和内联变量有两个主要要求:

  1. 编译器必须能够在使用该函数或变量的每一个翻译单元中看到内联函数或变量的完整定义(仅有前向声明是不够的)。
  2. 同一个内联函数或变量的每一份定义必须完全相同,否则会导致未定义行为。

在现代 C++ 中,inline 一词的含义已经演变为“允许多个定义”。因此,内联函数就是允许在多个文件中定义的函数。C++17 引入了内联变量,它允许变量在多个文件中被定义。

内联函数和内联变量对仅含头文件的库特别有用。

constexpr 函数是指其返回值可以在编译时计算的函数。要将一个函数声明为 constexpr 函数,只需在返回类型前加上 constexpr 关键字即可。constexpr 函数只有在需要常量表达式的上下文中被使用时,才一定会在编译时求值。否则,它可能会在编译时求值(如果符合条件),也可能会在运行时求值。

consteval 函数是必须在编译时求值的函数。

constexpr 函数和 consteval 函数都是隐式内联的。

字符串是由一系列连续字符组成的集合,用来表示文本(如名字、单词和句子)。字符串字面值总是用双引号括起来。C++ 中的字符串字面值是 C 风格字符串,它们具有一种奇怪且难以使用的类型。

std::string 提供了一种简单、安全的处理文本字符串的方式。std::string 位于 <string> 头文件中。std::string 的初始化和复制开销都较大。

std::string_view 提供了对已有字符串(C 风格字符串字面值、std::string 或 char 数组)的只读访问能力,且不会进行复制。查看已销毁字符串的 std::string_view 有时被称为悬空视图。当 std::string 被修改时,指向该 std::string 的所有视图都会失效。使用失效的视图会导致未定义行为。

由于 C 风格字符串字面值在整个程序运行期间都有效,因此可以把 std::string_view 赋值为 C 风格字符串字面值,甚至可以从函数中返回这样的 std::string_view。

子字符串是已有字符串中一段连续的字符序列。


习题

问题 1

为什么命名常量往往比字面值常量更好?

查看答案

在程序中使用字面值常量(也就是所谓的魔术数字)会让程序更难理解、更难修改。符号常量有助于表明这些数字实际代表什么,而且在声明处修改一个符号常量,就能同时改变它被使用的所有位置。

为什么const/constexpr变量通常比用#define定义的符号常量更好?

查看答案

#define常量不会出现在调试器中,而且更容易发生命名冲突。

问题 2

找出下面代码中的3个问题:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <cstdint> // std::uint8_t
#include <iostream>

int main()
{
  std::cout << "How old are you?\n";

  std::uint8_t age{};
  std::cin >> age;

  std::cout << "Allowed to drive a car in Texas: ";

  if (age >= 16)
      std::cout << "Yes";
  else
      std::cout << "No";

  std::cout << '.\n';

  return 0;
}

期望的输出示例:

How old are you?
6
Allowed to drive a car in Texas: No
How old are you?
19
Allowed to drive a car in Texas: Yes
查看答案
  1. 第8行中,age被定义为std::uint8_t。由于std::uint8_t通常被定义为char类型,在这里使用它会让程序表现得像是在输入输出一个char值而不是数值。例如,如果用户输入年龄「18」,只会提取字符'1'。由于1的ASCII值是49,用户会被当成49岁。存储年龄应当使用普通的int,因为年龄并不需要某个特定的最小整数宽度。同时还可以删掉#include <cstdint>。
  2. 第13行使用了魔术数字16。虽然从上下文能看出16的含义,但更推荐使用值为16的constexpr变量。
  3. 第18行中,'.\n'是多字符字面值,会打印出错误的值。它应当使用双引号(".\n")。

问题 3

std::string与std::string_view的主要区别是什么?

使用std::string_view时可能会出什么问题?

查看答案

std::string提供的是可修改的字符串,它的初始化和拷贝开销较大。

std::string_view提供的是对存在于其他地方的字符串的只读视图,它的初始化和拷贝开销很小。当被观察的字符串在观察它的std::string_view之前被销毁时,std::string_view就可能出问题。

问题 4

编写一个程序,要求输入两个人的姓名和年龄,然后打印出谁更年长。

下面是程序某次运行的输出示例:

Enter the name of person #1: John Bacon
Enter the age of John Bacon: 37
Enter the name of person #2: David Jenkins
Enter the age of David Jenkins: 44
David Jenkins (age 44) is older than John Bacon (age 37).
显示提示

使用std::getline()输入姓名。

查看答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <string>
#include <string_view>

std::string getName(int num)
{
    std::cout << "Enter the name of person #" << num << ": ";
    std::string name{};
    std::getline(std::cin >> std::ws, name); // 把一整行文本读入 name

    return name;
}

int getAge(std::string_view sv)
{
    std::cout << "Enter the age of " << sv << ": ";
    int age{};
    std::cin >> age;

    return age;
}

void printOlder(std::string_view name1, int age1, std::string_view name2, int age2)
{
    if (age1 > age2)
        std::cout << name1 << " (age " << age1 <<") is older than " << name2 << " (age " << age2 <<").\n";
    else
        std::cout << name2 << " (age " << age2 <<") is older than " << name1 << " (age " << age1 <<").\n";
}

int main()
{
    const std::string name1{ getName(1) };
    const int age1 { getAge(name1) };

    const std::string name2{ getName(2) };
    const int age2 { getAge(name2) };

    printOlder(name1, age1, name2, age2);

    return 0;
}

问题 5

在上面这道题的答案中,为什么main中的变量age1不能声明为constexpr?

查看答案

constexpr变量要求初始化值是常量表达式,而对getAge()的调用不允许出现在常量表达式中,因此这个变量只能声明为const。


5.10 std::string_view(第2部分)

上一节

6.0 运算符优先级和结合性

下一节