Skip to content

第2章 指针与内存地址 ​


🎯 本章学习目标 ​

通过本章的学习,你将能够:

  1. 理解指针本质:明白指针是存储内存地址的变量,建立"地址-值"的内存模型
  2. 掌握取地址与解引用:熟练使用 & 取地址和 * 解引用,分清二者的逆运算关系
  3. 正确定义指针:掌握指针变量的定义语法,避免 int* p, q; 这类常见陷阱
  4. 理解指针赋值:掌握指针之间的赋值规则,能分析"指针接力"问题
  5. 识别空指针与野指针:知道二者的区别和危害,养成安全使用指针的习惯
  6. 指针操作数组:能用指针遍历一维数组,理解 arr[i] 与 *(arr+i) 的等价性
  7. 掌握指针算术:理解 p++ 移动的是 sizeof(类型) 字节,不是 1 字节
  8. 辨析const与指针:能区分"指向常量的指针"和"常量指针"三种组合
  9. 阅读指针代码:能快速分析指针相关的程序阅读题,画出指向图
  10. 避免常见错误:识别野指针、越界访问、类型不匹配等指针易错点

🗺️ 本章知识图谱 ​

指针与内存地址
├── 基础概念
│   ├── 内存地址(变量的"门牌号")
│   ├── & 取地址运算符
│   └── 指针的本质(存地址的变量)
├── 指针定义与初始化
│   ├── 定义语法:类型 *变量名
│   ├── * 的位置陷阱(int* p, q)
│   ├── 初始化:定义时指向变量
│   └── p、&p、*p 三者辨析
├── 指针赋值与比较
│   ├── 指针赋值:复制地址值
│   ├── 指针接力问题(经典考点)
│   └── 指针比较:== / != / < / >
├── 解引用 *
│   ├── 解引用访问变量值
│   ├── 通过指针修改变量
│   └── & 与 * 互为逆运算
├── 空指针与野指针
│   ├── 空指针 nullptr / NULL
│   ├── 野指针(未初始化)
│   ├── 未定义行为(UB)
│   └── void* 通用指针
├── 指针与数组
│   ├── 数组名 = 首元素地址
│   ├── arr[i] ≡ *(arr+i)
│   ├── 用指针遍历数组
│   └── 数组名是常量指针
├── 指针算术运算
│   ├── 指针加减整数(步长)
│   ├── 不同类型指针步长不同
│   ├── 指针相减(元素个数)
│   └── 指针越界问题
├── const 与指针
│   ├── 指向常量的指针(左const)
│   ├── 常量指针(右const)
│   ├── 指向常量的常量指针
│   └── 记忆口诀:左定值右定向
└── 经典易错点
    ├── 指针接力问题
    ├── 指针类型不匹配
    └── 混淆 * 的不同含义

一、什么是指针与内存地址 ​

1.1 内存地址——变量的"门牌号" ​

计算机的内存就像一排编号的储物柜,每个柜子大小为 1 字节,每个柜子都有一个唯一的编号,这个编号就是内存地址。

当我们定义一个变量时,系统会在内存中分配一段空间,并把这段空间的起始地址与变量名关联起来。

内存地址(示意)       内存内容
  0x1000  ┌──────────┐
          │  变量 a   │  ← 变量 a 占用从 0x1000 开始的4字节(int)
  0x1004  ├──────────┤
          │  变量 b   │  ← 变量 b 占用从 0x1004 开始的4字节
  0x1008  └──────────┘

1.2 取地址运算符 & ​

& 运算符用于获取变量的内存地址:

cpp
#include <iostream>
using namespace std;

int main() {
    int a = 42;
    cout << "a 的值: " << a << endl;       // 输出: 42
    cout << "a 的地址: " << &a << endl;     // 输出: 类似 0x7ffdabc12345 的地址值
    return 0;
}

⚠️ 注意: 每次运行程序,变量的地址可能不同(由操作系统动态分配),所以不要死记某个具体地址值,重点是理解"地址"这个概念。

1.3 指针的本质 ​

