🔷 第七章 数组、指针和引用
📄 一句话概括

传递地址=调用时把 &变量 递进去,形参用指针接住,函数里用 *p 改原件。这是 6.3.1 那个"怎么都换不动"的 swap 唯一正确的打开方式:void swap(int *a, int *b) 配 swap(&x, &y)。两处缺一不可——形参要写成指针,调用要加 &。

📝 本节和前面几节的关系

这是 6.3.1 伏笔的正式回收点。当时你被"改的是复印件"憋了一句"想改原件,要等第 7 章的指针"——现在指针到账了。机制来自 7.4.2(*p 是左值,指的就是原件那块内存);写法来自 7.4.1(指针形参的声明)。本节之后,7.8.2 会给出第三种、也是最漂亮的写法:引用。

一、把 swap 写对

老写法(6.3.1,换不动)

void swapWrong(int a, int b) {          // 形参是 int(值)
    int t = a; a = b; b = t;            // 交换的是两份副本
}
swapWrong(x, y);                        // 传的是值

新写法(本节,换得动)

void swapRight(int *a, int *b) {        // ① 形参是 int*(指针)
    int t = *a; *a = *b; *b = t;        // ② 体内用 *a 操作"原件"
}
swapRight(&x, &y);                      // ③ 调用时用 & 取地址递进去

三处必须同时对上:

位置 传值版 传地址版
形参声明 int a int *a
函数体内的操作 直接用 a 必须用 *a
调用处 swap(x, y) swap(&x, &y)

任何一处漏改,结果就回到"换不动"。最常见的两个错误:调用时忘了 &(编译不过或警告),函数体里忘了 *(换了个寂寞)。

二、实例12:调用自定义函数交换两个变量值

#include <iostream>
using namespace std;

void swapWrong(int a, int b) {          // 传值:换的是副本
    int t = a; a = b; b = t;
}

void swapRight(int *a, int *b) {        // 传地址:换的是原件
    int t = *a; *a = *b; *b = t;
}

int main() {
    int x = 1, y = 2;

    cout << "交换前:        x = " << x << ", y = " << y << endl;   // 1, 2

    swapWrong(x, y);
    cout << "传值后:        x = " << x << ", y = " << y << endl;   // 1, 2(没变)

    swapRight(&x, &y);
    cout << "传地址后:      x = " << x << ", y = " << y << endl;   // 2, 1(变了)

    // 用一个真实的数组验证:交换两个元素
    int arr[4] = {10, 20, 30, 40};
    swapRight(&arr[0], &arr[3]);
    cout << "数组首尾交换:  ";
    for (int i = 0; i < 4; i++) cout << arr[i] << " ";              // 40 20 30 10
    cout << endl;
    return 0;
}

看到什么算成功:

交换前:        x = 1, y = 2
传值后:        x = 1, y = 2      ← 毫无变化
传地址后:      x = 2, y = 1      ← 成功交换
数组首尾交换:  40 20 30 10

最后那一段最实用:swapRight(&arr[0], &arr[3]) 把数组首尾两个元素换了——这就是"排序"的基础动作。后面学排序算法(冒泡、快速排序)时,swap 是最小的工作单元。6.3.1 里写不对的那几行,现在成为你能用的工具了。

30 秒实验:把 swapRight(&x, &y); 里的两个 & 去掉(写成 swapRight(x, y);),重新编译——编译器直接报错(invalid conversion from 'int' to 'int*')。这正是 7.4.1 说的"指针类型必须匹配"在替你把关:想在编译期就发现问题,靠的就是类型。

三、什么时候该传地址

情况 建议 原因
要改调用方的变量 传地址 只有拿着地址才能改原件
只是读参数、不改 传值 天然隔离,函数爱怎么折腾都不影响外面
参数是大数组/大对象 传地址(或 const 引用) 省掉整份拷贝,也避免"改不动"的困惑
参数是字符数组 传地址(数组名本身就是地址) 数组名就是首地址(7.5),压根没法"传值"

一句话判断法:问自己"函数结束后,我希不希望调用方的变量变了?" 希望 → 传地址;不希望 → 传值。

四、常见误区

  1. "形参写成指针,函数里就能直接改 a"——不行。指针形参在函数里也是个局部变量,直接改 a 只是改指针自己的指向(7.4.1),必须写 *a 才改到原件。
  2. "调用时加不加 & 都行"——必须加。少了 & 递进去的是值,类型不匹配,编译直接报错。
  3. "传地址就等于把整个变量搬进函数"——不是。搬进去的只是一个地址(4 或 8 字节),原变量还留在原地。
  4. "传地址比传值危险,能不用就不用"——危险的不是传地址,而是指针不判空、乱指(7.4.4)。该用的时候必须用,否则 swap 这类基本工具都写不出来。
  5. "数组传进函数时也是复制一份"——不是。数组名本身就是地址(7.5),传进函数后操作的就是原数组——这也是 7.8.3 要展开的点。
自测三问(点开对答案) 1. 写一个能真正交换两个 int 的函数,形参怎么写?——`void swap(int *a, int *b)`,调用 `swap(&x, &y);` 2. `swapRight(x, y)`(忘了 `&`)会怎样?——编译报错,类型不匹配(`int` 换不成 `int*`)。 3. 函数里写 `a = b;`(a 是指针形参)改的是什么?——改的是**指针自己的指向**,没碰到原件(必须写 `*a = *b;`)。

相关笔记