Lesson 0007 · 指针与引用

程序设计基础 — 第 6 课

上节课(0006 嵌套循环):用两层循环把二维的数据一格一格扫过去。
这节课:换一种做法 —— 不拷贝,直接操作原数据。变量在内存里有地址,指针存的就是这个地址;引用则是给变量起一个外号。这是 C++ 跟 Python/Java 最大的区别,也是它为什么快。
三句话速览
  1. 引用是外号:改 r 就是改 a,不产生新变量。
  2. 指针存地址:&a 取地址,*p 按地址把值取回来。
  3. 传参想改原值就用引用;new 了一定要 delete。

1. 引用 — 给变量起个外号

int a = 10;
int& r = a;      // r 是 a 的引用(外号)

r = 20;          // 改 r 就是改 a
cout << a;      // 20 —— a 变了!

引用不是新变量,它就是原变量的另一个名字。改任意一个,另一个同步变。

引用的真正用途:传参

// 没有引用 —— 拷贝,改不了原值
void addOne_copy(int x) { x = x + 1; }

// 有引用 —— 直接改原值
void addOne_ref(int& x) { x = x + 1; }

int main() {
    int n = 5;
    addOne_copy(n);  cout << n;  // 5(没变!)
    addOne_ref(n);   cout << n;  // 6(变了!)
}
什么时候用引用传参?
① 想让函数修改原变量;
② 传大对象(vector、string)不想拷贝 —— 加 const:void print(const vector<int>& v)。

2. 指针 — 存的是地址

每个变量在内存里有个地址。指针存的就是这个地址。

int a = 42;
int* p = &a;       // p 存的是 a 的地址(& 取地址)

cout << a;        // 42(值)
cout << &a;       // 0x5ffe8c(地址)
cout << p;        // 0x5ffe8c(p 就是地址)
cout << *p;       // 42(* 解引用:按地址找到值)

三个符号一张表

符号读法含义示例
int*“指向 int 的指针”声明指针类型int* p;
&a“a 的地址”取地址p = &a;
*p“p 指向的值”解引用cout << *p;
最容易混的:int* p(声明)和 *p(解引用)里的 * 是两回事。声明时 * 表示“这是个指针”,使用时 * 表示“去地址上拿值”。

3. 指针修改原值

void swap(int* x, int* y) {    // 接收两个地址
    int temp = *x;              // 解引用,拿到值
    *x = *y;                    // 通过地址直接改原变量
    *y = temp;
}

int main() {
    int a = 3, b = 7;
    swap(&a, &b);              // 传地址
    cout << a << " " << b;   // 7 3(真正交换了!)
}

和引用版对比:引用版写 swap(a, b),调用时不用加 &;指针版必须自己把地址传进去,函数里也必须解引用才能碰到原值。

4. 引用 vs 指针

对比项引用指针
语法int& r = a;int* p = &a;
用的时候直接当变量用 r = 5必须解引用 *p = 5
能空吗不行,必须绑定可以 nullptr
能改指向不行,终生绑定可以 p = &b;
推荐传参首选需要“空”或“换指向”时
新手建议:函数传参优先用引用(简洁,也不会忘了解引用),需要动态分配内存、或者要表达“这里可能没有对象”时才用指针。

5. 指针与数组的关系

int arr[] = {10, 20, 30};
int* p = arr;           // 数组名本身就是首地址

cout << *p;             // 10(arr[0])
cout << *(p + 1);       // 20(arr[1])—— 指针 +1 跳到下一个元素
cout << *(p + 2);       // 30(arr[2])

这就是为什么数组索引从 0 开始:arr[i] 等价于 *(arr + i)。注意 p + 1 跳的是“一个元素”的距离,不是地址加 1 —— 下一课 0007B 会把这件事讲透。

6. new 和 delete — 动态分配

int* p = new int;       // 在堆上申请一个 int
*p = 100;
cout << *p;             // 100
delete p;               // 用完了必须释放!不然内存泄漏

int* arr = new int[5];  // 申请动态数组
arr[0] = 1;
delete[] arr;           // 数组用 delete[]
铁律:new 和 delete 必须配对,new[] 配 delete[]。不过日常用 vector 就够了,它自动管理,不用你操心。

7. 坑点清单

8. 小测

Q1. int a = 5; int& r = a; r = 10; 之后 a 的值是?
Q2. int a = 5; int* p = &a; *p = 3; cout << a; 输出什么?
Q3. 为什么 swap 要用引用而不是直接传值?

9. 动手练习

题目 1:用引用写 swap
用 引用(不是指针)实现 swap 函数,交换两个 int。
提示:void swap(int& x, int& y)。
题目 2(选做):数组最大值指针版
用指针遍历数组找最大值。
提示:int* p = arr; 然后 p++ 往后走,*p 取值。
卡住时的自查顺序
  1. 编译过了吗?先看第一条报错,别从最后一条开始看。
  2. 传参这一处,函数签名写的是 int 还是 int&?写错就改不到原值。
  3. 指针用之前,它指向的东西还活着吗?nullptr 和“已经 delete”的都要停下来问一句。
  4. 都对还不对:把这几行单独拷进一个空 main 里,加 cout 把中间值打印出来。

本课小结

下一课预告
0007B 指针与内存模型 —— 把指针放回内存里看一遍:栈和堆到底差在哪、new 出来的数据为什么能活过函数、delete 之后指针为什么不能再用、指针 +1 为什么是按元素跳。这一课解决的是“指针的地址到底指向什么”。

← 上一课:嵌套循环 · 课程总览