指针(Pointer) 是一种特殊的变量,它存储的不是普通数据,而是另一个变量的内存地址。

普通变量 a:                   指针变量 p:
  地址 0x1000                    地址 0x2000
  ┌──────────┐                  ┌──────────┐
  │   42     │                  │ 0x1000   │  ← p 存储的是 a 的地址
  └──────────┘                  └──────────┘
       ↑                              │
       └──────── p 指向 a ────────────┘

通俗理解:变量是房子,地址是门牌号,指针就是写着门牌号的纸条。


二、指针变量的定义 ​

2.1 定义语法 ​

cpp
类型 *指针变量名;
定义语句含义
int *p;p 是一个指针,指向 int 类型变量
char *c;c 是一个指针,指向 char 类型变量
double *d;d 是一个指针,指向 double 类型变量

🔥 高频考点 ⚠️: * 的位置在定义时属于变量名而非类型。以下写法中,p 是指针,q 是普通 int 变量:

cpp
int* p, q;  // p 是 int*,q 是 int(不是指针!)

建议每行只定义一个指针变量,避免混淆。

2.2 完整示例 ​

cpp
#include <iostream>
using namespace std;

int main() {
    int a = 42;
    int *p = &a;   // 定义指针 p,指向变量 a

    cout << "a 的值: " << a << endl;       // 42
    cout << "&a 的值: " << &a << endl;     // a 的地址
    cout << "p 的值: " << p << endl;       // p 中存的地址,等于 &a
    cout << "&p 的值: " << &p << endl;     // 指针变量 p 自身的地址
    cout << "p 和 &a 是否相等: "
         << (p == &a ? "是" : "否") << endl;  // 是

    return 0;
}

🔥 高频考点 ⚠️: 一定要区分 p、&p、*p 三者的含义:

  • p:指针变量存储的值(即它指向的地址)
  • &p:指针变量自身的地址
  • *p:指针指向的变量的值(解引用,见下一节)

三、指针的赋值与初始化 ​

3.1 初始化与赋值 ​

定义指针的同时让它指向一个已存在的变量,称为初始化:

cpp
int a = 10;
int *p = &a;   // 初始化:p 指向 a

先定义指针,再给它赋值:

cpp
int a = 10, b = 20;
int *p;
p = &a;   // p 指向 a
p = &b;   // p 改为指向 b(不再指向 a)

3.2 指针之间的赋值 ​

cpp
int a = 10;
int *p1 = &a;
int *p2 = p1;   // p2 也指向 a,p1 和 p2 指向同一个地址

⚠️ 关键理解: p2 = p1 复制的是地址值,不是所指向的数据。赋值后两个指针指向同一块内存,修改 *p1 和 *p2 效果相同。

3.3 指针的比较 ​

指针可以使用 ==、!=、<、> 等关系运算符比较:

cpp
#include <iostream>
using namespace std;

int main() {
    int a = 10, b = 20;
    int *p1 = &a, *p2 = &b, *p3 = &a;

    cout << (p1 == p3) << endl;  // 1(true),p1 和 p3 都指向 a
    cout << (p1 == p2) << endl;  // 0(false),指向不同变量

    return 0;
}

注意: 指针比较中,== 和 != 最常用(判断是否指向同一地址)。<、> 等比较主要在数组场景中有意义。


四、指针的解引用 ​

4.1 解引用运算符 * ​

在指针前加 *,表示访问指针所指向的变量,这称为解引用(Dereference)。

cpp
int a = 42;
int *p = &a;

cout << *p << endl;   // 输出 42,相当于 cout << a
*p = 100;             // 通过指针修改 a 的值
cout << a << endl;    // 输出 100,a 被修改了!

可以理解为:p 是门牌号,*p 是推开门走进屋里拿到的东西。

p = 0x1000 (门牌号)        *p = 屋里的东西 (值)
       ↓
  0x1000 ┌──────────┐
         │   100    │  ← *p 就是访问这个格子
         └──────────┘

4.2 完整示例 ​

