Appearance
第2章 指针与内存地址
🎯 本章学习目标
通过本章的学习,你将能够:
- 理解指针本质:明白指针是存储内存地址的变量,建立"地址-值"的内存模型
- 掌握取地址与解引用:熟练使用
&取地址和*解引用,分清二者的逆运算关系 - 正确定义指针:掌握指针变量的定义语法,避免
int* p, q;这类常见陷阱 - 理解指针赋值:掌握指针之间的赋值规则,能分析"指针接力"问题
- 识别空指针与野指针:知道二者的区别和危害,养成安全使用指针的习惯
- 指针操作数组:能用指针遍历一维数组,理解
arr[i]与*(arr+i)的等价性 - 掌握指针算术:理解
p++移动的是sizeof(类型)字节,不是 1 字节 - 辨析const与指针:能区分"指向常量的指针"和"常量指针"三种组合
- 阅读指针代码:能快速分析指针相关的程序阅读题,画出指向图
- 避免常见错误:识别野指针、越界访问、类型不匹配等指针易错点
🗺️ 本章知识图谱
指针与内存地址
├── 基础概念
│ ├── 内存地址(变量的"门牌号")
│ ├── & 取地址运算符
│ └── 指针的本质(存地址的变量)
├── 指针定义与初始化
│ ├── 定义语法:类型 *变量名
│ ├── * 的位置陷阱(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变量:cppint* 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
解析:
逐行分析:
a=5, b=10,p1→a, p2→b*p1 = 15:通过 p1 修改 a,a=15p1 = p2:p1 改为指向 b,此时p1→b, p2→b*p1 = 20:通过 p1 修改 b,b=20- 最终
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;
}解析:
这是指针最经典的应用——交换两个变量。核心思路:
- 函数接收两个指针(地址)
- 通过解引用
*a和*b访问并修改原变量 - 利用临时变量
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* 不能直接解引用,要强转后用 │
└─────────────────────────────────────────────┘