编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • C语言入门精通

  • Cpp入门到精通

    • README
    • 入门教程

      • README
      • Cpp简史
      • 基础语法
      • 数据类型
      • 运算符
      • 复合类型
      • 流程语句
      • 函数
      • 指针引用
        • 8.1 指针概念
          • 8.1.1 什么是指针
          • 8.1.2 指针声明
          • 8.1.3 指针初始化
          • 8.1.4 指针占用空间
          • 8.1.5 综合案例与思考
        • 8.2 指针基本操作
          • 8.2.1 取地址运算符
          • 8.2.2 解引用运算符
          • 8.2.3 综合案例与思考
        • 8.3 指针使用
          • 8.3.1 指针和数组
          • 8.3.2 指针和函数
          • 8.3.3 指针与常量
          • 8.3.4 指针和结构体
          • 8.3.5 综合案例与思考
        • 8.4 指针高级用法
          • 8.4.1 指针的指针
          • 8.4.2 函数指针
          • 8.4.3 综合案例与思考
        • 8.5 引用
          • 8.5.1 什么是引用
          • 8.5.2 引用声明
          • 8.5.3 引用必须初始化
          • 8.5.4 引用访问数据
          • 8.5.5 引用和函数
          • 8.5.6 常量引用
          • 8.5.7 避免返回局部变量引用
          • 8.5.8 综合案例与思考
        • 8.6 指针VS引用
          • 8.6.1 两者对比
          • 8.6.2 两者转换
          • 8.6.3 综合案例与思考
        • 8.8 综合案例
          • 8.8.1 遍历数组案例
          • 8.8.2 指针vs普通对象
          • 8.8.3 交换两个变量值
        • 8.9 指针与引用底层原理
          • 8.9.1 指针的内存模型与寻址
          • 8.9.2 指针运算的汇编实现
          • 8.9.3 引用的底层本质
          • 8.9.4 const指针与const引用的编译器处理
        • 8.10 指针与引用训练题
        • 8.11 综合思考题
        • 8.12 新手陷阱 Top 5
      • 类和对象
      • 继承多态
      • 内存模型
      • 动态内存
      • IO和文件
      • 异常处理
      • 线程和锁
      • STL模版
      • 预处理器
      • 特性图谱
    • 综合案例

    • 专栏博客

    • 开发技巧

  • Java入门精通

  • Go入门到精通

  • JavaScript入门

  • CodeX
  • Cpp入门到精通
  • 入门教程
杨充
2026-05-07
目录

指针引用

# 第 8 章 C++ 指针引用

# 目录介绍

  • 8.1 指针概念
    • 8.1.1 什么是指针
    • 8.1.2 指针声明
    • 8.1.3 指针初始化
    • 8.1.4 指针占用空间
    • 8.1.5 综合案例与思考
  • 8.2 指针操作
    • 8.2.1 取地址运算符
    • 8.2.2 解引用运算符
    • 8.2.3 综合案例与思考
  • 8.3 指针使用
    • 8.3.1 指针和数组
    • 8.3.2 指针和函数
    • 8.3.3 指针与常量
    • 8.3.4 指针和结构体
    • 8.3.5 综合案例与思考
  • 8.4 指针高级用法
    • 8.4.1 指针的指针
    • 8.4.2 函数指针
    • 8.4.3 综合案例与思考
  • 8.5 引用
    • 8.5.1 什么是引用
    • 8.5.2 引用声明
    • 8.5.3 引用必须初始化
    • 8.5.4 引用访问数据
    • 8.5.5 引用和函数
    • 8.5.6 常量引用
    • 8.5.7 避免返回局部变量引用
    • 8.5.8 综合案例与思考
  • 8.6 指针VS引用
    • 8.6.1 两者对比
    • 8.6.2 两者转换
    • 8.6.3 综合案例与思考
  • 8.7 总结
  • 8.8 综合案例
    • 8.8.1 遍历数组案例
    • 8.8.2 指针vs普通对象
    • 8.8.3 交换两个变量值
  • 8.9 指针与引用底层原理
    • 8.9.1 指针的内存模型与寻址
    • 8.9.2 指针运算的汇编实现
    • 8.9.3 引用的底层本质
    • 8.9.4 const指针与const引用的编译器处理
  • 8.10 指针与引用训练题
  • 8.11 综合思考题

# 8.1 指针概念

# 8.1.1 什么是指针

指针的作用: 可以通过指针间接访问内存

  • 内存编号是从0开始记录的,一般用十六进制数字表示
  • 可以利用指针变量保存地址

指针,它存储了一个内存地址。指针可以指向其他变量或对象的内存地址,通过指针,可以直接访问和操作内存中的数据。

# 8.1.2 指针声明

指针变量定义语法:

数据类型 *指针变量名;
1
  • 数据类型:指针指向的变量的类型(如 int、double 等)。
  • *:表示这是一个指针变量。
  • 指针变量名:指针的名称。

# 8.1.3 指针初始化

指针在使用前必须初始化,否则会指向一个未知的内存地址,可能导致程序崩溃。

int *ptr = nullptr; // 初始化为空指针
1

看如下案例所示:

