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

两个符号,一对搭档:& 是"取地址"(问门牌号),* 是"解引用"(照着地址上门取值)。int *p = &a; 让 p 记住了 a 的地址;*p 就回到 a 那户人家。最关键的一点:*p 是左值——既能读也能写,写 *p = 99; 就等于写 a = 99;。这就是"通过指针改原件"的完整原理,也是 6.3.1 那个换不动的 swap 的解药。

📝 本节和前面几节的关系

7.4.1 学会了"声明指针、填上门牌号"。这一节学"照着门牌号办事"——是本节把指针从"名词"变成了"动词"。它直接回收两笔老账:3.4 讲过的"左值 = 有地址、能赋值"(*p 正是一个左值),以及 6.3.1 埋下的"swap 换不动"(因为传的是复印件;有了 *p 就能改原件了)。

一、两个运算符,各有各的活

运算符 名字 干什么 例子 读作
& 取地址运算符 问一个变量"你住哪儿" &a "a 的地址"
* 指针运算符(解引用) 顺着地址找上门,取出那户人家的东西 *p "p 指向的那个值"

它们是一对反操作:

int  a = 10;
int *p = &a;      // p = a 的地址
cout << *p;       // 10  ← 又回到了 a 的值

*&a 就等于 a:先取地址、再解引用,绕了一圈回到原地。

二、* 的三重身份(别混)

同一个字符 *,在不同位置干三件完全不同的事:

int  x = 3 * 4;        // ① 乘法运算符:3 乘 4
int  a = 10;
int *p = &a;           // ② 声明里的星号:p 是"指向 int 的指针"
cout << *p << endl;    // ③ 解引用:取出 p 指向的值(10)
出现位置 身份 怎么认
两个值中间:3 * 4 乘法 左右都是"值"
类型后面、名字前面:int *p 指针类型标记 出现在声明语句里
指针变量前面:*p 解引用 后面跟的是"已经声明过的指针"

认的技巧:看 * 后面跟的是"新名字"还是"已存在的变量"。跟在 int 后面引入新名字 → 是声明;跟在一个现成的指针变量前面 → 是解引用。

三、*p 是左值:能读、也能写

这是本节最值钱的一条。3.4 说过:左值 = 有明确地址的盒子,能出现在 = 左边。*p 指的就是 a 那块内存,当然有地址,所以它是左值:

int  a = 10;
int *p = &a;

cout << *p;        // 读:10
*p = 99;           // 写!等价于 a = 99;
cout << a;         // 99  ← a 真的被改了

注意这里有多反直觉:明明写的是 *p = 99;,改的却是 a。因为 *p 和 a 指的是同一块内存——两张指路牌,通到同一个房间。

     p ──────┐
             ▼
          ┌──────┐
   a ───► │  99  │     `a` 和 `*p` 是同一扇门的两个叫法
          └──────┘

这条就是"改原件"的完整机制:把地址交出去,对方就能用 * 改你家东西。6.3.1 的 swap 之所以失败,是因为传的是"复印件";只要传地址,就能换动——7.6.1 会亲手把这个 swap 写对。

四、实例06:输出指针对应的数值

#include <iostream>
using namespace std;

int main() {
    int  a = 10;
    int *p = &a;

    cout << "a  = " << a  << endl;      // 10
    cout << "*p = " << *p << endl;      // 10   ← 和 a 一样

    *p = 99;                            // 通过指针改原件
    cout << "改完之后:" << endl;
    cout << "a  = " << a  << endl;      // 99
    cout << "*p = " << *p << endl;      // 99

    a = 7;                              // 反过来直接改 a
    cout << "*p = " << *p << endl;      // 7    ← 指针立刻"看到"新值
    return 0;
}

看到什么算成功:10、10、99、99、7。

这五行输出把"同一块内存"钉死了:

操作 结果 说明
读 a / 读 *p 都是 10 两条路通到同一个房间
写 *p = 99 a 变 99 通过指针改原件
写 a = 7 *p 变 7 反过来也成立

30 秒实验:在 int *p = &a; 之前加一句 int b = 10;,再打印 &a 和 &b——两个地址不一样,说明"值相同 ≠ 同一户人家"。这正是 6.3.1 里 swap 换不动的原因:函数里拿到的是新盒子(副本),不是原盒子的地址。

五、常见误区

  1. "*p = 99; 是给 p 赋值"——不是。改的是 p 指向的那个变量。p = 某个地址; 才是改 p 自己。
  2. "*p 和 p 可以随便换着写"——差得远。p 是地址,*p 是地址里的值。混用最典型的后果:cout << p 打印一串 0x...,看着像乱码,其实是地址。
  3. "把 & 和 && 搞混"——&a 是取地址(单个 &),a && b 是 3.1.3 的逻辑与。虽然写法像,语义完全无关;还有按位与 a & b 也是单 &,靠前后是不是变量名来分辨。
  4. "指针不初始化也能解引用"——int *p; cout << *p; 会去读一块随机内存,可能直接崩溃,也可能读到垃圾值。这就是"野指针",7.4.4 给治疗方案。
  5. "*p 只能读不能写"——能写。它是左值(3.4)。这是指针最有价值的特性,也是 7.6.1 / 7.8.2 两种"改原件"手段的基础。
自测三问(点开对答案) 1. `&` 和 `*` 各干什么?——`&` 取地址;`*` 解引用(顺着地址取值)。 2. `int a = 5; int *p = &a; *p = 8;` 之后 `a` 是几?——`8`。`*p` 和 `a` 是同一块内存。 3. `int *p = &a;` 和 `*p = 99;` 里的两个 `*` 是同一种用法吗?——**不是**。前者是"声明里的指针类型标记",后者是"解引用"。

相关笔记