Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← C++ 编程 / C++ Programming

基础语法与编译模型 / Language & Compilation

1. C++ 语言基础完整学习手册 / Complete C++ Language Basics Guide

2. C++ 语言基础与程序运行 / C++ Language Basics and Program Execution

3. C++ 程序是怎样做出来的:从源码到可执行文件 / From Source to Executable

4. 值、类型与对象:C++ 程序中的第一层现实 / Values, Types, and Objects

5. 表达式、运算符与控制流 / Expressions, Operators, and Control Flow

6. 函数、作用域与重载 / Functions, Scope, and Overloading

7. 数组、指针与字符串:从地址到借用范围 / Arrays, Pointers, and Strings

8. 引用、const 与类型推导:从复制到借用 / References, const, and Type Deduction

9. 让数据说清楚自己:枚举、结构体、联合体与类型别名 / Enums, Structs, Unions, and Aliases

10. 一个头文件怎样影响整个程序:预处理、头文件与命名空间 / Preprocessor, Headers, and Namespaces

11. 基础 I/O、错误与调试:让程序会观察自己的失败 / Basic I/O, Errors, and Debugging

本页目录

函数、作用域与重载 / Functions, Scope, and Overloading ​

本文属于 C++ 语言基础模块的“函数和名字”部分。

我们先看一个具体问题:一个函数明明只是打印数据,为什么调用它之后容器可能变慢了?

然后从这个现象逐步走到参数传递、调用栈、作用域、生命周期、ABI 和重载。

1. 先看一个可能发生的复制 ​

cpp
#include <iostream>
#include <string>
#include <vector>

void print_names(std::vector<std::string> names) {
    for (const std::string& name : names) {
        std::cout << name << '\n';
    }
}