cpp
#include <iostream>
using namespace std;

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

    cout << "*p = " << *p << endl;  // 10(*p 就是 a)
    cout << "a  = " << a << endl;   // 10

    *p = 100;                       // 通过 p 修改 a
    cout << "a  = " << a << endl;   // 100

    p = &b;                         // p 改为指向 b
    cout << "*p = " << *p << endl;  // 20(*p 现在是 b)
    *p = 200;
    cout << "b  = " << b << endl;   // 200

    return 0;
}

4.3 & 与 * 互为逆运算 ​

运算符含义示例
&取变量的地址p = &a;
*访问指针指向的变量*p = 10;

& 和 * 互为逆运算:*(&a) 就是 a,&(*p) 就是 p。

⚠️ 易错点: * 在定义时和使用时含义不同——定义时表示"这是一个指针",使用时表示"解引用"。


五、空指针与野指针 ​

5.1 空指针 ​

空指针表示指针不指向任何有效的内存地址。C++11 推荐使用 nullptr:

cpp
int *p = nullptr;   // p 是空指针,不指向任何地方

C语言风格的 NULL 也可以使用(它本质上是整数0):

cpp
int *p = NULL;      // 旧写法,通常定义为 0,可隐式转为指针(C++11 推荐 nullptr)

使用前必须判空:

cpp
if (p != nullptr) {
    cout << *p << endl;   // 安全访问
} else {
    cout << "指针为空!" << endl;
}

5.2 野指针 ​

野指针是指没有被正确初始化、或指向的内存已经无效的指针。野指针指向的地址是随机的、不可预测的。

cpp
int *p;            // ❌ 未初始化!p 的值是随机的,是野指针
// 任何对 p 的读取(如 if(p == nullptr))或解引用 *p 都是未定义行为
cout << *p << endl; // ❌ 严重错误!可能导致程序崩溃

int *q = nullptr;  // ✅ 初始化为空指针,安全

🔥 高频考点 ⚠️: 解引用空指针和野指针都会导致未定义行为(Undefined Behavior),常见后果:

  • 程序崩溃(段错误 Segmentation Fault)
  • 读取到垃圾值
  • 偶尔"正常"运行但结果错误

养成好习惯:定义指针时立即初始化,暂时不指向任何变量就赋值为 nullptr。

5.3 空指针 vs 野指针 ​

对比项空指针野指针
值nullptr(地址0)随机的垃圾地址
可预测性可预测,可判空检查不可预测
危害解引用会崩溃(容易发现)可能崩溃也可能"正常"(难调试)
避免方法初始化时赋值 nullptr定义时立即初始化

5.4 void* 通用指针 ​

void* 是一种特殊的指针类型,可以存储任意类型变量的地址:

cpp
int a = 10;
double b = 3.14;

void *p;
p = &a;   // ✅ 可以指向 int
p = &b;   // ✅ 可以改为指向 double

// cout << *p << endl;  // ❌ 编译错误!void* 不能直接解引用
void *p2 = &a;          // 重新指向 int 再演示转换(避免与上面的 p 重名)
int *pi = (int*)p2;     // 转换为 int* 后再使用
cout << *pi << endl;    // 10

⚠️ 注意: void* 在四级中可能出现1~2道概念题,重点掌握'不能直接解引用'和'需强制类型转换'即可。


六、指针与一维数组 ​

6.1 数组名是首元素地址 ​

在C++中,数组名在大多数情况下会被隐式转换为指向首元素的指针:

cpp
int arr[5] = {10, 20, 30, 40, 50};

cout << arr << endl;       // 输出首元素地址
cout << &arr[0] << endl;   // 输出首元素地址,与 arr 相同
cout << (arr == &arr[0]) << endl;  // 1(true)

内存示意:

数组 arr:
  地址     0x1000  0x1004  0x1008  0x100C  0x1010
  值       [ 10 ]  [ 20 ]  [ 30 ]  [ 40 ]  [ 50 ]
  下标       [0]     [1]     [2]     [3]     [4]
            ↑
           arr 指向这里

6.2 arr[i] 等价于 *(arr+i) ​

这是指针与数组关系中最核心的等价关系:

