玄武纪8 月 · CSP-J 初赛打卡DAY 18 / 20
Day 18 内存、地址与指针
建议用时:22~28 分钟
今日目标:分清变量的值、变量的地址和指针中保存的地址,掌握 &、*、指针赋值、空指针、数组与链式节点中的基本用法。
大纲定位:入门级指针与基本动态数据结构阅读;不展开复杂指针、函数指针和手工内存管理技巧。
一、一个变量要同时看“内容”和“位置”
程序运行时,变量会占用内存中的一块空间。这块空间存放变量的值,同时有一个用于定位它的地址。
int a = 10;
可以把 a 想成一个贴着门牌号的盒子:盒子里的内容是 10,门牌号是 a 的地址。地址的具体数字由运行环境决定,初赛程序阅读一般只关心“它是谁的地址”,不需要猜真实十六进制值。
cout << a; // 变量的值
cout << &a; // 变量 a 的地址
这里的 & 是取地址运算符。它写在变量前面,得到变量在内存中的地址。
二、指针变量专门保存地址
int a = 10;
int *p = &a;
p 是一个“指向 int 的指针”,它保存 a 的地址。此时:
p:里面保存的是地址;
*p:沿着这个地址找到目标,读出目标的值;
&a:取得 a 的地址;
- 因为
p == &a,所以 *p 就是 a。
*p = 20;
cout << a; // 20
给 *p 赋值是在修改“所指向的变量”,不是修改指针本身保存的地址。

三、p = q 与 *p = *q 完全不同
int x = 101, y = 201;
int *p = &x;
int *q = &y;
若执行 p = q;,是把 q 中保存的地址复制给 p。执行后 p 和 q 都指向 y,x、y 的数值都没有因此改变。
若执行 *p = *q;,是把 q 所指向的值复制到 p 所指向的位置。执行前 p 指向 x、q 指向 y,因此这句会把 201 写入 x,而两个指针的指向不变。
| 语句 |
改变什么 |
结果 |
p = q |
指针 p 保存的地址 |
p 改为与 q 指向同一对象 |
*p = *q |
p 所指对象的值 |
目标变量被赋值,指针指向不变 |
p = &x |
指针 p 保存的地址 |
p 指向 x |
*p = 7 |
p 所指对象的值 |
目标变量变为 7 |
这正是 CSP-J 指针选择题最常见的设问方式。

四、空指针、野指针和悬空指针
int *p = nullptr;
nullptr 明确表示“当前不指向有效对象”。可以判断 p == nullptr,但不能执行 *p 或 p->member,因为没有有效目标可访问。
- 空指针:值为
nullptr,状态明确,但不能解引用;
- 野指针:指针未初始化,里面是不确定的地址;
- 悬空指针:原来指向的对象已经失效或内存已经释放,指针还保留旧地址。
安全的基本习惯是:定义时初始化;解引用前确保目标有效;释放动态内存后及时把不再使用的指针设为 nullptr。初赛题若让空指针或无效指针被解引用,通常应判断为错误或未定义行为,不能继续按普通数值计算。
五、数组名与指针的基本关系
int a[4] = {10, 20, 30, 40};
int *p = a;
在多数表达式中,数组名 a 会转换为指向首元素 a[0] 的指针。因此:
*p // a[0],值为 10
*(p + 1) // a[1],值为 20
p[2] // a[2],值为 30
指针加 1 表示移动到“下一个同类型元素”,不是把地址的数值简单加 1 字节。若 p 指向 int 数组元素,p+1 就指向下一个 int。
“数组名就是一个可以随意赋值的普通指针变量”并不准确。数组名能在许多表达式中转换为首元素指针,但不能写 a = p; 来改变数组的位置。
六、链式节点:指针把节点连起来
struct Node {
int data;
Node *next;
};
每个节点保存一份数据和下一个节点的地址。head 指向第一个节点,最后一个节点的 next 通常为 nullptr。
在表头插入值为 42 的新节点,顺序是:
Node *newNode = new Node;
newNode->data = 42;
newNode->next = head;
head = newNode;
先让新节点接住原来的表头,再更新 head。如果先把 head 改为新节点,却没有保存原表头地址,原来的链表可能就接不上了。