int main() {
    std::vector<std::string> names{"Ada", "Grace", "Dennis"};
    print_names(names);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

这段程序很短。

它也很容易让人忽略一个事实:

text
main 中的 names
  |
  v
按值传给 print_names
  |
  v
函数得到一个独立的 vector 对象
  |
  v
元素通常也需要被复制
1
2
3
4
5
6
7
8
9
10

如果 names 只有三个字符串,这种复制不一定值得担心。

如果它有一千万个元素,就需要重新思考接口。

如果函数只是读取数据,可以写成:

cpp
void print_names(const std::vector<std::string>& names) {
    for (const std::string& name : names) {
        std::cout << name << '\n';
    }
}
1
2
3
4
5

这时函数借用调用者的对象:

text
调用者仍然拥有 names
  |
  v
print_names 通过 const 引用读取
  |
  v
函数返回后借用关系结束
1
2
3
4
5
6
7

这里的核心不是“引用永远比值快”。

真正的问题是:

text
函数需要独立副本吗?
函数会修改调用者吗?
函数会保存这个对象吗?
对象可以不存在吗?
1
2
3
4

函数签名其实是在描述这些契约。

2. 本文先建立一张地图 ​

text
调用者准备实参
  |
  v
编译器检查声明和类型
  |
  v
ABI 决定参数和返回值如何传递
  |
  v
被调用函数建立自己的执行环境
  |
  v
函数读取参数、创建局部对象、产生结果
  |
  v
局部对象销毁,控制权返回调用者
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

我们会在不同层次观察同一次调用:

text
C++ 语言层
  +-- 函数声明、参数、返回值、重载

对象模型层
  +-- 复制、引用、生命周期、所有权

实现层
  +-- 寄存器、栈帧、返回地址、调用约定

工程层
  +-- 接口清晰度、性能、错误路径、可测试性
1
2
3
4
5
6
7
8
9
10
11

不要把“通常使用栈传参”当成语言规则。

C++ 规定的是语义。

具体平台的 ABI 再决定这些语义怎样落到寄存器和内存。

3. 本文基础词小注释 ​

3.1 函数 ​

函数是一段有名字的可调用代码。

它接受输入,执行动作,可能产生输出:

text
输入
  |
  v
函数
  |
  v
结果或副作用
1
2
3
4
5
6
7

3.2 声明和定义 ​

声明告诉编译器某个名字和类型:

cpp
int add(int left, int right);
1

定义提供真正的实现:

cpp
int add(int left, int right) {
    return left + right;
}
1
2
3

3.3 实参和形参 ​

调用时写出的值叫实参:

cpp
add(20, 22);
1

函数声明中的名字叫形参:

cpp
int add(int left, int right);
1
text
调用者的 20      -> 形参 left
调用者的 22      -> 形参 right
1
2

3.4 作用域 ​

作用域回答:

在源码的哪一段区域内,可以通过这个名字找到它?

3.5 生命周期 ​

生命周期回答:

这个对象在运行时从什么时候开始存在,到什么时候结束?

名字的作用域和对象的生命周期有关,但不是一回事。

3.6 栈帧 ​

栈帧是一次函数调用通常需要的一块执行状态区域。

它可能包含:

text
参数
局部变量
保存的寄存器
返回位置
临时状态
1
2
3
4
5

这是实现层概念,不是 C++ 标准要求的固定布局。

3.7 ABI ​

ABI 是 Application Binary Interface,应用二进制接口。

它规定已经编译好的代码如何互相合作,例如:

text
参数放哪些寄存器
返回值放哪里
谁负责保存哪些寄存器
对象如何布局
符号如何命名
1
2
3
4
5

3.8 重载 ​

重载是同一个作用域中允许多个同名函数,只要参数列表能区分:

cpp
int area(int side);
double area(double width, double height);
1
2

编译器在编译期选择具体哪个函数。

4. 从最简单的 add 开始 ​

cpp
#include <iostream>

int add(int left, int right) {
    return left + right;
}

int main() {
    int result = add(20, 22);
    std::cout << result << '\n';
}
1
2
3
4
5
6
7
8
9
10

可以把执行过程写成:

text
main()
  |
  +-- 准备实参 20 和 22
  |
  +-- 调用 add
          |
          +-- left 取得 20
          +-- right 取得 22
          +-- 计算 left + right
          +-- 产生 42
  |
  +-- result 取得返回值 42
1
2
3
4
5
6
7
8
9
10
11
12

这里有三个值得区分的名字和对象:

text
main 的 result
add 的 left
add 的 right
1
2
3

即使 left 和 right 是由实参初始化的,它们在语言上也属于函数调用中的形参对象。

5. 先理解按值传递 ​

cpp
void increment(int value) {
    ++value;
}

int main() {
    int number = 10;
    increment(number);
    std::cout << number << '\n';
}
1
2
3
4
5
6
7
8
9

输出是:

text
10
1

因为:

text
number == 10
  |
  v
初始化一个独立的 value == 10
  |
  v
value 变成 11
  |
  v
函数返回,value 生命周期结束
  |
  v
number 仍然是 10
1
2
3
4
5
6
7
8
9
10
11
12
13

按值传递的关键语义是:

函数得到一个独立的参数对象。

对于 int,复制很便宜。

对于大型容器,复制可能很贵。

因此不能把按值传递简单说成“好”或“坏”。

要看类型和函数意图。

6. 引用传递:给函数一个别名 ​

cpp
void increment(int& value) {
    ++value;
}

int main() {
    int number = 10;
    increment(number);
    std::cout << number << '\n';
}
1
2
3
4
5
6
7
8
9

这次输出:

text
11
1

int& value 可以理解为:

text
value 不是另一个独立 int
value 是 number 的另一个名字
1
2
text
number
   ^
   |
value
1
2
3
4

函数修改 value,调用者看到的就是同一个对象被修改。

6.1 引用表达什么 ​

非常量引用通常表达:

text
调用者必须提供一个存在的对象
函数可能修改这个对象
1
2

只读引用:

cpp
void print_name(const std::string& name);
1

表达:

text
函数不通过 name 修改对象
函数不需要拥有对象
调用期间对象必须保持有效
1
2
3

7. 指针参数和引用参数不是一回事 ​

cpp
bool increment_if_present(int* value) {
    if (value == nullptr) {
        return false;
    }
    ++*value;
    return true;
}
1
2
3
4
5
6
7

调用:

cpp
int number = 10;
increment_if_present(&number);
increment_if_present(nullptr);
1
2
3

指针本身是一个对象。

它保存一个地址值,也可以保存“没有对象”的状态:

text
int* value
  |
  +-- 指向 number
  |
  +-- 或者等于 nullptr
1
2
3
4
5

引用通常必须绑定到一个有效对象:

cpp
void use(int& value);
1

不能用 nullptr 作为 int&。

7.1 三种接口的对照 ​

text
T value
  +-- 独立值
  +-- 函数可修改自己的副本

T& value
  +-- 调用者对象的别名
  +-- 通常不能为空
  +-- 可修改调用者对象

T* value
  +-- 指针值本身按值传递
  +-- 可以为空
  +-- 解引用后可修改目标对象
1
2
3
4
5
6
7
8
9
10
11
12
13

这不是只关于性能。

它是在写接口时表达不同的关系。

8. const 让借用关系更清楚 ​

cpp
void print_names(const std::vector<std::string>& names) {
    for (const std::string& name : names) {
        std::cout << name << '\n';
    }
}
1
2
3
4
5

这里的 const 约束的是:

text
不能通过这个引用修改 names
1

它不等于:

text
对象在全世界任何地方都绝对不会改变
1

基础阶段先记住接口含义:

text
const T&
  +-- 借用
  +-- 只读
  +-- 不接管所有权
1
2
3
4

9. 返回值:为什么按值返回通常安全 ​

cpp
std::string make_message() {
    std::string text = "hello";
    return text;
}
1
2
3
4

很多初学者担心:

text 是局部变量,函数结束后不是销毁了吗?

是的,局部 text 的生命周期会结束。

但按值返回的结果不是“把引用留在原来的局部变量上”。

text
函数内部 text
  |
  +-- 复制消除或移动
  v
调用者得到独立的结果对象
  |
  v
局部 text 结束
结果对象仍然存在
1
2
3
4
5
6
7
8
9

返回局部对象的引用则是错误:

cpp
const std::string& bad_message() {
    std::string text = "hello";
    return text;
}
1
2
3
4

函数返回后:

text
引用仍然保存着原来的定位关系
  |
  v
原来的 text 已经结束生命周期
  |
  v
引用悬空
1
2
3
4
5
6
7

悬空引用就是引用仍然保存着一个看似有效的定位关系,但目标对象已经不存在。

10. 调用栈:函数调用时机器需要保存什么 ​

语言层的函数调用可以映射到实现层的执行状态。

cpp
int square(int value) {
    int result = value * value;
    return result;
}

int main() {
    int answer = square(6);
}
1
2
3
4
5
6
7
8

在没有复杂优化的直觉模型中:

text
main 的执行状态
  |
  +--> 调用 square
             |
             +-- 保存返回位置
             +-- 传递 value
             +-- 计算 result
             +-- 产生返回值
  |
  +<-- 回到 main
1
2
3
4
5
6
7
8
9
10

可以画成栈帧:

text
高地址
+---------------------------+
| main 的局部状态            |
| answer                    |
+---------------------------+
| square 的局部状态          |
| value = 6                 |
| result = 36               |
| 返回位置                  |
+---------------------------+
低地址
1
2
3
4
5
6
7
8
9
10
11

这只是帮助理解的示意图。

实际实现可能把 value 和 result 放进寄存器,甚至直接把 square(6) 优化成常量 36。

11. ABI:调用者和被调用者怎样达成默契 ​

如果一个函数由一个 .cpp 文件编译,另一个函数由另一个 .cpp 文件编译,它们仍然要能够合作。

这需要一套二进制层面的约定:

text
调用者
  |
  +-- 把参数放到约定位置
  +-- 调用目标地址
  |
  v
被调用者
  +-- 按同一约定读取参数
  +-- 计算结果
  +-- 按约定放回返回值
1
2
3
4
5
6
7
8
9
10

这就是 ABI 参与的地方。

不同平台的 ABI 可能不同:

text
x86-64 Linux
  +-- 一组寄存器和调用约定

Windows x64
  +-- 另一组细节

RISC-V
  +-- 又一组寄存器和规则
1
2
3
4
5
6
7
8

所以 C++ 标准不承诺:

text
第一个参数一定放在某个寄存器
1

但编译器和平台 ABI 必须有一致约定,已编译代码才能合作。

12. 用汇编观察一个简单函数 ​

创建 square.cpp:

cpp
int square(int value) {
    return value * value;
}
1
2
3

先用最简单的命令生成汇编:

bash
g++ -S square.cpp -o square.s
1

命令拆解:

text
g++        调用 C++ 编译器
-S         停在汇编文本阶段
square.cpp 输入源码
-o         指定输出文件
square.s   汇编文件名
1
2
3
4
5

打开 square.s,不需要一开始读懂所有指令。

先找三件事:

text
函数标签在哪里
参数在哪里被使用
return 对应什么结束动作
1
2
3

再用优化级别对比:

bash
g++ -O0 -S square.cpp -o square-o0.s
g++ -O2 -S square.cpp -o square-o2.s
1
2

-O0 表示尽量不做优化,方便观察。

-O2 表示启用一组常见优化。

不同编译器生成的汇编可能不同,但函数的可观察结果必须符合 C++ 语义。

13. 作用域:名字在哪里可见 ​

cpp
int global_count = 1;

void demo(int parameter) {
    int outer = 2;

    {
        int inner = 3;
        std::cout << global_count + parameter + outer + inner << '\n';
    }

    // inner 在这里不可见
}
1
2
3
4
5
6
7
8
9
10
11
12

可以分成几层:

text
全局/命名空间作用域
  +-- global_count

函数参数作用域
  +-- parameter

函数体局部作用域
  +-- outer

内部代码块作用域
  +-- inner
1
2
3
4
5
6
7
8
9
10
11

作用域回答名字查找问题。

它不直接回答对象什么时候销毁。

14. 作用域和生命周期不是一回事 ​

cpp
int next_id() {
    static int id = 0;
    return ++id;
}
1
2
3
4

id 的名字只在函数内部可见。

但 id 对象不会每次调用都重新创建。

text
名字作用域:函数内部
对象存储期:整个程序运行期间
初始化:第一次经过声明时
1
2
3

另一个例子:

cpp
const int& get_value() {
    static int value = 42;
    return value;
}
1
2
3
4

这里返回引用是可能安全的,因为被引用的静态对象会一直存在。

但这种写法会引入隐藏状态:

text
调用顺序影响结果
测试之间可能互相影响
多线程修改需要同步
1
2
3

15. 名字遮蔽:能编译不代表好读 ​

cpp
int count = 1;

void demo() {
    int count = 2;
    std::cout << count << '\n';
}
1
2
3
4
5
6

函数里的 count 会遮蔽外层的 count。

text
使用 count
  |
  v
先找最内层作用域
  |
  +-- 找到局部 count
  |
  v
不再继续找外层同名 count
1
2
3
4
5
6
7
8
9

可以用 ::count 访问全局命名空间中的名字,但更好的工程习惯通常是减少同名遮蔽。

16. 默认参数不是运行时魔法 ​

cpp
void connect(int timeout_ms = 1000, bool retry = true);
1

调用:

cpp
connect();
connect(500);
connect(500, false);
1
2
3

可以把它理解为调用点的补全:

text
connect()
  -> connect(1000, true)

connect(500)
  -> connect(500, true)
1
2
3
4
5

默认参数通常由编译器在调用点处理。

因此库更新默认值后,已经编译好的调用方不一定自动改变。

17. 重载:编译器怎样选择同名函数 ​

cpp
void print(int value);
void print(double value);
void print(const char* text);
1
2
3

调用:

cpp
print(3);
print(3.14);
print("hello");
1
2
3

编译器不是运行时猜测。

它在编译期大致经历:

text
找到名字 print
  |
  v
收集候选函数
  |
  v
检查参数数量和类型是否可行
  |
  v
比较需要的隐式转换
  |
  v
选出唯一最佳候选
1
2
3
4
5
6
7
8
9
10
11
12
13

17.1 精确匹配优先 ​

cpp
void show(long);
void show(double);

// show(1);
1
2
3
4

1 的类型是 int。

它可以转换成 long,也可以转换成 double。

如果没有唯一最佳匹配,调用会产生二义性。

更明确的做法:

cpp
show(1L);
show(1.0);
1
2

17.2 返回类型不能区分普通重载 ​

错误:

cpp
// int parse();
// double parse();
1
2

调用者只写:

cpp
parse();
1

编译器不能靠接收结果的变量类型稳定地区分两个函数。

17.3 顶层 const 不产生值参数重载 ​

cpp
void set(int value);
// void set(const int value); // 不能作为另一个重载
1
2

因为按值传递时,形参本身是否 const 不改变调用接口的区分方式。

但指向 const 和非 const 对象的指针是不同类型:

cpp
void inspect(int* value);
void inspect(const int* value);
1
2

18. 函数指针:把行为当作值 ​

cpp
int add(int left, int right) {
    return left + right;
}

int multiply(int left, int right) {
    return left * right;
}

int apply(int (*operation)(int, int), int a, int b) {
    return operation(a, b);
}
1
2
3
4
5
6
7
8
9
10
11

调用:

cpp
int x = apply(add, 2, 3);
int y = apply(multiply, 2, 3);
1
2

这里 operation 保存一个函数地址。

text
apply
  |
  +-- operation -> add
  |
  +-- 或 operation -> multiply
1
2
3
4
5

函数指针是理解回调、事件循环和虚函数实现的基础。

19. 递归:调用栈一层层增加 ​

cpp
unsigned long long factorial(unsigned int n) {
    if (n <= 1) {
        return 1;
    }
    return n * factorial(n - 1);
}
1
2
3
4
5
6

factorial(4) 的调用关系:

text
factorial(4)
  |
  +-- 4 * factorial(3)
              |
              +-- 3 * factorial(2)
                          |
                          +-- 2 * factorial(1)
                                      |
                                      +-- 1
1
2
3
4
5
6
7
8
9

每一层调用都要保留自己的参数和返回位置。

所以递归深度太大可能耗尽调用栈。

逻辑上会终止,不等于资源上一定安全。

20. return 和局部对象销毁 ​

cpp
#include <iostream>

struct Marker {
    const char* name;

    ~Marker() {
        std::cout << "destroy " << name << '\n';
    }
};

void work() {
    Marker first{"first"};
    Marker second{"second"};
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

离开 work() 时:

text
先销毁 second
再销毁 first
1
2

局部对象通常按构造的逆序销毁。

这让资源管理可以形成:

text
进入作用域 -> 获得资源
离开作用域 -> 自动释放资源
1
2

这就是后续 RAII 学习需要的基础。

21. noexcept 和函数契约 ​

cpp
int square(int value) noexcept {
    return value * value;
}
1
2
3

noexcept 表示这个函数不应让异常传播出去。

如果异常试图逃出 noexcept 函数,程序会调用 std::terminate。

因此它是一个强承诺。

不要为了看起来高性能就到处添加。

22. [[nodiscard]]:提醒调用者不要丢结果 ​

cpp
[[nodiscard]] bool save_file();
1

如果调用者写:

cpp
save_file();
1

编译器可能提醒结果被忽略。

这适合:

text
错误码
资源句柄
解析结果
纯计算结果
1
2
3
4

23. 一个完整实验:按值、引用和指针 ​

创建 parameters.cpp:

cpp
#include <iostream>

void by_value(int value) {
    ++value;
}

void by_reference(int& value) {
    ++value;
}

void by_pointer(int* value) {
    if (value != nullptr) {
        ++*value;
    }
}

int main() {
    int number = 10;

    by_value(number);
    std::cout << "after value: " << number << '\n';

    by_reference(number);
    std::cout << "after reference: " << number << '\n';

    by_pointer(&number);
    std::cout << "after pointer: " << number << '\n';
}
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

先编译:

bash
g++ parameters.cpp -o parameters
1

再运行:

bash
./parameters
1

预期观察:

text
after value: 10
after reference: 11
after pointer: 12
1
2
3

这个实验验证的是接口语义,不是某个 ABI 的具体寄存器。

24. 一个完整实验:观察作用域和生命周期 ​

创建 lifetime.cpp:

cpp
#include <iostream>

struct Marker {
    const char* name;

    Marker(const char* value) : name(value) {
        std::cout << "construct " << name << '\n';
    }

    ~Marker() {
        std::cout << "destroy " << name << '\n';
    }
};

void demo() {
    Marker outer{"outer"};

    {
        Marker inner{"inner"};
        std::cout << "inside block\n";
    }

    std::cout << "after block\n";
}

int main() {
    demo();
}
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

观察顺序:

text
construct outer
construct inner
inside block
destroy inner
after block
destroy outer
1
2
3
4
5
6

25. 一个完整实验:观察重载选择 ​

cpp
#include <iostream>

void show(int) {
    std::cout << "int\n";
}

void show(double) {
    std::cout << "double\n";
}

void show(const char*) {
    std::cout << "text\n";
}

int main() {
    show(1);
    show(1.0);
    show("hello");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

先预测输出,再编译运行。

然后添加:

cpp
void show(long) {
    std::cout << "long\n";
}
1
2
3

重新观察 show(1) 是否仍然明确。

添加一个重载,可能改变原有调用的候选集。

26. 常见误解 ​

误解一:引用就是更安全的指针 ​

不完全是。

引用通常不能表达空值,也不能像指针那样重新绑定。

误解二:按引用传递一定更快 ​

不一定。

小型整数按值很自然,引用还可能引入别名关系。

误解三:函数返回局部对象一定危险 ​

按值返回通常安全。

返回局部对象的引用或指针才是悬空风险。

误解四:作用域结束就一定是对象销毁 ​

大多数普通局部对象确实如此,但局部 static 的对象存储期不同。

误解五:重载是在运行时选择 ​

普通重载解析发生在编译期。

误解六:调用栈就是 C++ 标准规定的栈 ​

调用栈是常见实现模型。

误解七:const 表示整个系统中对象永远不会变化 ​

const 主要限制某条访问路径。

27. 函数设计时的检查顺序 ​

text
1. 输入是独立值还是借用对象?
2. 函数是否会修改输入?
3. 参数允许为空吗?
4. 函数是否保存参数?
5. 返回值是新对象、引用还是指针?
6. 失败怎样表达?
7. 调用者是否必须检查结果?
8. 重载是否会产生隐式转换歧义?
9. 递归深度是否有边界?
10. ABI 或平台边界是否影响接口?
1
2
3
4
5
6
7
8
9
10

函数签名不是装饰。

它是调用者和实现者之间的协议。

28. 本文和编译、内存文章的连接 ​

上一篇讲的是:

text
源码
  |
  v
预处理
  |
  v
汇编
  |
  v
目标文件
  |
  v
链接
1
2
3
4
5
6
7
8
9
10
11
12
13

本文继续追问:

text
目标文件中的函数
  |
  v
调用者怎样找到它
  |
  v
参数怎样传过去
  |
  v
返回值怎样回来
1
2
3
4
5
6
7
8
9
10

下一篇数组、指针和字符串会继续追问:

text
函数参数中的地址到底是什么?
数组为什么连续?
指针为什么会悬空?
1
2
3

29. 自测题 ​

问题一 ​

按值传递后,函数修改形参会不会改变调用者的 int?

**答案:**通常不会,因为形参是独立对象。

问题二 ​

为什么 const T& 常用于只读的大对象?

**答案:**它借用调用者对象,不通过该引用修改,也避免建立完整副本。

问题三 ​

为什么不能返回局部对象的引用?

**答案:**函数返回时局部对象结束生命周期,引用会悬空。

问题四 ​

作用域和生命周期的区别是什么?

**答案:**作用域描述名字在源码中的可见区域,生命周期描述对象运行时存在的时间。

问题五 ​

重载什么时候选择?

**答案:**普通重载在编译期根据静态类型和隐式转换选择。

问题六 ​

ABI 解决什么问题?

**答案:**它规定编译好的代码如何在二进制层面传递参数、返回结果、布局对象和解析符号。

30. 本篇总结 ​

函数可以同时从四个角度理解:

text
语言角度
  +-- 名字、声明、定义、参数、返回值

对象角度
  +-- 复制、借用、修改、生命周期

机器角度
  +-- 寄存器、栈帧、返回地址、调用约定

工程角度
  +-- 接口契约、失败路径、性能、可测试性
1
2
3
4
5
6
7
8
9
10
11

最重要的学习顺序是:

text
先看函数需要什么输入和输出
  |
  v
再看参数是值、引用还是指针
  |
  v
再看对象什么时候开始和结束
  |
  v
最后再看 ABI 和汇编怎样实现
1
2
3
4
5
6
7
8
9
10

不要把“按值、按引用、按指针”背成性能口诀。

先问它们表达什么关系:

text
值       独立副本
引用     必须存在的借用/别名
指针     可为空的地址值
1
2
3

上一篇:表达式、运算符与控制流 | 下一篇:数组、指针与字符串

最后更新于:

Pager
上一篇5. 表达式、运算符与控制流 / Expressions, Operators, and Control Flow
下一篇7. 数组、指针与字符串:从地址到借用范围 / Arrays, Pointers, and Strings

持续记录,持续成长

Copyright © Tidenflow