跳转至

指针

方法速览

任务 方法 口诀
取地址 &x 得到 x 的地址 取址用 &
解引用 *p 访问 p 指向的变量 取值用 *
数组与指针 数组名 = 首元素地址;a[i] = *(a + i) 下标即偏移
指针加减 p + 1 移动一个元素(int 指针走 4 字节) 加一走一步
改实参 &x,函数内 *p 改本体 传址改本体

变量的指针

指针是存地址的变量。int* p 声明了一个指向 int 的指针 pp 里存放的是一个 int 变量的地址——p 自己不是 int,它只是记着地址。

两个互逆操作:

1
2
3
4
int x = 5;
int* p = &x;   // & 取地址:p 拿到 x 的地址
cout << *p;    // * 解引用:顺着地址找到 x,输出 5
*p = 9;        // 通过地址改 x —— x 变成 9
表达式 含义 值(示例)
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 起),解引用空指针是运行时崩溃——“野指针”指未初始化就使用。

数组与指针

数组名在表达式里退化为首元素地址

int a[5] = {10, 20, 30, 40, 50};
int* p = a;        // 等价 p = &a[0]
  • 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
2
3
void setTen(int* p) { *p = 10; }
int x = 1;
setTen(&x);      // x 变 10 —— 调用取地址,函数内解引用

链表结点就是“数据 + 自指指针”:

1
2
3
4
5
struct Node {
    int data;
    Node* next;   // 指向下一个结点,末结点 next = nullptr
};
Node* head;       // 头指针:整个链表的入口

遍历模板:

for (Node* p = head; p != nullptr; p = p->next)
    cout << p->data;    // 指针访问成员用 ->

练习题目

练习 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 改值

题目

1
2
3
4
int x = 1, y = 2;
int* p = &x;
int* q = &y;
p = q;
执行后( )

  • 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 · 指针走数组

题目

1
2
3
int a[] = {2, 4, 6, 8};
int* p = a;
cout << *(p + 2) << " " << *(++p);
输出( )

  • 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 指针

题目

int a[10];
int* p = a;
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 · 指针作参数

题目

void addOne(int* p) { (*p)++; }
int x = 7; addOne(&x);
调用后 x( )

  • A. \(7\)
  • B. 编译错误
  • C. 未定义
  • D. \(8\)
答案与解析

答案:D

传地址 + 解引用,改到本体。注意 (*p)++ 的括号:*p++*(p++)(先用再加指针),含义完全不同。

练习 16 · 传址与传值对比

题目

1
2
3
void f(int* p) { *p = 100; }
int a = 1, b = 2;
f(&a);
之后 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* pint* 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 · 交换两个指针的指向

题目

1
2
3
int a = 1, b = 2;
int* p = &a; int* q = &b;
int* t = p; p = q; q = t;
之后 *p*q 分别是( )

  • A. \(1, 2\)
  • B. \(2, 2\)
  • C. \(2, 1\)
  • D. 未定义
答案与解析

答案:C

三个指针变量轮换指向:p 改指 b、q 改指 a;a、b 的值没动。

练习 24 · 指针比较

题目

判断 p 是否“没指向任何东西”,应写( )

  • A. p == 0p == nullptr
  • B. *p == 0
  • C. p == &0
  • D. sizeof(p) == 0
答案与解析

答案:A

空指针与 0、nullptr 比较是标准判空写法;B 是“指向的值为 0”,且空指针时解引用直接崩。

练习 25 · 数组作参数后的指针行为

题目

void f(int a[]) { cout << sizeof(a); }
int arr[10]; f(arr);
64 位下输出( )

  • A. \(40\)
  • B. \(10\)
  • C. \(8\)
  • D. \(80\)
答案与解析

答案:C

形参 int a[] 就是 int*——退化后 sizeof 得到指针大小。

练习 26 · 综合追踪

题目

1
2
3
4
int a[] = {1, 2, 3};
int* p = a + 1;
*p = *(p - 1) + *(p + 1);
cout << a[1];
输出( )

  • 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 题

题目

运行以下代码片段的行为是( )。

1
2
3
4
5
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 里存的地址(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