arr[i]  ≡  *(arr + i)
cpp
#include <iostream>
using namespace std;

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

    cout << arr[2] << endl;       // 30
    cout << *(arr + 2) << endl;   // 30,完全等价

    cout << arr[0] << endl;       // 10
    cout << *arr << endl;         // 10,*(arr + 0) 即 *arr

    return 0;
}

📌 延伸:二维数组的数组名同样代表首行地址(int (*)[4] 类型),arr[i][j] 等价于 *(*(arr+i)+j)。具体细节将在下一章《二维及多维数组》中深入展开。

6.3 用指针遍历数组 ​

cpp
#include <iostream>
using namespace std;

int main() {
    int arr[5] = {10, 20, 30, 40, 50};
    int *p = arr;   // p 指向首元素,等价于 p = &arr[0]

    // 方式一:指针下标法
    for (int i = 0; i < 5; i++) {
        cout << p[i] << " ";   // 10 20 30 40 50
    }
    cout << endl;

    // 方式二:指针偏移法
    for (int i = 0; i < 5; i++) {
        cout << *(p + i) << " ";  // 10 20 30 40 50
    }
    cout << endl;

    // 方式三:指针移动法
    p = arr;  // 重置 p 到开头
    for (int i = 0; i < 5; i++) {
        cout << *p << " ";
        p++;   // 指针向后移动一个元素
    }
    cout << endl;  // 10 20 30 40 50

    return 0;
}

🔥 高频考点 ⚠️: 数组名 arr 是一个常量指针(地址不可修改),不能执行 arr++ 或 arr = ...。但如果用一个指针变量 p = arr,则 p++ 是合法的。


七、指针的算术运算 ​

7.1 指针加减整数 ​

指针可以加/减一个整数 n,表示向后/向前移动 n 个元素(注意:不是 n 字节!):

int *p = &arr[0];   // p 指向 arr[0]
p + 1               // 指向 arr[1],地址增加 sizeof(int) = 4 字节
p + 2               // 指向 arr[2],地址增加 8 字节

实际移动的字节数 = n × sizeof(指针指向的类型)

cpp
#include <iostream>
using namespace std;

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

    cout << *p << endl;       // 10
    cout << *(p + 1) << endl; // 20
    cout << *(p + 4) << endl; // 50

    p++;                      // p 向后移动一个 int
    cout << *p << endl;       // 20,p 现在指向 arr[1]

    p += 2;                   // 再向后移动两个 int
    cout << *p << endl;       // 40,p 现在指向 arr[3]

    p--;                      // 向前移动一个 int
    cout << *p << endl;       // 30,p 现在指向 arr[2]

    return 0;
}

7.2 不同类型的指针步长 ​

指针类型p++ 移动字节数
char*1 字节
int*4 字节
double*8 字节

🔥 高频考点 ⚠️: 这是考试常考的判断题!指针 p++ 不是地址加1,而是地址加 sizeof(类型)。同一段内存,用 int* 和 char* 去遍历,每步跳过的字节数不同。

7.3 指针相减 ​

两个指向同一数组中元素的指针可以相减,结果是它们之间相隔的元素个数:

cpp
#include <iostream>
using namespace std;

int main() {
    int arr[5] = {10, 20, 30, 40, 50};
    int *p1 = &arr[1];   // 指向 arr[1]
    int *p2 = &arr[4];   // 指向 arr[4]

    cout << (p2 - p1) << endl;  // 3,相隔 3 个元素

    return 0;
}

⚠️ 注意: 只有指向同一个数组的指针相减才有意义。指向不同数组的指针相减是未定义行为。

7.4 指针越界 ​

cpp
int arr[3] = {10, 20, 30};
int *p = arr;   // p → arr[0]

p++;            // p → arr[1],*p = 20
p++;            // p → arr[2],*p = 30
p++;            // p → arr[3],越界!*p 是未定义行为

🔥 高频考点 ⚠️: 指针越界访问是常见的程序阅读题陷阱。遍历数组时一定要注意边界条件,p 不能越过数组最后一个元素。


八、const 与指针 ​

