指针¶
方法速览¶
| 任务 | 方法 | 口诀 |
|---|---|---|
| 取地址 | &x 得到 x 的地址 |
取址用 & |
| 解引用 | *p 访问 p 指向的变量 |
取值用 * |
| 数组与指针 | 数组名 = 首元素地址;a[i] = *(a + i) |
下标即偏移 |
| 指针加减 | p + 1 移动一个元素(int 指针走 4 字节) |
加一走一步 |
| 改实参 | 传 &x,函数内 *p 改本体 |
传址改本体 |
变量的指针¶
指针是存地址的变量。int* p 声明了一个指向 int 的指针 p:p 里存放的是一个 int 变量的地址——p 自己不是 int,它只是记着地址。
两个互逆操作:
| 表达式 | 含义 | 值(示例) |
|---|---|---|
x |
变量本身 | \(5\) |
&x |
x 的地址 | 如 0x7fff...a0 |
p |
指针存的地址 | 同 &x |
*p |
地址处的变量 | 即 x |
&(*p) / *(&x) |
互逆抵消 | 分别回到 p / x |
指针自己的大小与类型无关:64 位下所有指针都是 8 字节(sizeof(int*) == sizeof(double*) == 8)。
空指针:int* p = nullptr;(C++11 起),解引用空指针是运行时崩溃——“野指针”指未初始化就使用。
数组与指针¶
数组名在表达式里退化为首元素地址:
a[i]=*(a + i):下标访问就是“首地址 + 偏移 i 个元素再解引用”;p + 1按元素大小移动:int 指针 +1 地址走 4 字节,double 指针走 8 字节——不是走 1 字节;*(p + 2)=p[2]:指针也能用下标。
例:p = a 后,p[1]、*(a + 1)、*(p + 1) 三种写法都取到 \(20\)。
一个例外:sizeof(a) 得到整个数组 20 字节(这是数组名不退化的少数场合之一);但把 a 传给函数后,形参是指针,sizeof 只剩 8。
函数与指针¶
指针作参数 = 把地址传进去,函数内 *p 直达本体:
链表结点就是“数据 + 自指指针”:
遍历模板:
练习题目¶
练习 1 · 取地址¶
题目
把变量 x 的地址存进指针 p,正确写法( )
- A.
int* p = x; - B.
int* p = &x; - C.
int p = *x; - D.
int& p = *x;
答案与解析
答案:B
指针存地址,&x 才是地址;A 把值当地址存,类型不符。
练习 2 · 解引用¶
题目
int x = 5; int* p = &x; cout << *p; 输出( )
- A. \(5\)
- B. p 的地址
- C. x 的地址
- D. 编译错误
答案与解析
答案:A
*p 解引用:顺着 p 里的地址找到 x 本身。
练习 3 · 通过指针修改¶
题目
int x = 5; int* p = &x; *p = 12; 之后 x 的值( )
- A. \(5\)
- B. 编译错误
- C. 未定义
- D. \(12\)
答案与解析
答案:D
*p = 12 是“给 p 指向的变量赋值”,就是给 x 赋值。
练习 4 · 改指针 vs 改值¶
题目
执行后( )- A. x 变为 2
- B. y 变为 1
- C. p 改指向 y,x、y 的值都不变
- D. 编译错误
答案与解析
答案:C
p = q 是指针赋值:p 里改存 y 的地址;两块内存都没动。要让 x 变 2 得写 *p = *q;。
练习 5 · *p = *q¶
题目
设 p 指向 x、q 指向 y(x = 1、y = 2),执行 *p = *q; 后( )
- A. x = 2, y = 1
- B. p 指向 y
- C. x = 2, y = 2
- D. x = 1, y = 1
答案与解析
答案:C
解引用后赋值是值拷贝:把 y 的值 2 抄进 x;y 不变、指向也不变。
练习 6 · 数组名即首地址¶
题目
int a[5]; 与 int* p = a; 等价于( )
- A.
p = &a[0]; - B.
p = a[0]; - C.
*p = &a; - D.
p = *a;
答案与解析
答案:A
数组名退化为首元素地址 &a[0];B 存的是首元素的值。
练习 7 · 下标与偏移等价¶
题目
int a[] = {10, 20, 30}; 则与 a[1] 等价的是( )
- A.
&a[1] - B.
*a + 1 - C.
a + 1 - D.
*(a + 1)
答案与解析
答案:D
a[i] = *(a + i)。
干扰项思路:B 是 *a + 1 = \(10 + 1 = 11\)(先解引用再加一);C、A 都是地址。
练习 8 · *a + 1 的值¶
题目
int a[] = {10, 20, 30}; cout << *a + 1; 输出( )
- A. \(21\)
- B. \(20\)
- C. \(11\)
- D. 地址值
答案与解析
答案:C
*a = a[0] = 10,再 +1 = 11。想取 a[1] 要加括号 *(a + 1)。
练习 9 · 指针加一走多远¶
题目
int* p 指向某 int,p + 1 使地址增加( )
- A. \(1\) 字节
- B. \(4\) 字节
- C. \(8\) 字节
- D. 不确定
答案与解析
答案:B
指针加减按元素大小移动:int 4 字节。double 指针 +1 才走 8。
练习 10 · 指针走数组¶
题目
输出( )- A.
6 4 - B.
6 2 - C.
8 4 - D.
4 6
答案与解析
答案:A
*(p + 2) 不改变 p:取 a[2] = 6;++p 让 p 指向 a[1] 再解引用 = 4。
练习 11 · sizeof 数组 vs 指针¶
题目
64 位下sizeof(a) 与 sizeof(p) 分别是( )
- A. \(40\) 和 \(8\)
- B. \(8\) 和 \(40\)
- C. \(40\) 和 \(40\)
- D. \(10\) 和 \(8\)
答案与解析
答案:A
sizeof(数组名) 是整个数组(此场合不退化);指针本身固定 8 字节。
练习 12 · 所有指针一样大¶
题目
64 位环境下,sizeof(char*)、sizeof(int*)、sizeof(double*) 的关系( )
- A. 分别是 1、4、8
- B. 都是 8
- C. 都是 4
- D. 不确定
答案与解析
答案:B
指针存的是地址,大小只跟机器位数有关(64 位 → 8 字节),与指向类型无关。
练习 13 · 空指针¶
题目
int* p = nullptr; cout << *p; 的行为( )
- A. 运行时崩溃(解引用空指针)
- B. 输出 0
- C. 编译错误
- D. 输出 p 的地址
答案与解析
答案:A
空指针“哪里都不指”,解引用是未定义行为,典型表现是段错误崩溃。
练习 14 · 野指针¶
题目
“野指针”指的是( )
- A. 指向数组的指针
- B. 未初始化就使用的指针
- C. 值为 nullptr 的指针
- D. 指向函数的指针
答案与解析
答案:B
未初始化的指针里是随机地址,用它读写是未定义行为;C 的空指针是“明确的无效”,好歹可判断。
练习 15 · 指针作参数¶
题目
调用后 x( )- A. \(7\)
- B. 编译错误
- C. 未定义
- D. \(8\)
答案与解析
答案:D
传地址 + 解引用,改到本体。注意 (*p)++ 的括号:*p++ 是 *(p++)(先用再加指针),含义完全不同。
练习 16 · 传址与传值对比¶
题目
之后 a、b 分别是( )- A. \(2, 1\)
- B. \(100, 100\)
- C. \(1, 2\)
- D. \(100, 2\)
答案与解析
答案:D
只把 a 的地址传了进去,函数只能碰到 a。
练习 17 · 链表结点定义¶
题目
单链表结点的典型定义( )
- A.
struct Node { int data; Node next; }; - B.
struct Node { int data; Node* next; }; - C.
struct Node { int* data; Node next; }; - D.
struct Node* { int data; Node next; };
答案与解析
答案:B
next 存下一个结点的地址,必须是指针;A 会无限嵌套定义。
练习 18 · 指针访问成员¶
题目
Node* p 指向某结点,访问其 data 成员写作( )
- A.
p.data - B.
data->p - C.
p->data - D.
p::data
答案与解析
答案:C
指针用 ->((*p).data 等价);p.data 用于结构体变量。
练习 19 · 遍历链表¶
题目
从 head 遍历到尾的循环条件,最恰当的是( )
- A.
p != head - B.
p != nullptr - C.
p->next != nullptr - D.
p != 0 && p != 1
答案与解析
答案:B
末结点的 next 是 nullptr,走到空即结束。
干扰项思路:C 会在最后一个结点提前停(末结点的 next 已空但本身还有数据)。
练习 20 · 走到下一结点¶
题目
让 p 从当前结点走到下一结点( )
- A.
p++ - B.
p = p->next; - C.
p = p->data; - D.
*p = p->next;
答案与解析
答案:B
next 存着下一结点地址,赋给 p 即“走到下一个”。
干扰项思路:A 只适用于连续内存(数组);链表结点不连续。
练习 21 · 指针与 const¶
题目
const int* p 与 int* const q 的区别( )
- A. 没有区别
- B. p 不能通过解引用改值但可改指向;q 不能改指向但可改值
- C. p 不能改指向;q 不能改值
- D. 两者都不能改值
答案与解析
答案:B
const int* p:指向的东西只读;int* const q:指针本身只读。口诀:const 在 * 左边管值,在 * 右边管指向。
练习 22 · 二级指针¶
题目
int x = 3; int* p = &x; int** q = &p; 则 **q 的值( )
- A. x 的地址
- B. 编译错误
- C. p 的地址
- D. \(3\)
答案与解析
答案:D
q 存 p 的地址:*q 是 p(一个地址),**q 再解一次 = x = 3。
练习 23 · 交换两个指针的指向¶
题目
之后*p 与 *q 分别是( )
- A. \(1, 2\)
- B. \(2, 2\)
- C. \(2, 1\)
- D. 未定义
答案与解析
答案:C
三个指针变量轮换指向:p 改指 b、q 改指 a;a、b 的值没动。
练习 24 · 指针比较¶
题目
判断 p 是否“没指向任何东西”,应写( )
- A.
p == 0或p == nullptr - B.
*p == 0 - C.
p == &0 - D.
sizeof(p) == 0
答案与解析
答案:A
空指针与 0、nullptr 比较是标准判空写法;B 是“指向的值为 0”,且空指针时解引用直接崩。
练习 25 · 数组作参数后的指针行为¶
题目
64 位下输出( )- A. \(40\)
- B. \(10\)
- C. \(8\)
- D. \(80\)
答案与解析
答案:C
形参 int a[] 就是 int*——退化后 sizeof 得到指针大小。
练习 26 · 综合追踪¶
题目
输出( )- A. \(1\)
- B. \(2\)
- C. \(4\)
- D. \(3\)
答案与解析
答案:C
p 指向 a[1];*(p-1) = a[0] = 1,*(p+1) = a[2] = 3,和 = 4 写回 a[1]。
历年真题¶
2022 年 · 第 3 题¶
题目
运行以下代码片段的行为是( )。
- A. 将
x的值赋为 \(201\) - B. 将
y的值赋为 \(101\) - C. 将
q指向x的地址 - D. 将
p指向y的地址
答案与解析
答案:D
p = q 是指针之间的赋值:把 q 里存的地址(y 的地址)抄给 p——p 改指 y,x、y 两块内存都原封不动。要改值得写 *p = *q。
干扰项思路:A/B 都把“改指向”当成了“改值”;C 方向写反(是 p 学 q,不是 q 学 p)。
易错小结¶
&取地址、*解引用,互逆操作;*&x就是 x;p = q只改指向,*p = *q才改值(2022 真题);a[i]=*(a + i);但*a + 1≠*(a + 1);- 指针 +1 按元素大小走(int 走 4),所有指针本身都是 8 字节(64 位);
*p++是*(p++);想先解引用再自增写(*p)++;- 链表遍历条件
p != nullptr;指针访问成员用->; - 解引用空指针/野指针 = 未定义行为,判空用
p == nullptr。