
第九课 指针、地址与引用
——变量到底住在内存的什么地方?
这一课要解决 CSP-J 初赛中一个非常核心的问题:
程序中的变量,到底是怎样放进计算机里的?
&a 是什么?
*p 是什么?
p = &a 到底做了什么?
为什么修改 *p,变量 a 也会跟着变化?
首先从计算机内存讲起:内存由许多存储单元组成,每个字节都有一个固定编号,这个编号称为地址,CPU 按地址访问内存中的数据。
所以这一课真正要建立的是:
“变量”和“变量的地址”是两回事。
一、先从一个故事开始:每个变量都有自己的“房间”
假设:
int a = 10;
我们可以把它想象成学校宿舍:
变量名:a
房间号:1000
房间里的东西:10
也就是:
内存
┌─────────────┐
1000 → │ 10 │
└─────────────┘
↑
│
a
这里有两个完全不同的东西:
① a
表示:
房间里面存放的东西。
也就是:
10
② &a
表示:
这个房间的门牌号。
例如:
1000
所以:
a 是数据,&a 是地址。
这是本课第一个必须掌握的内容。
二、什么是内存地址?
内存地址:
内存中的每个字节都有一个固定编号,这个编号叫作地址,CPU 按地址访问数据。
可以把内存想象成一条特别长的街道:
地址:
1000 1001 1002 1003 1004 1005
↓ ↓ ↓ ↓ ↓ ↓
┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐
│ │ │ │ │ │ │ │ │ │ │ │
└───┘ └───┘ └───┘ └───┘ └───┘ └───┘
每个位置都有自己的编号。
因此:
地址就是内存中一个位置的编号。
三、&:取地址运算符
例如:
int a = 10;
如果写:
cout << &a;
意思不是输出10。
而是:
告诉我变量 a 在内存中的地址。
例如可能输出:
0x61ff0c
这个具体数字每次运行都可能不同。
所以 CSP-J 程序阅读时:
一般不要纠结地址具体是多少。
我们真正关心的是:
谁和谁指向同一个内存位置?
四、一个非常重要的区别
看下面:
int a = 10;
cout << a;
cout << &a;
第一个:
a
表示:
a里面的数据。
第二个:
&a
表示:
a所在的位置。
可以记成:
a
↓
“里面是什么?”
&a
↓
“住在哪里?”
五、什么是指针?
现在假设:
int a = 10;
我们已经知道:
&a
可以得到 a 的地址。
那么:
如果我想把这个地址保存起来怎么办?
这就是指针。
例如:
int *p;
这里的 p 是一个:
指针变量。
它专门用来保存地址。
六、int *p 到底是什么意思?
不要把它死记成:
“星号就是指针。”
更准确的理解是:
int *p;
表示:
p 是一个指针,它保存的是一个 int 类型变量的地址。
例如:
int a = 10;
int *p = &a;
现在:
a
┌─────┐
│ 10 │
└─────┘
↑
│
│ &a
│
┌─────┐
p ───→ │地址 │
└─────┘
也就是说:
p里面保存的是a的地址。
所以:
p = &a;
可以理解成:
“让p记住a住在哪里。”
七、指针最重要的三个符号
本课要把三个概念分开:
int a = 10;
int *p = &a;
a
变量本身:
10
&a
变量的地址:
a住在哪里
p
保存这个地址:
p里面存着&a
八、那么 *p 又是什么?
这是指针最容易把初学者绕晕的地方。
现在:
int a = 10;
int *p = &a;
因为:
p → a
所以:
*p
意思是:
“顺着p保存的地址,找到那个变量。”
因此:
cout << *p;
输出:
10
九、用“快递地址”理解 *
可以这样理解。
假设:
p里面保存:
“幸福路100号”
那么:
p
是:
地址纸条。
而:
*p
是:
根据地址找到房子,然后看看房子里面有什么。
所以:
p
↓
地址
*p
↓
这个地址对应的内容
这就是指针最核心的思想。
十、*p 不仅能读,还能修改
这是重要的一步。
int a = 10;
int *p = &a;
*p = 20;
发生了什么?
第一步:
a = 10
第二步:
p → a
第三步:
*p = 20;
意思:
找到 p 指向的那个变量,把它改成20。
因此:
cout << a;
输出:
20
十一、程序阅读的重点
以后看到:
int a = 10;
int *p = &a;
*p = 100;
一定要马上想到:
p
↓
a
因此:
a = 100
而不是认为:
“修改了p。”
实际上:
修改的是p指向的那个变量。
十二、画箭头,是解决指针题,好的方法
例如:
int a = 5;
int b = 10;
int *p = &a;
int *q = &b;
不要在脑子里想。
直接画:
p ─────→ a = 5
q ─────→ b = 10
然后:
*p = 20;
得到:
p ─────→ a = 20
q ─────→ b = 10
十三、经典程序阅读题
看:
int a = 10;
int b = 20;
int *p = &a;
*p = 30;
p = &b;
*p = 40;
cout << a << " " << b;
我们一步一步模拟。
第一步
int a = 10;
int b = 20;
状态:
a = 10
b = 20
第二步
p = &a;
画:
p ─────→ a
10
第三步
*p = 30;
所以:
p ─────→ a
30
第四步
p = &b;
注意!
这一步不是修改 b。
而是:
让p改去指向b。
现在:
p ─────→ b
20
第五步
*p = 40;
因此:
p ─────→ b
40
最终:
a = 30
b = 40
输出:
30 40
十四、指针题容易出现的“换目标”
例如:
int a = 1;
int b = 2;
int *p = &a;
p = &b;
有的孩子会认为:
a和b发生了交换。
完全错误。
实际上只是:
原来:
p ──→ a
后来:
p ──→ b
a和b里面的数字完全没有改变。
所以一定要记:
p = &b 是“换指向”,不是“换数据”。
十五、两个指针可以指向同一个变量
例如:
int a = 10;
int *p = &a;
int *q = &a;
现在:
┌───────┐
p ────→│ │
│ a=10 │
q ────→│ │
└───────┘
所以:
*p = 20;
那么:
cout << *q;
也是:
20
因为:
p和q指向的是同一个变量。
十六、这就是“别名”思想
两个名字:
p
q
实际上都找到了:
同一个a
所以:
一个人有两个绰号
改的是同一个人。
这是以后理解:
-
引用
-
函数引用传参
-
链表
-
动态内存
-
树
-
图
的重要基础。
十七、指针和数组有什么关系?
例如:
int a[5] = {10,20,30,40,50};
数组中的元素:
a[0] a[1] a[2] a[3] a[4]
可以想象成一排连续的小房间:
┌────┬────┬────┬────┬────┐
│ 10 │ 20 │ 30 │ 40 │ 50 │
└────┴────┴────┴────┴────┘
而指针可以指向其中某个位置。
例如:
int *p = &a[2];
那么:
p
↓
a[2]
↓
30
所以:
cout << *p;
输出:
30
十八、指针与数组的一个重要关系
考试中经常会看到:
*(a + i)
它与:
a[i]
有密切关系。
在C++中:
a[i]
可以理解为:
*(a+i)
这一点对初赛中的程序阅读很重要。
十九、指针移动
假设:
int a[5] = {10,20,30,40,50};
int *p = a;
可以理解为:
p
↓
10
如果:
p++;
那么:
p
↓
20
再:
p++;
变成:
p
↓
30
所以:
指针可以沿着数组移动。
二十、为什么 p++ 不是简单地加1?
这是一个稍微提高一点的知识。
如果:
int *p;
那么:
p++
表示:
指向下一个 int。
并不是简单地让地址数字增加1。
因为一个 int通常占多个字节。
这一点在初赛中可能作为概念题出现,因此孩子应该记住:
指针加1,移动的是“一个元素”,而不是简单的一个字节。
二十一、空指针 nullptr
有一个非常重要的概念:
int *p = nullptr;
表示:
p目前没有指向任何有效变量。
可以画成:
p ─────→ 什么也没有
这和:
int *p = &a;
完全不同。
后者:
p ─────→ a
二十二、为什么不能随便 *p?
如果:
int *p = nullptr;
然后:
cout << *p;
就是错误的。
因为:
p没有告诉计算机“去哪里找”。
所以:
p = nullptr
的时候:
不要解引用 *p。
二十三、野指针是什么?
对于 CSP-J 初赛,只需要形成一个初步概念。
例如:
int *p;
这里的 p 没有初始化。
它里面可能是一个乱七八糟的地址。
这类指针可能成为:
野指针。
可以理解成:
“它像一个没有写地址的快递员,却被要求去送货。”
所以:
int *p = nullptr;
比:
int *p;
安全得多。
二十四、引用是什么?
讲完指针之后,要掌握引用:
int a = 10;
int &b = a;
这里的:
b
是 a 的:
引用。
可以理解成:
b不是另外创建了一个新的10,而是给 a 起了另一个名字。
二十五、指针和引用的直观区别
例如:
int a = 10;
int &b = a;
可以画:
┌───────┐
a ─────→│ 10 │
└───────┘
↑
│
b
也就是说:
a和b
是同一个变量的两个名字。
所以:
b = 20;
之后:
cout << a;
得到:
20
二十六、指针和引用的区别
这是初赛经常考选择题的地方。
| 保存地址 | 是 | 本质上是别名 |
| 可以修改指向 | 可以 | 一旦绑定不能改绑 |
| 可以为空 | 可以,用nullptr | 通常不能表示“空引用” |
| 使用数据 | *p | 直接使用名字 |
| 常见形式 | int *p | int &r |
例如:
int a = 10;
int b = 20;
int *p = &a;
p = &b;
可以。
但:
int &r = a;
之后:
r = b;
不是让 r 改去引用 b。
而是:
把 b 的值赋给 a。
也就是:
a = b
这一点容易出错。
二十七、为什么函数参数经常出现引用?
前面的第7课我们讲了函数。
例如:
void change(int x)
{
x = 100;
}
调用:
int a = 10;
change(a);
函数里的 x 是一个副本。
所以:
a还是10
但是:
void change(int &x)
{
x = 100;
}
调用:
int a = 10;
change(a);
此时:
x
和
a
是同一个变量。
所以:
a变成100
这就把:
“函数参数”
和
“引用”
连接起来了。
二十八、CSP-J程序阅读经典模型
以后看到:
void f(int &x)
{
x++;
}
int main()
{
int a = 10;
f(a);
cout << a;
}
不要认为:
f里面有一个新的x。
应该画:
a ───┐
↓
[10]
↑
│
x
执行:
x++;
之后:
[11]
所以:
a = 11
二十九、指针参数也一样
例如:
void f(int *p)
{
*p = 100;
}
int main()
{
int a = 10;
f(&a);
cout << a;
}
调用:
f(&a);
相当于把:
a的地址
交给了 p。
所以:
p ───→ a
10
执行:
*p = 100;
变成:
p ───→ a
100
最终:
a = 100
三十、这一题一定要会画
void f(int *p)
{
(*p)++;
}
int main()
{
int a = 5;
f(&a);
cout << a;
}
分析:
调用之前
a = 5
f(&a)
p ───→ a
5
(*p)++
p ───→ a
6
所以输出:
6
三十一、本课与前面知识的真正连接
现在可以把前面9课串起来:
变量
↓
表达式
↓
条件、循环
↓
函数
↓
数组
↓
字符串
↓
栈、队列
↓
指针、引用
↓
内存
同学们开始逐渐建立一个完整的认识:
程序
↓
变量
↓
变量存放在内存
↓
内存有地址
↓
指针可以保存地址
↓
通过地址找到变量
↓
函数可以通过引用/指针修改变量
三十二、本课最重要的“符号翻译表”
同学们要背下来。
| a | a这个变量的值 |
| &a | a的地址 |
| int *p | p是指向int的指针 |
| p = &a | p指向a |
| p | p里面保存的地址 |
| *p | 找到p指向的变量 |
| *p = 20 | 修改p指向的变量 |
| nullptr | 不指向有效对象 |
| int &r = a | r是a的另一个名字 |
三十三、CSP-J初赛指针题“五步法”
同学们看到指针程序,不要慌。
严格按照:
第一步:找变量
a、b、x……
第二步:找指针
p、q……
第三步:画箭头
例如:
p → a
q → b
第四步:看到 *p
马上翻译:
找到p指向的变量。
第五步:程序每执行一步,就更新图
例如:
p → a=10
*p=20
p → a=20
三十四、综合例题
CSP-J 程序阅读题:
void f(int *p, int &x)
{
*p += 10;
x += 20;
}
int main()
{
int a = 5;
int b = 10;
f(&a, b);
cout << a << " " << b;
}
我们一步一步看。
第一步
a = 5
b = 10
第二步
调用:
f(&a,b);
因此:
p ───→ a
5
x ───→ b
10
注意:
p是指针。
x是引用。
第三步
*p += 10;
所以:
a = 15
第四步
x += 20;
因为:
x就是b的另一个名字
所以:
b = 30
最终:
a = 15
b = 30
输出:
15 30
三十五、本课需要掌握的知识层次
对于准备 CSP-J 初赛的小学生,要分成三个层次来看。
★ 第一层:必须掌握
变量
地址
&a
指针
*p
p=&a
nullptr
要求:
能读懂程序。
★★ 第二层:重点掌握
指针修改变量
多个指针指向同一个变量
指针与数组
引用
引用参数
指针参数
要求:
能够完成程序模拟。
★★★ 第三层:提高了解
指针运算
*(a+i)
动态内存
new/delete
指针与结构体
结构体指针,以及 new/delete、malloc/free 的区别;例如结构体指针访问成员时可以使用 ->,动态分配的结构体对象可以用 new,最后用 delete 释放。
这些内容可以参考后面专项讲义。
三十六、课后练习
第一组:看懂符号
判断:
int a=10;
int *p=&a;
分别问:
a是什么?
&a是什么?
p是什么?
*p是什么?
第二组:画箭头
int a=10;
int b=20;
int *p=&a;
p=&b;
*p=100;
要求:
不运行程序,只画图并说出a、b最后是多少。
第三组:函数参数
比较:
void f(int x)
void f(int &x)
void f(int *x)
请回答:
哪些情况下能修改调用者的变量?
第四组:CSP-J程序阅读
专门训练:
“指针箭头图 + 状态表”
不要在脑子里算,要画出来。
三十七、本课给同学的一句话
变量就像房间,值是房间里的东西,地址是房间号,指针就是记住这个房间号的纸条,而 *p 就是拿着纸条找到那个房间。
还有要注意:
看到指针题,千万别怕;先画箭头,再做程序模拟。


实现100%通过率-171主机测评](https://www.171host.com/wp-content/uploads/2026/10/20261026191216-6adfa61015a7f-220x80.gif)