第 28 章 门牌号——指针基础
🏗️ 前情回顾:结构体把数据打包得整整齐齐——一个学生的姓名、年龄、分数全在一个档案袋里。但你有没有想过:这个档案袋在内存的哪个位置?能不能想办法直接找到地址、绕过变量名去操作它?就像一个快递员,不喊收件人名字,直接按门牌号敲门——C++ 能做到吗?能。答案就是本章主角——指针。
🎯 本章目标
学完这一章,你能:
- 用
&运算符获取任意变量的内存地址 - 声明指针变量,并让它指向某个变量
- 用
*运算符通过指针间接访问和修改变量的值 - 理解
nullptr的含义和使用场景 - 区分指针变量本身的大小和被指向对象的大小
- 绘制"指针 → 变量"的内存指向关系图
📖 故事引入
你有一本通讯录,里面写着:
小明 — 阳光小区 3 号楼 502 室
现在你要去小明家。你有两种办法:
- 办法 A:直接喊"小明!"——他听到就回应你。(就像你直接用变量名
cout << x;) - 办法 B:翻开通讯录,找到小明家的地址,然后按照地址找过去。(就像你用一个指针,存着小明的地址,然后顺着地址找到他)
办法 A 简单直接,但有限制——你只能在"能喊到小明的地方"叫他。办法 B 灵活得多——只要你有地址,不管小明在哪儿你都能找到。
指针就是编程里的"通讯录"——它不存数据本身,它存的是数据在哪里。
🧱 知识讲解
28.1 内存地址:每间房子的门牌号
计算机的内存就像一栋巨大的公寓楼,里面有几十亿个房间。每个房间能存 1 个字节的数据,而且每个房间都有唯一的门牌号——地址。
当你写 int x = 42; 时,计算机会:
- 在内存里找到 4 个连续的空房间(因为 int 占 4 字节)
- 在这 4 个房间里存进数字 42
- 把第一个房间的门牌号记为"x 的地址"
用 & 运算符可以查看这个地址:
int x = 42;
cout << "x 的值是:" << x << endl;
cout << "x 的地址是:" << &x << endl; // 输出一个十六进制数,如 0x7ffd4a3b6c2c运行效果类似:
x 的值是:42
x 的地址是:0x7ffd4a3b6c2c& 读作"取地址"。&x 的意思是"请告诉我 x 住在哪"。
💡 每次运行程序,地址可能不一样——因为操作系统每次分配的内存位置可能不同。这很正常。
28.2 指针变量:专门存地址的变量
既然地址也是一个值(一串数字),那能不能用一个变量专门存它?当然可以。这种变量叫指针变量,简称指针。
int x = 42;
int* p = &x; // p 是一个指针,存着 x 的地址拆开看:
| 部分 | 含义 |
|---|---|
int* | "这是一个指针类型,它指向的东西是 int" |
p | 指针变量的名字 |
= &x | 把 x 的地址存进 p |
可以这样理解:
int类型的变量存整数double类型的变量存小数int*类型的变量存"int 变量的地址"
声明指针的三种写法本质上一样:
int* p; // 星号紧挨 int——强调"int* 是一种类型"
int *p; // 星号紧挨变量名——强调"p 是指针"
int * p; // 星号在中间本书统一用 int* p; 的写法。选一种你喜欢的,保持统一即可。
28.3 解引用:顺着地址找到房子
有了地址,怎么"按照地址找到房子"?用 * 运算符——解引用。
❌ 最常见的指针悲剧——没初始化就解引用:
cppint* p; // p 没初始化,里面是个垃圾地址 *p = 42; // ❌ 往垃圾地址写数据——程序崩溃或诡异行为! int* q = nullptr; *q = 10; // ❌ 往空地址写数据——立即崩溃!黄金法则:指针必须先指向一个有效的变量,才能解引用。
✅ 正确用法——先指向,再解引用:
int x = 42;
int* p = &x; // p 存着 x 的地址
cout << *p << endl; // 输出 42(顺着地址找到了 x 的值)
*p = 100; // 顺着地址找到 x,把它的值改成 100
cout << x << endl; // 输出 100——x 真的被改了!注意区别同一个 * 在不同位置的两种含义:
| 位置 | 含义 | 示例 |
|---|---|---|
声明时(int* p) | "p 是一个指针类型" | int* p = &x; |
使用时(*p) | "解引用,去 p 指向的地方" | cout << *p; |
💡 记忆技巧:声明时是"自我介绍"——"我是 int* 类型";使用时是"行动"——"我要去你指向的地方"。
28.4 指针的图解
内存示意图:
变量 x(地址 0x1000): ┌──────────┐
│ 42 │
└──────────┘
↑
指针 p(地址 0x2000): ┌──────────┐
│ 0x1000 │──────┘
└──────────┘
p 存着 0x1000(x 的地址)
*p 就是 42(x 的值)画这张图是理解指针的万能方法。以后遇到指针问题,先画图。
28.5 nullptr:空指针
有时候你需要一个指针"暂时不指向任何东西"。C++ 给你一个特殊值:nullptr。
int* p = nullptr; // p 现在不指向任何变量int* p = nullptr;
// 解引用空指针是危险操作——程序会崩溃!
// cout << *p; ← 千万不要这样做!
// 安全的做法:先用 if 判断
if (p != nullptr) {
cout << *p << endl; // 确认不是空指针再解引用
} else {
cout << "p 是空指针" << endl;
}nullptr 的用途:初始化指针变量,避免指向随机的垃圾地址。好习惯:声明指针时如果不马上赋值,就初始化为 nullptr。
28.6 指针的 sizeof
指针变量本身也占内存。不管它指向什么类型,指针变量本身的大小是固定的:
cout << sizeof(int*) << endl; // 通常是 8(64 位系统)或 4(32 位系统)
cout << sizeof(char*) << endl; // 和 int* 一样大
cout << sizeof(double*) << endl; // 和 int* 一样大但 sizeof(*p) 返回的是被指向对象的大小:
int x = 10;
int* p = &x;
cout << sizeof(p) << endl; // 指针本身的大小,通常 8
cout << sizeof(*p) << endl; // int 的大小,4💡
sizeof(p)是门牌号纸条的大小(都一样大),sizeof(*p)是房间的大小(不同类型的房间大小不同)。
28.7 指针的基本操作总结
int a = 10, b = 20;
int* p = &a; // 1. p 指向 a
cout << *p; // 2. 通过 p 读取 a 的值 → 10
*p = 30; // 3. 通过 p 修改 a 的值 → a 变成 30
p = &b; // 4. 让 p 改指向 b
cout << *p; // 5. 现在 *p 是 b 的值 → 20
p = nullptr; // 6. p 不指向任何东西指针的核心就六个字:存地址,访数据。
✋ 动手试试
试试 1:写程序输出几个普通变量的地址,观察它们的十六进制表示。注意观察:连续声明的几个变量,地址是不是也连续?
int a = 1, b = 2, c = 3;
cout << &a << endl << &b << endl << &c << endl;试试 2:声明一个指针 p 指向一个变量 x,分别用 x 和 *p 两种方式修改它的值,每次修改后输出。验证它们操作的是同一块内存。
试试 3:声明一个 int* 指针和一个 double* 指针,分别输出 sizeof(指针) 和 sizeof(*指针),比较两者的差异。
试试 4:先创建一个 nullptr 指针,用 if 判断后安全地输出提示信息。然后让它指向一个变量,再判断一次。
🦶 你踩过这些坑吗
逐一核对,看你是不是也中过招:
- [ ] 声明时
*和使用时*混淆:int* p; *p = 10;← p 没初始化就解引用,p 指向垃圾地址 → ✅int x; int* p = &x; *p = 10; - [ ] 不同类型指针混用:
double x = 3.14; int* p = &x;← ❌ 编译错误!类型不匹配 → ✅double* p = &x; - [ ] 解引用空指针:
int* p = nullptr; cout << *p;← 运行时崩溃!→ ✅ 解引用前先判断if (p != nullptr) - [ ] 以为
*p和p是一回事:int* p = &x; p = 100;← 把地址改成了 100,p 指向了莫名其妙的位置 → ✅*p = 100;才是修改 x 的值
📝 练习
基础题
1. 填空题
(1)获取变量 x 地址的运算符是 ____。
(2)声明一个指向 int 的指针变量 p 的写法是 ____。
(3)通过指针 p 访问它指向的值的运算符是 ____。
(4)表示"空指针"的关键字是 ____。
2. 读代码写结果
int a = 10, b = 20;
int* p = &a;
*p = 30;
p = &b;
*p = 40;
cout << a << " " << b << endl;(1)a 的值是? (2)b 的值是?
3. 选择题
(1)以下关于指针的说法,错误的是?
A. 指针变量存储的是地址 B. int* p 表示 p 是一个指向 int 的指针
C. 指针本身不占用内存 D. nullptr 表示指针不指向任何变量
(2)int x = 5; int* p = &x; 之后,*p 的值是?
A. x 的地址 B. 5 C. p 的地址 D. 不确定
4. 判断题
(1)在 64 位系统上,int* 和 double* 的大小通常相同。( )
(2)int* p = nullptr; *p; 不会导致程序崩溃。( )
提高题
5. 编程题 — 交换变量(指针版)
写一个函数 swap(int* p, int* q),用指针交换两个变量的值。在 main 中测试。
示例运行:
交换前:a = 3, b = 8
交换后:a = 8, b = 3(提示:在函数里通过解引用修改原变量。)
6. 编程题 — 指针输出
输入一个整数,用指针的方式输出它的值、它的地址、以及它占用的字节数。
7. 编程题 — 两数运算(指针版)
写一个函数 void calc(int a, int b, int* sum, int* diff, int* prod),用指针"返回"两数的和、差、积。在 main() 里测试。
挑战题
8. 分析题
下面这段代码能否正确运行?如果不能,哪里有问题?如果能,输出什么?
int x = 5;
int* p1 = &x;
int* p2 = p1;
*p1 = 10;
cout << *p2 << endl;9. 编程题 — 最大值指针
写一个函数 int* findMax(int* a, int* b),返回指向较大值的指针。在 main 中测试:用返回的指针修改那个较大的值(比如把它翻倍)。
🧠 本章小结
指针就是"存地址的变量"
&x → 取地址("x 住哪儿?")
int* p → 声明指针("p 是用来记地址的")
*p → 解引用("去 p 记的地址那儿看看")
nullptr → 空指针("暂时哪儿也不指")
sizeof(p) → 指针本身大小(门牌号纸条)
sizeof(*p) → 指向的内容大小(房间)
核心规则:类型要匹配、解引用前确保非空 p x
┌─────────┐ ┌─────────┐
│ &x │────→│ 42 │
└─────────┘ └─────────┘
p 里存的是地址 *p 就是 42📝 配套练习
共1题。M4指针只讲概念——数组名=首地址。大量练习留给M8链表。。★核心(课堂必做) ◆拓展(课后练习)
| 级别 | 题号 | 链接 | 覆盖知识点 |
|---|---|---|---|
| ★ 核心 | J0105 | https://hydro.ac/d/srqc/p/J0105 | 递归、数组传递隐含指针 |
💡 练习建议:先完成 1 道★核心题,确保掌握本章基本方法。
配套练习
共1题。M4指针只讲概念——数组名=首地址。大量练习留给M8链表。★核心(课堂必做)
| 级别 | 题号 | 链接 | 覆盖知识点 |
|---|---|---|---|
| ★ 核心 | J0105 | https://hydro.ac/d/srqc/p/J0105 | 递归、数组传递隐含指针 |
练习建议:先在课堂完成 1 道★核心题,掌握本章基本方法。
自查清单:
- [ ] 我会用
&取变量的地址 - [ ] 我能声明
int*类型的指针变量 - [ ] 我能用
*通过指针读写变量的值 - [ ] 我知道
nullptr是干什么的 - [ ] 我能区分
sizeof(p)和sizeof(*p) - [ ] 我会画指针→变量的内存关系图
- [ ] 我理解为什么解引用空指针会崩溃
🚀 下章预告:内存地址都会操作了——指针就像 GPS,想去哪就去哪。但程序一关,所有数据全没。辛苦算了一下午的结果,下次打开还得重新输入——能不能像游戏存档一样,存到文件里下次接着用?第 29 章——文件操作入门,让程序拥有跨越时间的记忆力。