const 和指针的组合是GESP四级的常见辨析题,关键看 const 修饰的是谁。

8.1 三种组合一览 ​

写法名称能否修改指向的值能否修改指针指向
const int *p指向常量的指针❌ 不能✅ 能
int * const p常量指针✅ 能❌ 不能
const int * const p指向常量的常量指针❌ 不能❌ 不能

8.2 指向常量的指针(const 在 * 左边) ​

cpp
const int *p;   // 或 int const *p;
  • const 修饰的是 *p,即不能通过指针修改所指向的值
  • 指针本身可以修改(可以改变指向)
cpp
#include <iostream>
using namespace std;

int main() {
    int a = 10, b = 20;
    const int *p = &a;

    // *p = 100;   // ❌ 编译错误!不能通过 p 修改 a 的值
    a = 100;       // ✅ 直接修改变量 a 是可以的
    cout << *p << endl;  // 100

    p = &b;        // ✅ 指针可以改为指向 b
    cout << *p << endl;  // 20

    return 0;
}

8.3 常量指针(const 在 * 右边) ​

cpp
int * const p = &a;
  • const 修饰的是指针 p 本身,即指针的指向不能改变
  • 可以通过指针修改所指向的值
cpp
#include <iostream>
using namespace std;

int main() {
    int a = 10, b = 20;
    int * const p = &a;

    *p = 100;      // ✅ 可以通过 p 修改 a 的值
    cout << a << endl;  // 100

    // p = &b;     // ❌ 编译错误!p 是常量,不能改变指向

    return 0;
}

8.4 指向常量的常量指针(两边都有 const) ​

cpp
const int * const p = &a;
  • 既不能通过指针修改所指向的值,也不能改变指针的指向
cpp
int a = 10, b = 20;
const int * const p = &a;

// *p = 100;   // ❌ 不能修改值
// p = &b;     // ❌ 不能改变指向
cout << *p << endl;  // 10,只能读取

8.5 记忆口诀 ​

cpp
const int *p;          // 左常量:指向的值不能改(指针可以改)
int * const p;         // 右常量:指针不能改(值可以改)
const int * const p;   // 都常量:值和指针都不能改

记忆技巧: 从右往左读——

  • const int *p:p 是一个指针,指向 const int(常量int),所以值不能改
  • int * const p:p 是一个 const 指针,指向 int,所以指向不能改

九、经典易错点辨析 ​

易错点1:指针接力问题 🔥 ​

这是GESP四级最经典的考法,几乎每次考试都会出现!

cpp
int a = 10, b = 20;
int *p1 = &a, *p2 = &b;
p1 = p2;        // p1 改为指向 b
*p2 = 99;       // 通过 p2 修改 b
// 此时 *p1 也是 99!因为 p1 和 p2 都指向 b

解题技巧: 遇到指针接力题,画一张"指针→变量"的指向图,每步更新指向,就不会出错。

易错点2:指针类型不匹配 ​

cpp
double d = 3.14;
int *p = &d;    // ❌ 编译错误!类型不匹配

// C++ 要求指针类型必须与所指变量类型严格一致
// (void* 除外)

易错点3:混淆 * 的不同含义 ​

cpp
int a = 10;
int *p = &a;    // 这里的 * 是"定义指针"的标记
*p = 20;        // 这里的 * 是"解引用"运算符

十、📝 实战练习 ​

练习题 1(程序阅读题) ​

题目: 阅读以下程序,输出结果是( )

cpp
#include <iostream>
using namespace std;

int main() {
    int a = 5, b = 10;
    int *p1 = &a, *p2 = &b;
    *p1 = 15;
    p1 = p2;
    *p1 = 20;
    cout << a << " " << b << endl;
    return 0;
}

A. 15 10
B. 15 20
C. 5 20
D. 5 10

🔑 点击查看答案与解析

答案: B

解析:

逐行分析:

  1. a=5, b=10,p1→a, p2→b
  2. *p1 = 15:通过 p1 修改 a,a=15
  3. p1 = p2:p1 改为指向 b,此时 p1→b, p2→b
  4. *p1 = 20:通过 p1 修改 b,b=20
  5. 最终 a=15, b=20,输出 15 20,选 B。