七、指针程序的草稿纸画法
不要在脑中追地址,画两种符号即可:
x = 101 y = 201
p ───────→ x
q ───────→ y
执行 p=q 后,只改箭头:
x = 101 y = 201
↑
p ──┤
q ──┘
执行 *p=300 后,再改箭头终点盒子里的值。坚持“赋值号左边是谁就改谁”:左边是 p,改指针;左边是 *p,改目标。
今日选择题
第 1、2、4 题为近五年 CSP-J 第一轮单选原题;第 3、5 题为专题练习;第 6~8 题围绕同一段指针程序阅读。
【CSP-J 2022·第 3 题】 运行以下代码片段的行为是( )。
int x = 101;
int y = 201;
int *p = &x;
int *q = &y;
p = q;
A. 将 x 的值赋为 201
B. 将 y 的值赋为 101
C. 将 q 指向 x 的地址
D. 将 p 指向 y 的地址
【CSP-J 2023·第 4 题】 已知链表节点定义如下,并有指向链表头部的指针 Node *head。要插入一个 data 为 42 的新节点,并使新节点成为第一个节点,正确操作是( )。
struct Node {
int data;
Node *next;
};
A. Node* newNode = new Node; newNode->data = 42; newNode->next = head; head = newNode;
B. Node* newNode = new Node; head->data = 42; newNode->next = head; head = newNode;
C. Node* newNode = new Node; newNode->data = 42; head->next = newNode;
D. Node* newNode = new Node; newNode->data = 42; newNode->next = head;
执行下面的代码后,a 的值是( )。
int a = 7;
int *p = &a;
*p = 12;
A. 7 B. 12 C. a 的地址 D. 无法确定
【CSP-J 2022·第 4 题】 链表和数组的区别包括( )。
A. 数组不能排序,链表可以
B. 链表比数组能存储更多的信息
C. 数组大小固定,链表大小可动态调整
D. 以上均正确
关于 int *p = nullptr;,下列说法正确的是( )。
A. p 当前不指向有效的 int 对象
B. *p 的值一定为 0
C. 可以安全执行 *p = 5
D. p 是一个普通整数变量
主题程序阅读
阅读下面的程序:
#include <iostream>
using namespace std;
int main() {
int a[4] = {2, 4, 6, 8};
int *p = a;
int *q = p + 2;
*p = *q;
q = p + 3;
*q += a[1];
cout << a[0] << ' ' << a[2] << ' ' << a[3] << endl;
return 0;
}
执行 int *q = p + 2; 后,q 指向( )。
A. a[0] B. a[1] C. a[2] D. a[3]
执行 *p = *q; 后,a[0] 的值是( )。
A. 2 B. 4 C. 6 D. 8
程序最终输出( )。
A. 6 6 8 B. 6 6 12 C. 2 6 12 D. 6 4 12
暂停 · 先完成并提交
画箭头,不猜地址
请先独立完成全部题目,并到玄武 OJ 提交今日答案。
- `p=q` 只改指向,`*p=*q` 才改目标值;
- 数组指针每移动 1 格,就跨过一个同类型元素;
- 提交后再向下订正并重画错题箭头。
答案与解析
下面按“题目 → 答案 → 解析”的顺序订正。
第 1 题|指针之间赋值
【CSP-J 2022·第 3 题】 运行以下代码片段的行为是( )。
int x = 101;
int y = 201;
int *p = &x;
int *q = &y;
p = q;
A. 将 x 的值赋为 201
B. 将 y 的值赋为 101
C. 将 q 指向 x 的地址
D. 将 p 指向 y 的地址
答案:D
解析: p=q 把 q 保存的地址复制给 p。原来 q 指向 y,所以执行后 p 也指向 y;这句没有解引用,不会修改 x 或 y 的值。
第 2 题|链表表头插入
【CSP-J 2023·第 4 题】 已知链表节点定义如下,并有指向链表头部的指针 Node *head。要插入一个 data 为 42 的新节点,并使新节点成为第一个节点,正确操作是( )。
struct Node {
int data;
Node *next;
};
A. Node* newNode = new Node; newNode->data = 42; newNode->next = head; head = newNode;
B. Node* newNode = new Node; head->data = 42; newNode->next = head; head = newNode;
C. Node* newNode = new Node; newNode->data = 42; head->next = newNode;
D. Node* newNode = new Node; newNode->data = 42; newNode->next = head;
答案:A
解析: 新节点先保存数据,再令 newNode->next=head 接住原链表,最后令 head=newNode 更新表头。D 少了最后一步,新节点还不会成为 head。
第 3 题|通过指针修改变量
执行下面的代码后,a 的值是( )。
int a = 7;
int *p = &a;
*p = 12;
A. 7 B. 12 C. a 的地址 D. 无法确定
答案:B
解析: p 指向 a,所以 *p 就是 a。给 *p 赋 12 等价于给 a 赋 12。
第 4 题|数组与链表
【CSP-J 2022·第 4 题】 链表和数组的区别包括( )。
A. 数组不能排序,链表可以
B. 链表比数组能存储更多的信息
C. 数组大小固定,链表大小可动态调整
D. 以上均正确
答案:C
解析: 普通数组定义后大小固定,链表可以通过增加或删除节点动态调整长度。数组和链表都能参与排序;“能存储多少信息”还受可用内存等条件影响,不能据此断言链表一定更多,因此只有 C 正确。
第 5 题|空指针
关于 int *p = nullptr;,下列说法正确的是( )。
A. p 当前不指向有效的 int 对象
B. *p 的值一定为 0
C. 可以安全执行 *p = 5
D. p 是一个普通整数变量
答案:A
解析: nullptr 表示不指向有效对象。空指针可以参与是否为空的判断,但不能解引用;*p 不是 0,而是无效访问。
第 6~8 题共用程序|数组中的指针移动
阅读下面的程序:
#include <iostream>
using namespace std;
int main() {
int a[4] = {2, 4, 6, 8};
int *p = a;
int *q = p + 2;
*p = *q;
q = p + 3;
*q += a[1];
cout << a[0] << ' ' << a[2] << ' ' << a[3] << endl;
return 0;
}
第 6 题|确定指向位置
执行 int *q = p + 2; 后,q 指向( )。
A. a[0] B. a[1] C. a[2] D. a[3]
答案:C
解析: p=a 指向 a[0],向后移动两个元素后,q 指向 a[2]。
第 7 题|解引用赋值
执行 *p = *q; 后,a[0] 的值是( )。
A. 2 B. 4 C. 6 D. 8
答案:C
解析: 此时 p 指向 a[0],q 指向 a[2]。语句把 a[2] 的值 6 写入 a[0]。
第 8 题|完整追踪
程序最终输出( )。
A. 6 6 8 B. 6 6 12 C. 2 6 12 D. 6 4 12
答案:B
解析: *p=*q 后数组为 {6,4,6,8};随后 q=p+3 指向 a[3],*q+=a[1] 使 a[3]=8+4=12,输出 6 6 12。
完成后检查
- 我能区分变量值、变量地址和指针中保存的地址,并正确解释
& 与 *。
- 我不会混淆
p=q 和 *p=*q,会用箭头图追踪指向变化。
- 我能读懂数组指针和链表表头插入的基本代码,也知道空指针不能解引用。
本页用于 CSP-J 第一轮自主复习。请先看知识卡并独立完成练习,提交玄武 OJ 后再查看解析。