int main() {
    int a = 10; //定义整型变量a
    //1、指针的定义
    //指针定义语法: 数据类型 * 变量名 ;
    int * p;
    //初始化,指针变量赋值
    p = &a; //指针指向变量a的地址
    cout << &a << endl; //打印数据a的地址
    cout << p << endl;  //打印指针变量p

    //2、指针的使用
    //通过*操作指针变量指向的内存
    cout << "*p = " << *p << endl;
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

指针变量和普通变量的区别

  • 普通变量存放的是数据,指针变量存放的是地址
  • 指针变量可以通过" * "操作符,操作指针变量指向的内存空间,这个过程称为解引用
  1. 总结1:我们可以通过 & 符号 获取变量的地址
  2. 总结2:利用指针可以记录地址
  3. 总结3:对指针变量解引用,可以操作指针指向的内存

# 8.1.4 指针占用空间

提问:指针也是种数据类型,那么这种数据类型占用多少内存空间?示例:

int main() {
    int a = 10;
    int * p;
    p = &a; //指针指向数据a的地址

    cout << *p << endl; //* 解引用
    cout << sizeof(p) << endl;
    cout << sizeof(char *) << endl;
    cout << sizeof(float *) << endl;
    cout << sizeof(double *) << endl;\
    return 0;
}
//10
//8
//8
//8
//8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

在 C++ 中,指针的大小取决于编译器和操作系统的位数。通常情况下,指针在 C++ 中也会占用一定的内存空间,这个空间大小与系统的位数相关。

在大多数现代计算机系统中,指针的大小通常如下:

32 位系统:在 32 位系统中,指针通常占用 4 个字节(32 位)的内存空间。

64 位系统:在 64 位系统中,指针通常占用 8 个字节(64 位)的内存空间。

# 8.1.5 综合案例与思考

综合案例:指针的声明、初始化与基本操作

#include <iostream>
using namespace std;

int main() {
    // 1. 指针的声明和初始化
    int a = 42;
    int* p1 = &a;     // 指向a的指针
    int* p2 = nullptr; // 空指针(安全初始化)

    cout << "=== 指针基础 ===" << endl;
    cout << "a的值: " << a << endl;
    cout << "a的地址: " << &a << endl;
    cout << "p1存储的地址: " << p1 << endl;
    cout << "p1指向的值: " << *p1 << endl;

    // 2. 通过指针修改值
    *p1 = 100;
    cout << "\n修改后a的值: " << a << endl;  // 100

    // 3. 指针的大小(与类型无关)
    cout << "\n=== 指针大小 ===" << endl;
    cout << "int*:    " << sizeof(int*) << " 字节" << endl;
    cout << "double*: " << sizeof(double*) << " 字节" << endl;
    cout << "char*:   " << sizeof(char*) << " 字节" << endl;

    // 4. 空指针检查
    cout << "\n=== 空指针检查 ===" << endl;
    if (p2 == nullptr) {
        cout << "p2是空指针,不能解引用" << endl;
    }
    // *p2 = 10;  // 危险!解引用空指针会崩溃

    // 5. 野指针的危险
    cout << "\n=== 野指针警告 ===" << endl;
    int* wild;  // 未初始化!指向未知地址
    // *wild = 10;  // 极其危险!未定义行为
    cout << "永远不要使用未初始化的指针!" << endl;

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

案例知识融合:这个案例覆盖了指针概念的核心知识——指针的声明与初始化、取地址&和解引用*、通过指针修改变量值、指针大小与系统位数相关(而非类型)、空指针nullptr的安全检查,以及野指针的危险性。

思考题:

  1. int* p 和 int *p 和 int * p 三种写法有区别吗?在多变量声明int* p, q;中,q是什么类型?
  2. nullptr(C++11)比NULL和0有什么优势?为什么C++11要引入新关键字?
  3. 野指针和空指针哪个更危险?为什么?如何养成良好的指针使用习惯?

# 8.2 指针基本操作

# 8.2.1 取地址运算符

& 用于获取变量的内存地址。

int num = 10;
int *ptr = &num; // ptr 指向 num 的地址
1
2

# 8.2.2 解引用运算符

* 用于访问指针指向的内存地址中的值。

int num = 10;
int *ptr = &num;
cout << *ptr; // 输出 10
1
2
3

# 8.2.3 综合案例与思考

综合案例:指针运算与地址算术

#include <iostream>
using namespace std;

int main() {
    int arr[] = {10, 20, 30, 40, 50};
    int* p = arr;

    // 1. 指针算术:p+1不是加1字节,而是加一个元素大小
    cout << "=== 指针算术 ===" << endl;
    for (int i = 0; i < 5; ++i) {
        cout << "*(p+" << i << ") = " << *(p + i)
             << "  地址: " << (p + i) << endl;
    }
    // 两个相邻元素的地址差 = sizeof(int) = 4字节
    cout << "地址差: " << (long)((p + 1)) - (long)(p) << " 字节" << endl;

    // 2. 指针比较
    cout << "\n=== 指针比较 ===" << endl;
    int* begin = arr;
    int* end = arr + 5;  // 指向数组尾后位置
    cout << "数组范围: [" << begin << ", " << end << ")" << endl;
    cout << "begin < end? " << (begin < end) << endl;

    // 3. 指针递增遍历
    cout << "\n=== 指针遍历 ===" << endl;
    for (int* it = begin; it != end; ++it) {
        cout << *it << " ";
    }
    cout << endl;

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

案例知识融合:这个案例演示了取地址&和解引用*之外的指针操作——指针算术(p+i跳过i个元素而非i字节)、指针差值(地址差等于元素大小×元素个数)、指针比较(用于判断范围)、以及用指针递增实现类似迭代器的数组遍历。

思考题:

  1. p + 1实际上在地址上增加了sizeof(int)字节,这种"类型感知"的指针算术是如何实现的?如果p是void*能做算术吗?
  2. 指向数组尾后位置的指针arr + n是合法的,但解引用它是未定义行为。为什么C++允许创建这样的指针?
  3. 两个不相关的指针(指向不同数组)可以比较大小吗?C++标准对此怎么说?

# 8.3 指针使用

# 8.3.1 指针和数组

数组名本身就是一个指针,指向数组的第一个元素。示例

#include <iostream>
using namespace std;

int main() {
    int arr[3] = {10, 20, 30};
    int *ptr = arr; // ptr 指向数组的第一个元素

    for (int i = 0; i < 3; i++) {
        cout << "Element " << i << ": " << *(ptr + i) << endl;
    }

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13

输出:

Element 0: 10
Element 1: 20
Element 2: 30
1
2
3

# 8.3.2 指针和函数

指针可以作为函数的参数或返回值,用于传递或返回内存地址。

1.指针作为函数参数

#include <iostream>
using namespace std;

void increment(int *ptr) {
    (*ptr)++; // 修改指针指向的值
}

int main() {
    int num = 10;
    increment(&num); // 传递 num 的地址
    cout << "Incremented value: " << num << endl;
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13

输出:

Incremented value: 11
1

2.指针作为函数返回值

#include <iostream>
using namespace std;

int* getMax(int *a, int *b) {
    return (*a > *b) ? a : b;
}

int main() {
    int x = 10, y = 20;
    int *maxPtr = getMax(&x, &y);
    cout << "Max value: " << *maxPtr << endl;
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13

输出:

Max value: 20
1

# 8.3.3 指针与常量

指针可以与 const 关键字结合,表示指针指向的值或指针本身不可修改。

const修饰指针有三种情况

  1. const修饰指针 --- 常量指针。常量指针(Constant Pointer):在这种情况下,const 修饰指针本身,表示指针本身是常量,不能通过该指针修改指向的地址。
  2. const修饰常量 --- 指针常量。指向常量的指针(Pointer to Constant):在这种情况下,const 修饰指针所指向的值,表示指针指向的值是常量,不能通过该指针修改所指向的值。
  3. const即修饰指针,又修饰常量。指向常量的常量指针(Constant Pointer to Constant):结合上述两种情况,指针本身和指针所指向的值都是常量,既不能通过指针修改所指向的值,也不能修改指针本身指向的地址。

1.指向常量的指针

const int *ptr; // ptr 指向的值不可修改
1

2.常量指针

int *const ptr = &num; // ptr 本身不可修改
1

3.指向常量的常量指针

const int *const ptr = &num; // ptr 和 ptr 指向的值都不可修改
1

技巧:看const右侧紧跟着的是指针还是常量, 是指针就是常量指针,是常量就是指针常量

int main() {
    int a = 10;
    int b = 10;
    //const修饰的是指针,指针指向可以改,指针指向的值不可以更改
    const int * p1 = &a;
    p1 = &b; //正确
    //*p1 = 100;  报错
    //const修饰的是常量,指针指向不可以改,指针指向的值可以更改
    int * const p2 = &a;
    //p2 = &b; //错误
    *p2 = 100; //正确
    //const既修饰指针又修饰常量
    const int * const p3 = &a;
    //p3 = &b; //错误
    //*p3 = 100; //错误
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 8.3.4 指针和结构体

作用:通过指针访问结构体中的成员

利用操作符 ->可以通过结构体指针访问结构体属性

结构体指针是指向结构体实例的指针,允许你通过指针来访问和操作结构体的数据成员。

//结构体定义
struct student4 {
    //成员列表
    string name;  //姓名
    int age;      //年龄
    int score;    //分数
};

int main() {
    struct student4 stu = {"张三", 18, 100,};
    struct student4 *p = &stu;
    p->score = 80; //指针通过 -> 操作符可以访问成员
    cout << "姓名:" << p->name << " 年龄:" << p->age << " 分数:" << p->score << endl;
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

总结:结构体指针可以通过 -> 操作符 来访问结构体中的成员

# 8.3.5 综合案例与思考

综合案例:指针在数组、函数和结构体中的综合应用

#include <iostream>
#include <string>
using namespace std;

struct Student {
    string name;
    int scores[3];
};

// 用const指针保护数据不被修改
void printStudent(const Student* s) {
    cout << s->name << ": ";
    for (int i = 0; i < 3; ++i)
        cout << s->scores[i] << " ";
    cout << endl;
}

// 指针遍历数组找最大值
int findMax(const int* arr, int size) {
    const int* maxPtr = arr;
    for (int i = 1; i < size; ++i) {
        if (*(arr + i) > *maxPtr)
            maxPtr = arr + i;
    }
    return *maxPtr;
}

// 用指针实现swap
void swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    // 1. 指针与数组
    int arr[] = {15, 42, 8, 23, 51};
    cout << "最大值: " << findMax(arr, 5) << endl;

    // 2. 指针与函数(swap)
    int x = 10, y = 20;
    swap(&x, &y);
    cout << "交换后: x=" << x << ", y=" << y << endl;

    // 3. 指针与const
    const int* cp = arr;    // 指向常量的指针
    // *cp = 100;           // 错误!不能通过cp修改值
    cp = arr + 2;           // 可以改变指向
    cout << "cp指向: " << *cp << endl;

    int* const pc = arr;    // 常量指针
    *pc = 100;              // 可以修改值
    // pc = arr + 2;        // 错误!不能改变指向

    // 4. 指针与结构体
    Student s = {"张三", {90, 85, 92}};
    Student* sp = &s;
    sp->scores[1] = 95;  // 通过指针修改
    printStudent(sp);

    return 0;
}
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

案例知识融合:这个案例综合运用了指针与数组(遍历找最大值)、指针与函数(swap交换)、指针与const(常量指针 vs 指向常量的指针)、指针与结构体(->运算符访问成员)四个核心知识点。

思考题:

  1. const int* 和 int* const 的区别是什么?有一个记忆技巧叫"const在左是底层const,在右是顶层const",请解释这个规则。
  2. 数组作为函数参数时会退化为指针,丢失了长度信息。有什么方法可以保留数组长度信息?(提示:考虑模板或std::array)
  3. 通过指针实现的swap函数和通过引用实现的swap函数有什么区别?C++标准库的std::swap用的是哪种方式?

# 8.4 指针高级用法

# 8.4.1 指针的指针

指针可以指向另一个指针。

int num = 10;
int *ptr = &num;
int **ptr2 = &ptr; // ptr2 指向 ptr
1
2
3

# 8.4.2 函数指针

函数指针是指向函数的指针变量,可以用于动态调用函数。

#include <iostream>
using namespace std;

// 函数
int add(int a, int b) {
    return a + b;
}

int main() {
    // 声明函数指针
    int (*funcPtr)(int, int) = add;

    // 使用函数指针调用函数
    int result = funcPtr(3, 5);
    cout << "Sum: " << result << endl;

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

输出:

Sum: 8
1

# 8.4.3 综合案例与思考

综合案例:函数指针实现简易计算器

#include <iostream>
using namespace std;

// 四则运算函数
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int divOp(int a, int b) { return b != 0 ? a / b : 0; }

// 函数指针类型别名
using Operation = int(*)(int, int);

// 用函数指针数组实现计算器
void calculate(int a, int b, Operation op, const string& name) {
    cout << a << " " << name << " " << b << " = " << op(a, b) << endl;
}

int main() {
    // 1. 基本函数指针
    cout << "=== 函数指针 ===" << endl;
    int (*funcPtr)(int, int) = add;
    cout << "通过函数指针调用: " << funcPtr(3, 5) << endl;

    // 2. 函数指针数组(跳转表)
    cout << "\n=== 函数指针数组 ===" << endl;
    Operation ops[] = {add, sub, mul, divOp};
    string names[] = {"+", "-", "*", "/"};
    for (int i = 0; i < 4; ++i) {
        calculate(10, 3, ops[i], names[i]);
    }

    // 3. 指针的指针
    cout << "\n=== 指针的指针 ===" << endl;
    int val = 42;
    int* p = &val;
    int** pp = &p;
    cout << "val = " << val << endl;
    cout << "*p = " << *p << endl;
    cout << "**pp = " << **pp << endl;
    **pp = 100;
    cout << "修改后val = " << val << endl;

    return 0;
}
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
43
44

案例知识融合:这个案例展示了指针的高级用法——函数指针的声明和调用、函数指针数组(跳转表模式)实现计算器分发、using类型别名简化复杂指针类型声明、以及指针的指针(二级指针)的读写操作。

思考题:

  1. 函数指针数组(跳转表)在性能上比switch语句有什么优势?游戏引擎和解释器中为什么常用这种模式?
  2. C++11的std::function和传统函数指针相比有什么优势?它能存储哪些函数指针不能存储的可调用对象?
  3. 二级指针(指针的指针)在实际编程中有哪些应用场景?为什么C函数有时需要char**参数?

# 8.5 引用

# 8.5.1 什么是引用

在 C++ 中,引用 是一种别名机制,它为已存在的变量提供了一个新的名称。引用与指针类似,但更安全且易于使用。

引用是一个变量的别名,它必须在声明时初始化,并且一旦绑定到一个变量后,就不能再绑定到其他变量。

# 8.5.2 引用声明

作用: 给变量起别名

数据类型 &引用名 = 变量名;
1
  • 数据类型:引用绑定的变量的类型。
  • &:表示这是一个引用。
  • 引用名:引用的名称。
  • 变量名:引用绑定的变量。

示例

void test1() {
    int a = 10;
    int &b = a;   // b 是 a 的引用
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
    b = 100;      //修改 b 等同于修改 a
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
}

int main() {
    test1();
    return 0;
}

// a = 10
// b = 10
// a = 100
// b = 100
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 8.5.3 引用必须初始化

引用在声明时必须绑定到一个变量,否则会编译错误。

int num = 10;
int &ref = num; // 正确
int &ref2;      // 错误:引用必须初始化
1
2
3

引用一旦绑定到一个变量后,就不能再绑定到其他变量。

int num1 = 10, num2 = 20;
int &ref = num1;
ref = num2; // 这是赋值操作,不是重新绑定
1
2
3

# 8.5.4 引用访问数据

通过引用可以访问和修改原变量的值。

int num = 10;
int &ref = num;
ref = 20; // 修改 ref 的值
cout << num; // 输出 20
1
2
3
4

# 8.5.5 引用和函数

1.引用作为函数参数

函数可以返回引用,但必须确保返回的引用指向的变量在函数调用结束后仍然有效。示例

void increment(int& num) {
    num++;
}

int main() {
    int a = 10;
    increment(a); // 传递引用
    std::cout << a; // 输出 11
    return 0;
}
1
2
3
4
5
6
7
8
9
10

2.引用作为函数返回值

函数返回值 引用可以作为函数的返回值,但必须确保返回的引用指向有效的内存。

int& getMax(int& a, int& b) {
    return (a > b) ? a : b;
}

int main() {
    int x = 10, y = 20;
    cout << "y = " << getMax(x,y) << endl; // 输出 20
    getMax(x, y) = 30; // 修改较大的值
    cout << "y = " << y << endl; // 输出 30
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11

# 8.5.6 常量引用

作用: 常量引用(const 引用)用于防止通过引用修改原变量,同时避免拷贝开销。

在函数形参列表中,可以加==const修饰形参==,防止形参改变实参

示例:

//引用使用的场景,通常用来修饰形参
void showValue(const int & v) {
    //v += 10; //常量不能做新的赋值。会直接编译报错
    cout << v << endl;
}

void test() {
    //int& ref = 10;//引用本身需要一个合法的内存空间,因此这行错误
    //加入const就可以了,编译器优化代码,int temp = 10; const int& ref = temp;
    const int& ref = 10;
    //ref = 100;  //加入const后不可以修改变量
    cout << ref << endl;
    //函数中利用常量引用防止误操作修改实参
    int a = 10;
    showValue(a);
}

int main() {
    test();
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 8.5.7 避免返回局部变量引用

避免返回局部变量的引用:局部变量在函数结束后会被销毁,返回其引用会导致未定义行为。

int& badFunction() {
    int a = 10;
    return a; // 错误:返回局部变量的引用
}
1
2
3
4

# 8.5.8 综合案例与思考

综合案例:引用在实际编程中的最佳实践

#include <iostream>
#include <string>
#include <vector>
using namespace std;

// 1. const引用做参数(最常用的函数参数方式)
double average(const vector<int>& scores) {
    double sum = 0;
    for (int s : scores) sum += s;
    return sum / scores.size();
}

// 2. 引用做返回值(允许链式调用)
class StringBuilder {
    string data_;
public:
    StringBuilder& append(const string& s) {
        data_ += s;
        return *this;  // 返回自身引用
    }
    const string& str() const { return data_; }
};

// 3. 引用 vs 拷贝的性能差异
void processByValue(string s) { /* 拷贝 */ }
void processByRef(const string& s) { /* 零拷贝 */ }

int main() {
    // const引用做参数
    vector<int> scores = {85, 92, 78, 96, 88};
    cout << "平均分: " << average(scores) << endl;

    // 引用返回实现链式调用
    StringBuilder sb;
    sb.append("Hello").append(", ").append("World!");
    cout << sb.str() << endl;

    // 常量引用绑定临时对象
    const int& ref = 42;  // 合法!const引用延长临时对象生命周期
    cout << "const引用绑定字面量: " << ref << endl;

    // 引用必须初始化且不能重新绑定
    int a = 10, b = 20;
    int& r = a;
    r = b;  // 这是赋值,不是重新绑定!a变成了20
    cout << "a = " << a << " (被赋值为b的值)" << endl;

    return 0;
}
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
43
44
45
46
47
48
49

案例知识融合:这个案例展示了引用的最佳实践——const引用作为函数参数(避免拷贝+保护数据)、返回*this引用实现链式调用、常量引用可以绑定字面量和临时对象、以及引用赋值和重新绑定的区别。

思考题:

  1. const int& ref = 42; 是合法的,编译器在背后做了什么?为什么非const引用不能绑定字面量?
  2. 引用在底层通常是用指针实现的,那为什么说"引用不占额外内存"?这是否矛盾?
  3. C++11引入了右值引用(int&&),它和左值引用(int&)有什么区别?为什么需要右值引用?

# 8.6 指针VS引用

# 8.6.1 两者对比

特性 引用 指针
语法 类型& 引用名;比如 int& ref 数据类型 * 变量名;比如 int* ref
初始化 必须初始化 可以不初始化
重新绑定 不能重新绑定 可以重新指向其他变量
空值 不能为空 可以为 nullptr
内存占用 不占用额外内存 占用额外内存(存储地址)
语法 更简洁(直接使用变量名) 需要解引用(* 操作符)

# 8.6.2 两者转换

指针可以隐式转换为引用:

int a = 10;
int* p = &a;
int& r = *p; // 将指针解引用为引用
1
2
3

引用不能直接转换为指针,但可以获取引用的地址:

int a = 10;
int& r = a;
int* p = &r; // 获取引用的地址
1
2
3

# 8.6.3 综合案例与思考

综合案例:指针与引用的选择策略

#include <iostream>
#include <string>
using namespace std;

struct Node {
    int value;
    Node* next;  // 链表必须用指针(可能为nullptr)
};

// 场景1:必须用指针(可能为空)
Node* findNode(Node* head, int target) {
    while (head != nullptr) {
        if (head->value == target) return head;
        head = head->next;
    }
    return nullptr;  // 没找到返回空
}

// 场景2:优先用引用(一定存在)
void doubleValue(int& val) {
    val *= 2;
}

// 场景3:只读大对象用const引用
void printInfo(const string& info) {
    cout << info << endl;
}

int main() {
    // 指针场景:链表操作
    Node n3 = {30, nullptr};
    Node n2 = {20, &n3};
    Node n1 = {10, &n2};

    Node* found = findNode(&n1, 20);
    if (found) cout << "找到: " << found->value << endl;
    found = findNode(&n1, 99);
    if (!found) cout << "未找到99" << endl;

    // 引用场景:修改变量
    int x = 5;
    doubleValue(x);
    cout << "翻倍后: " << x << endl;

    // 选择建议总结
    cout << "\n=== 选择策略 ===" << endl;
    cout << "1. 可能为空 -> 用指针" << endl;
    cout << "2. 一定存在且不重新绑定 -> 用引用" << endl;
    cout << "3. 只读大对象 -> 用const引用" << endl;
    cout << "4. 需要重新指向 -> 用指针" << endl;
    cout << "5. 动态内存管理 -> 用智能指针" << endl;

    return 0;
}
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
43
44
45
46
47
48
49
50
51
52
53
54

案例知识融合:这个案例通过实际场景说明了指针和引用的选择策略——链表等可能为空的场景用指针、确定存在的参数传递用引用、只读大对象用const引用。明确了两者的使用边界。

思考题:

  1. Google C++风格指南建议"输入参数用const引用,输出参数用指针",这个规则背后的理由是什么?你同意吗?
  2. 在哪些C++特性中必须使用引用(不能用指针替代)?(提示:考虑运算符重载、拷贝构造函数等)
  3. C++的智能指针(unique_ptr、shared_ptr)结合了指针的灵活性和引用的安全性,它们是否完全替代了裸指针?什么场景下仍需要使用裸指针?

# 8.8 综合案例

# 8.8.1 遍历数组案例

作用:利用指针访问数组中元素。数组名本身是一个指针,指向数组的第一个元素。

示例:

int main() {
    int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    int *p = arr;  //指向数组的指针。看解释1
    cout << "第一个元素: " << arr[0] << endl;
    cout << "指针访问第一个元素: " << *p << endl;  //指向数组第一个元素的指针。看解释2
    for (int i = 0; i < 10; i++) {
        //利用指针遍历数组。看解释3
        cout << *p << endl;
        p++;
    }
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
  1. 解释1,数组名是指针:在 C++ 中,数组名可以被隐式转换为指向数组第一个元素的指针。这意味着p可以将数组名视为指向数组的第一个元素的指针。
  2. 解释2,指针和数组的关系:指针可以用来访问数组中的元素。通过指针算术运算,可以遍历数组中的元素。
  3. 解释3,指针和数组的传递:当传递数组给函数时,实际上传递的是数组的地址,即数组名被隐式转换为指向数组第一个元素的指针。

# 8.8.2 指针vs普通对象

值语义 vs 指针语义,核心概念理解:

// 值语义:操作副本
Account copy = original;    // 创建副本
copy.deposit(100);         // 修改副本,原对象不变

// 指针语义:操作原对象
Account* ptr = &original;  // 指向原对象
ptr->deposit(100);         // 修改原对象
1
2
3
4
5
6
7

性能影响分析,内存使用对比

// Account对象大小估算
class Account {
    std::string accountNumber;  // ~24字节(小字符串优化)
    std::string name;          // ~24字节
    double balance;            // 8字节
    // 总计:约56字节
};

// 使用指针:8字节(64位系统)
Account* ptr;

// 使用对象:56字节 + 复制开销
Account obj;
1
2
3
4
5
6
7
8
9
10
11
12
13

时间复杂度对比:

  1. 指针方式:O(1) 赋值操作
  2. 对象方式:O(n) 复制操作(n为对象大小)

# 8.8.3 交换两个变量值

void swap(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

int main() {
    int x = 10, y = 20;
    swap(x, y); // 传递引用
    std::cout << x << " " << y; // 输出 20 10
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12

# 8.9 指针与引用底层原理

# 8.9.1 指针的内存模型与寻址

指针变量本质上就是一个存储内存地址的整数。在x86-64系统中,指针占8字节,存储的是目标变量在虚拟地址空间中的64位地址。

指针变量在内存中的布局:

假设 int a = 42; 存放在地址 0x7ffd1234
     int* p = &a; p本身存放在地址 0x7ffd1240

内存布局:
地址            内容              说明
0x7ffd1234    2A 00 00 00      a的值42(小端序)
...
0x7ffd1240    34 12 fd 7f      p的值(存的是a的地址,小端序)
              00 00 00 00      (地址高4字节)
1
2
3
4
5
6
7
8
9

取地址与解引用的汇编实现:

int a = 42;
int* p = &a;
int b = *p;
1
2
3

对应的x86-64汇编(GCC -O0):

; int a = 42;
mov    DWORD PTR [rbp-12], 42      ; 将42存入栈上a的位置

; int* p = &a;
lea    rax, [rbp-12]               ; LEA(Load Effective Address)取a的地址
mov    QWORD PTR [rbp-8], rax      ; 将地址存入p(8字节)

; int b = *p;
mov    rax, QWORD PTR [rbp-8]      ; 将p的值(地址)加载到rax
mov    eax, DWORD PTR [rax]        ; 间接寻址:从rax指向的地址读取4字节
mov    DWORD PTR [rbp-16], eax     ; 存入b
1
2
3
4
5
6
7
8
9
10
11

关键指令解析:

  • LEA(Load Effective Address):计算地址但不访问内存,用于取地址&操作
  • MOV reg, [reg]:间接寻址,即解引用*操作,CPU通过寄存器中的地址去访问内存
  • 取地址是编译期计算(栈变量的偏移在编译时已知),解引用是运行期的内存访问

空指针的底层:

int* p = nullptr;  // 等价于 p = 0
*p = 10;           // 段错误(Segmentation Fault)
1
2

nullptr在底层就是地址0x0。操作系统将虚拟地址空间的第0页标记为不可访问,任何对地址0的读写都会触发页错误(Page Fault),内核向进程发送SIGSEGV信号,程序崩溃。这是一种硬件级别的保护机制。

# 8.9.2 指针运算的汇编实现

指针算术的本质——类型感知的地址计算:

int arr[5] = {10, 20, 30, 40, 50};
int* p = arr;
int val = *(p + 2);  // 访问arr[2]
1
2
3

汇编:

; int* p = arr;
lea    rax, [rbp-32]              ; 取数组首地址

; *(p + 2) 的计算过程:
; 编译器知道p指向int(4字节),所以p+2 = p + 2*sizeof(int) = p + 8
mov    eax, DWORD PTR [rax+8]     ; 直接地址 + 8字节偏移
1
2
3
4
5
6

编译器在编译期就将p + 2翻译为地址 + 2 × sizeof(int),这就是指针算术"类型感知"的底层实现——编译器根据指针的类型自动计算偏移量。

数组下标 vs 指针偏移:

arr[i]     // 等价于 *(arr + i)
*(p + i)   // 等价于 p[i]
1
2

两者在汇编层面完全等价,编译器生成相同的指令。C/C++标准定义a[b]等价于*(a+b),因此理论上2[arr]也是合法的(等价于*(2+arr))。

void 为什么不能做指针运算*:

void* vp = arr;
// vp + 1;  // 错误!编译器不知道sizeof(void)是多少
1
2

指针运算需要知道元素大小,void*没有类型信息,编译器无法计算偏移量。GNU扩展将sizeof(void)视为1,但这不是标准行为。

# 8.9.3 引用的底层本质

引用在汇编层面等价于const指针:

int a = 42;
int& ref = a;    // 引用
int* const p = &a; // 常量指针
1
2
3

两者生成的汇编完全相同:

; int& ref = a;  和  int* const p = &a;  生成相同代码:
lea    rax, [rbp-12]              ; 取a的地址
mov    QWORD PTR [rbp-8], rax     ; 存储地址
1
2
3

通过引用修改值:

ref = 100;   // 通过引用赋值
*p = 100;    // 通过指针赋值
1
2
; 两者汇编完全相同:
mov    rax, QWORD PTR [rbp-8]     ; 加载存储的地址
mov    DWORD PTR [rax], 100       ; 间接寻址写入
1
2
3

结论:引用在底层就是一个自动解引用的const指针。编译器隐藏了取地址和解引用的语法,让引用看起来像变量的别名。

引用"不占内存"的说法辨析:

说法 真相
"引用不占内存" 作为局部变量时可能不占——编译器可能优化掉引用,直接用原变量地址
"引用占内存" 作为类成员或函数参数时占内存——底层需要存储地址(8字节)
struct Ref {
    int& r;  // sizeof = 8,确实占用内存
};

// 编译器优化后的局部引用
int a = 10;
int& r = a;
r = 20;
// 优化后等价于:
// a = 20;   编译器直接消除了引用
1
2
3
4
5
6
7
8
9
10

引用传参 vs 值传参的汇编对比:

void byValue(int x)   { x = 100; }
void byRef(int& x)    { x = 100; }
void byPtr(int* x)    { *x = 100; }
1
2
3
byValue:
    mov    DWORD PTR [rbp-4], edi   ; 参数拷贝到栈上
    mov    DWORD PTR [rbp-4], 100   ; 修改的是副本

byRef:                               ; 和byPtr生成完全相同的代码!
    mov    DWORD PTR [rdi], 100     ; rdi存放的就是原变量地址,直接写入

byPtr:
    mov    DWORD PTR [rdi], 100     ; 和byRef完全一样
1
2
3
4
5
6
7
8
9

# 8.9.4 const指针与const引用的编译器处理

const的底层实现——编译期检查,不产生运行时开销:

const int* p1 = &a;     // 底层const:不能通过p1修改a
int* const p2 = &a;     // 顶层const:不能修改p2本身
const int& r = a;       // const引用:不能通过r修改a
1
2
3

这三种const修饰在汇编层面没有任何区别——它们和非const版本生成相同的机器码。const是纯粹的编译期约束,编译器在编译阶段检查是否有违规的写操作,通过后const信息就被丢弃了。

const_cast的危险:

const int a = 42;
const int& r = a;
int& evil = const_cast<int&>(r);
evil = 100;  // 未定义行为!
1
2
3
4

因为编译器可能将const int a = 42优化为编译期常量,直接在使用处替换为42,修改evil实际修改的是一个已经不被读取的内存位置。

常量引用绑定临时对象的底层:

const int& r = 42;
// 编译器生成:
// int __temp = 42;
// const int& r = __temp;  // __temp的生命周期延长到r的作用域
1
2
3
4

编译器会创建一个匿名临时变量,并将其生命周期延长(lifetime extension)到引用的生命周期。这是C++标准的特殊规定。

# 8.10 指针与引用训练题

训练题1:实现安全的动态数组

要求:用指针和动态内存实现一个简化版的整型动态数组类,支持添加元素、按下标访问、获取大小、自动扩容。

#include <iostream>
#include <cstring>
using namespace std;

class IntArray {
    int* data_;       // 指向堆上数组的指针
    int size_;        // 当前元素个数
    int capacity_;    // 当前容量

public:
    IntArray() : data_(nullptr), size_(0), capacity_(0) {}

    ~IntArray() {
        delete[] data_;   // 释放堆内存
        data_ = nullptr;
    }

    void push_back(int val) {
        if (size_ >= capacity_) {
            int newCap = (capacity_ == 0) ? 4 : capacity_ * 2;
            int* newData = new int[newCap];
            if (data_) {
                memcpy(newData, data_, size_ * sizeof(int));
                delete[] data_;
            }
            data_ = newData;
            capacity_ = newCap;
        }
        data_[size_++] = val;
    }

    // 返回引用,支持读写
    int& operator[](int index) {
        if (index < 0 || index >= size_) {
            throw out_of_range("Index out of range");
        }
        return data_[index];  // 返回引用
    }

    // const版本,只读
    const int& operator[](int index) const {
        if (index < 0 || index >= size_) {
            throw out_of_range("Index out of range");
        }
        return data_[index];
    }

    int getSize() const { return size_; }

    // 用指针实现迭代
    int* begin() { return data_; }
    int* end() { return data_ + size_; }
};

int main() {
    IntArray arr;
    for (int i = 0; i < 10; ++i) {
        arr.push_back(i * 10);
    }

    // 下标访问(通过引用修改)
    arr[3] = 999;

    // 指针迭代
    cout << "数组内容: ";
    for (int* p = arr.begin(); p != arr.end(); ++p) {
        cout << *p << " ";
    }
    cout << endl;
    // 输出: 0 10 20 999 40 50 60 70 80 90

    cout << "大小: " << arr.getSize() << endl;
    return 0;
}
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

练习重点:指针管理堆内存、operator[]返回引用、指针迭代器模式、扩容策略(2倍增长)。


训练题2:用指针实现链表基本操作

要求:使用指针实现单链表的创建、插入、查找、删除和遍历。

#include <iostream>
using namespace std;

struct Node {
    int data;
    Node* next;
    Node(int val) : data(val), next(nullptr) {}
};

class LinkedList {
    Node* head_;

public:
    LinkedList() : head_(nullptr) {}

    ~LinkedList() {
        Node* curr = head_;
        while (curr) {
            Node* temp = curr;
            curr = curr->next;
            delete temp;
        }
    }

    // 头插法
    void pushFront(int val) {
        Node* newNode = new Node(val);
        newNode->next = head_;
        head_ = newNode;
    }

    // 尾插法(需要遍历到末尾)
    void pushBack(int val) {
        Node* newNode = new Node(val);
        if (!head_) {
            head_ = newNode;
            return;
        }
        Node* curr = head_;
        while (curr->next) {
            curr = curr->next;
        }
        curr->next = newNode;
    }

    // 查找:返回指针(可能为nullptr)
    Node* find(int val) {
        Node* curr = head_;
        while (curr) {
            if (curr->data == val) return curr;
            curr = curr->next;
        }
        return nullptr;
    }

    // 删除:使用指向指针的指针(二级指针思维)
    bool remove(int val) {
        Node** pp = &head_;  // 指向"指向当前节点的指针"
        while (*pp) {
            if ((*pp)->data == val) {
                Node* toDelete = *pp;
                *pp = (*pp)->next;  // 修改前驱的next指针
                delete toDelete;
                return true;
            }
            pp = &((*pp)->next);
        }
        return false;
    }

    void print() const {
        for (Node* p = head_; p; p = p->next) {
            cout << p->data;
            if (p->next) cout << " -> ";
        }
        cout << " -> NULL" << endl;
    }
};

int main() {
    LinkedList list;
    list.pushBack(10);
    list.pushBack(20);
    list.pushBack(30);
    list.pushFront(5);

    cout << "初始链表: ";
    list.print();  // 5 -> 10 -> 20 -> 30 -> NULL

    Node* found = list.find(20);
    if (found) cout << "找到: " << found->data << endl;

    list.remove(20);
    cout << "删除20后: ";
    list.print();  // 5 -> 10 -> 30 -> NULL

    return 0;
}
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98

练习重点:指针链接节点、二级指针简化删除逻辑(Linus推荐的技巧)、指针遍历、动态内存管理。


训练题3:探究引用与指针的汇编等价性

要求:编写以下代码,用g++ -S -O0生成汇编,观察引用和指针的底层实现。

#include <iostream>
using namespace std;

void swapByPtr(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

void swapByRef(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

// 观察:结构体中引用占多少空间?
struct RefHolder {
    int& ref;
    RefHolder(int& r) : ref(r) {}
};

struct PtrHolder {
    int* ptr;
    PtrHolder(int* p) : ptr(p) {}
};

int main() {
    int x = 10, y = 20;

    // 对比两种swap
    swapByPtr(&x, &y);
    cout << "指针swap后: x=" << x << ", y=" << y << endl;

    swapByRef(x, y);
    cout << "引用swap后: x=" << x << ", y=" << y << endl;

    // 验证引用在结构体中的大小
    cout << "sizeof(RefHolder) = " << sizeof(RefHolder) << endl;  // 8
    cout << "sizeof(PtrHolder) = " << sizeof(PtrHolder) << endl;  // 8
    cout << "两者大小相同!引用底层就是指针" << endl;

    // 验证引用的地址
    int a = 42;
    int& r = a;
    cout << "&a = " << &a << endl;
    cout << "&r = " << &r << endl;  // 和&a完全相同!
    cout << "引用没有自己的地址,&r就是&a" << endl;

    return 0;
}
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
43
44
45
46
47
48
49
50

编译并查看汇编:

g++ -S -O0 -o test.s test.cpp
# 在test.s中搜索swapByPtr和swapByRef
# 你会发现两者的函数体汇编指令完全相同
1
2
3

练习重点:通过实验验证引用=const指针的结论、理解sizeof反映底层实现、掌握用汇编分析C++特性的方法。

# 8.11 综合思考题

  1. 指针与虚拟内存:程序中的指针存储的是虚拟地址而非物理地址。同一个指针值0x7ffd1234在不同进程中指向不同的物理内存。请思考:如果两个进程中的指针值相同,它们指向的数据相同吗?操作系统是如何通过**页表(Page Table)**实现虚拟地址到物理地址的映射的?

  2. 智能指针的底层实现:unique_ptr的大小通常和裸指针一样(8字节),而shared_ptr却是16字节。请分析:shared_ptr多出的8字节存储了什么?引用计数是如何实现线程安全的(提示:原子操作)?为什么unique_ptr能做到零开销抽象?

  3. 指针别名(Pointer Aliasing)问题:编译器在优化时需要假设两个指针是否可能指向同一块内存。C99引入了restrict关键字(C++中非标准但编译器支持__restrict),请思考:指针别名为什么会阻碍编译器优化?restrict如何帮助编译器生成更高效的代码?

  4. 移动语义与指针:C++11的移动语义(std::move)本质上是转移资源的所有权,而资源通常通过指针持有。请思考:std::vector的移动构造函数做了什么(提示:只是拷贝了内部指针)?为什么移动比拷贝快?移动后源对象的指针变成了什么状态?

# 8.12 新手陷阱 Top 5

# 陷阱 说明
1 悬挂指针 / 悬挂引用 指向已销毁对象;一律 RAII + 智能指针
2 把同一裸指针交给两个 shared_ptr 双重 free;只通过 make_shared 获取
3 unique_ptr 按值传递 触发所有权转移,调用方不持有;按 & 或 T* 传
4 用 delete 释放 new[] UB;规则:方括号配方括号
5 函数返回局部变量引用/指针 栈对象已销毁;返回值或 unique_ptr
上次更新: 2026/06/10, 11:13:41
函数
类和对象

← 函数 类和对象→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式