解题技巧: 画指向图,每步更新箭头方向,谁指向谁一目了然。


练习题 2(程序阅读题) ​

题目: 阅读以下程序,输出结果是( )

cpp
#include <iostream>
using namespace std;

int main() {
    int arr[5] = {2, 4, 6, 8, 10};
    int *p = arr;
    int sum = 0;
    for (int i = 0; i < 5; i++) {
        sum += *(p + i);
    }
    cout << sum << endl;
    return 0;
}

A. 20
B. 30
C. 40
D. 50

🔑 点击查看答案与解析

答案: B

解析:

*(p + i) 等价于 arr[i],循环依次取 2、4、6、8、10:

sum = 2 + 4 + 6 + 8 + 10 = 30,选 B。


练习题 3(单选题) ​

题目: 以下说法正确的有( )

① 指针存储的是变量的地址 ② int *p; 定义后,p 自动初始化为 nullptr ③ arr[i] 等价于 *(arr + i) ④ int* p 中 p++ 使地址值增加1字节 ⑤ const int *p 不能通过 p 修改所指向变量的值 A. ①③⑤
B. ①②③
C. ②③④
D. ①③④

🔑 点击查看答案与解析

答案: A

解析:

  • ① 正确 ✅ 指针的本质就是存储地址。
  • ② 错误 ❌ 局部指针变量不会自动初始化为 nullptr,其值是随机的(野指针)。
  • ③ 正确 ✅ 这是数组与指针等价关系的核心公式。
  • ④ 错误 ❌ p++ 使地址增加 sizeof(int) = 4字节,不是1字节。
  • ⑤ 正确 ✅ const int *p 是指向常量的指针,不能通过 p 修改值。

选 A(①③⑤)。


练习题 4(判断题) ​

题目: 局部指针变量定义后会自动初始化为 nullptr,所以可以放心使用。( )

🔑 点击查看答案与解析

答案: 错误 ❌

解析: 局部指针变量和普通局部变量一样,不会自动初始化,其值是内存中的随机垃圾值,这就是"野指针"。解引用野指针是未定义行为,可能导致程序崩溃。

💡 安全习惯:定义指针时立即初始化,暂时不用就赋值为 nullptr。就像买了把钥匙,暂时不用就放钥匙扣上做标记,别随手乱扔。


练习题 5(程序阅读题) ​

题目: 阅读以下程序,输出结果是( )

cpp
#include <iostream>
using namespace std;

int main() {
    char arr[] = "hello";
    char *p = arr;
    p += 2;
    cout << *p << endl;
    return 0;
}

A. h
B. e
C. l
D. o

🔑 点击查看答案与解析

答案: C

解析:

  • arr 是字符数组,存储字符串 "hello",arr[0]='h', arr[1]='e', arr[2]='l'
  • p = arr:p 指向首元素 'h'
  • p += 2:p 向后移动 2 个 char(每个 char 占 1 字节),指向 arr[2]
  • *p 就是 arr[2] = 'l'

选 C。

🔥 关键考点:指针加减整数时,移动的字节数 = n × sizeof(类型)。char* 每步移动 1 字节,int* 每步移动 4 字节。


练习题 6(单选题) ​

题目: 以下说法错误的是( ) A. 数组名可以隐式转换为指向首元素的指针
B. arr[i] 等价于 *(arr + i)
C. 数组名是常量指针,可以执行 arr++
D. 指向数组的指针可以用下标法访问元素

🔑 点击查看答案与解析

答案: C

解析:

  • A 正确 ✅:数组名在大多数情况下会退化为首元素地址
  • B 正确 ✅:这是指针与数组最核心的等价关系
  • C 错误 ❌:数组名是常量指针(地址值不能修改),不能执行 arr++ 或 arr = ...
  • D 正确 ✅:指针也可以用下标,p[i] 等价于 *(p+i)

💡 记忆技巧:数组名是"固定地址",就像小区大门的位置不能动;但你可以拿个小本本(指针变量)记下地址,然后到处走。


练习题 7(填空题) ​

cpp
int arr[10];
int *p1 = &arr[2];
int *p2 = &arr[7];

p2 - p1 的值是 ______。

🔑 点击查看答案与解析

答案: 5

解析:

两个指向同一数组的指针相减,结果是它们之间相隔的元素个数,而不是字节数。

  • p1 指向 arr[2](第3个元素)
  • p2 指向 arr[7](第8个元素)
  • 它们之间相隔 7 - 2 = 5 个元素

⚠️ 注意:指针相减的结果类型是 ptrdiff_t(有符号整数),表示元素个数差,不是字节差。


练习题 8(编程题) ​

题目: 请编写一个函数 void swap(int *a, int *b),使用指针实现两个整数的交换。并在主函数中测试:输入两个整数,输出交换后的结果。

🔑 点击查看答案与解析

参考答案:

cpp
#include <iostream>
using namespace std;

void swap(int *a, int *b) {
    int temp = *a;  // 保存 a 指向的值
    *a = *b;        // 将 b 指向的值赋给 a 指向的位置
    *b = temp;      // 将保存的值赋给 b 指向的位置
}

int main() {
    int x, y;
    cin >> x >> y;
    cout << "交换前:" << x << " " << y << endl;
    swap(&x, &y);   // 传入两个变量的地址
    cout << "交换后:" << x << " " << y << endl;
    return 0;
}

解析:

这是指针最经典的应用——交换两个变量。核心思路:

  1. 函数接收两个指针(地址)
  2. 通过解引用 *a 和 *b 访问并修改原变量
  3. 利用临时变量 temp 完成值的交换

🍎 生活比喻:就像交换两个杯子里的水,需要第三个空杯子(temp)。指针就是告诉你"杯子在哪",让你能直接操作杯子里的水。


十一、📊 考点总结表 ​

考点重要程度出题形式易错点
内存地址与 & 运算符⭐⭐选择/判断混淆"变量的值"和"变量的地址"
指针定义与初始化⭐⭐⭐选择/程序阅读int* p, q; 中 q 不是指针
解引用 * 访问与修改⭐⭐⭐⭐⭐程序阅读/代码填空未初始化的指针不能解引用
指针赋值与接力问题⭐⭐⭐⭐⭐程序阅读(高频)赋值后两个指针指向同一地址,修改一个影响另一个
空指针与野指针⭐⭐⭐选择/判断野指针的值是随机的,解引用是UB
指针与一维数组⭐⭐⭐⭐程序阅读/代码填空数组名是常量指针,不能 arr++
指针算术运算⭐⭐⭐⭐选择/程序阅读p++ 移动 sizeof(类型) 字节,不是1字节
const 与指针三种组合⭐⭐⭐选择/判断左定值右定向,记混左右

⭐ 必背速记卡 ​

┌─────────────────────────────────────────────┐
│         指针与内存地址 · 必背速记卡          │
├─────────────────────────────────────────────┤
│  1. 指针存地址,不是存值                     │
│  2. & 取地址,* 解引用,互为逆运算           │
│  3. int* p, q; 中 q 是 int 不是指针          │
│  4. p 是地址值,*p 是指向的值,&p 是自身地址 │
│  5. 指针赋值复制的是地址,不是数据           │
│  6. 野指针 = 指向无效内存(未初始化/已释放/越界),解引用是未定义行为(可能崩溃,也可能不出错只给乱值) │
│  7. 空指针 = nullptr,使用前判空             │
│  8. 数组名 = 首元素地址(常量指针)           │
│  9. arr[i] ≡ *(arr + i),完全等价            │
│ 10. p++ 移动 sizeof(类型) 字节               │
│ 11. 同数组指针相减 = 相隔元素个数            │
│ 12. 左 const 定值(指向的值不能改)          │
│ 13. 右 const 定向(指针指向不能改)          │
│ 14. 指针接力题:画指向图,步步更新           │
│ 15. void* 不能直接解引用,要强转后用         │
└─────────────────────────────────────────────┘

百炼成钢,融会贯通