指针 ⭐
🎯 一句话秒杀:指针就是地址——指针变量是存储地址的变量。
*是"取出指针指向的内容",&是"取变量的地址"。
① 📊【历年真题考情】
本章(指针)在广东专升本计算机统考中的位置:
| 项目 | 结论 |
|---|---|
| 出题频次 | ⭐⭐⭐⭐⭐(五星必考)——2026 考纲全解标注"指针 ⭐⭐⭐,能独立手推和编写完整程序";连续 6 年真题必考 |
| 常考题型 | 单选(2021 单选6、2026 单选5/7)、判断(2021 判断23)、填空(2024 填空1)、计算/读程(2021 大题41)、编程大题(2021 大题43) |
| 预估分值 | 10~25 分 / 200 分(约 5%~13%,与函数并列最高分值模块) |
| 考纲归属 | 2026 考纲「复合数据」考点7(指针变量、指针与数组/函数/字符串) |
考场真实出现过的高频题:
- 2021 单选6:指针变量 p 不能初始化为 → 1(D)
- 2021 判断23:指针变量不可以为函数参数 → 错(可以)
- 2021 大题43:用指针交换 a、b 并输出较大值
- 2024 填空1:指针形参返回两数和 →
*z = x + y - 2026 单选5:能交换调用者两个整型变量的声明 → B.
void swap(int *a, int *b) - 2026 单选7:
int *p=&x; (*p)++;后 → x 变为 11(B)
🎯 学习目标:学完本章,你能 ① 判断指针能否如此初始化;② 手推
(*p)++和*p++的区别;③ 判断哪个函数声明能交换变量;④ 写出 malloc/free 完整代码——这四类题就是本章真题的全部考法。
② 🗣️【零基础大白话引入】
为什么需要指针? 想象一排快递柜(内存),每个柜子有编号(地址)和里面的包裹(值)。普通变量是"我知道包裹在 3 号柜,直接去拿";指针是"我记住 3 号柜这个编号,以后通过编号去拿"。
比喻对应:
- 变量
a= 柜子本身(值 10 在 0x1000 号柜) &a= 柜子的编号(0x1000)- 指针
p= 一张写着"0x1000"的纸条(存的是编号) *p= 拿着纸条去取包裹(取 0x1000 柜的值 10)
为什么能交换变量? 你直接报"把 3 号柜和 5 号柜的包裹互换"——但普通函数传参时,只把包裹复印一份给你,你换的是复印件!传指针就是直接把柜子编号给你,你才能动真包裹。
* 和 & 是什么?
&a= 问"a 在几号柜"(取地址)*p= 问"纸条 p 指向的柜里是什么"(取内容)- 这俩是互逆操作:
*&a == a
💡 本章主线:*指针是什么(地址)→ 怎么用(& 和 )→ 和数组什么关系 → 怎么让函数改外部变量(交换)→ 怎么动态分配内存。全是最实用、考试最高频的技能。
③ 📖【正式核心知识点讲解】
本章知识点 = 考纲要求 + 历年真题实际考过的内容。真题未考、考纲不作要求的超纲内容已剔除或标注"了解"。
3.1 什么是指针?
int a = 10; // 普通变量:存值
int *p = &a; // 指针变量:存地址内存模型:
变量 a: [10 ] ← 地址 0x1000
指针 p: [0x1000] ← 指向 a(存储的是a的地址)
printf("%d", a); // 10 — 直接访问
printf("%d", *p); // 10 — 间接访问(通过地址找到a的值)3.2 指针的定义与使用
int *p; // p是指向int的指针
char *cp; // cp是指向char的指针
double *dp; // dp是指向double的指针
int a = 10;
p = &a; // p指向a(p存a的地址)
*p = 20; // 通过p修改a的值(a变成20)🚨 红牌警告
定义时
*是类型的一部分:int *p;表示p是int*类型 使用时*是间接访问运算符:*p表示取p指向的值 没有初始化的指针不能用! 野指针会导致程序崩溃
3.3 指针的合法初始化(2021 单选6 必考)
指针变量只能初始化为:① 一个变量的地址(
&a);②NULL;③ 字面量0(等价 NULL)
| 初始化写法 | 合法? | 说明 |
|---|---|---|
int *p = &a; | ✅ | 地址,最常用 |
int *p = NULL; | ✅ | 空指针 |
int *p = 0; | ✅ | 0 等价于 NULL |
int *p = 1; | ❌ 非法 | 1 不是合法地址/NULL 语义 |
✅ 2021 单选6:指针变量 p 不能初始化为 → D.
1解析:地址、NULL、0都合法;1既不是有效地址也不是空指针语义。
3.4 指针的运算(*p++ 优先级 · 2026 单选7 必考)
| 运算 | 含义 | 示例 |
|---|---|---|
p + n | 指向后面第n个元素 | p + 1 指向下一个int(地址+4) |
p - n | 指向前面第n个元素 | p - 1 指向上一个元素 |
p1 - p2 | 两个指针间的元素个数 | |
*p | 取内容 | |
&a | 取地址 |
🔑 秒杀秘籍——指针加1加多少?
int *p; // p+1 → 地址+4(一个int 4字节)
char *cp; // cp+1 → 地址+1(一个char 1字节)
double *dp; // dp+1 → 地址+8(一个double 8字节)指针+1 = 地址 + sizeof(指向的类型) × 1 所以指针加1是"跳到下一个元素",不是"地址加1"
3.4.1 (*p)++ vs *p++ vs *++p 优先级逐层推演(2026 单选7 必考)
三个长得像的表达式,结果完全不同——考的就是运算符优先级和结合性!
优先级规则:
++(后置)优先级高于*(解引用)*和++(前置)同级,右结合
① (*p)++:先解引用,再对"指向的值"自增
int x = 10;
int *p = &x;
(*p)++;逐层推演:
- 括号优先:先算
*p→ 取 p 指向的值,即 x 的值 10 ++作用于这个值:把 x 的值 10 → 11- 结果:x 变为 11,p 不变(还指向 x)
✅ 2026 单选7:
(*p)++;后 → B. x 变为 11 记忆:(*p)++改的是"指向的值"
② *p++:先自增指针,再解引用(等价的优先级是 *(p++))
int a[3] = {10, 20, 30};
int *p = a; // p 指向 a[0]
printf("%d", *p++);逐层推演:
- 后置
++优先级高于*:*p++等价于*(p++) p++是"先用后加":先取 p 当前值(指向 a[0]),p 再自增(指向 a[1])*作用在 p 的旧值上 → 取 a[0] = 10- 副作用:p 现在指向 a[1]
- 输出:10,且 p 已指向 a[1]
记忆:
*p++改的是"指针本身"(先取旧值,指针后移)
③ *++p:先自增指针,再解引用(等价的优先级是 *(++p))
int a[3] = {10, 20, 30};
int *p = a; // p 指向 a[0]
printf("%d", *++p);逐层推演:
- 前置
++与*同级右结合:*++p等价于*(++p) ++p先自增:p 先指向 a[1]*作用在 p 的新值上 → 取 a[1] = 20- 输出:20,p 现在指向 a[1]
三者对比表(必背):
| 表达式 | 等价写法 | 先做什么 | 结果 |
|---|---|---|---|
(*p)++ | 解引用后自增值 | 先取 *p 再值+1 | 改指向的值 |
*p++ | *(p++) | 先用 p 旧值再 p 后移 | 取旧值,指针后移 |
*++p | *(++p) | 先 p 前移再取新值 | 取新值,指针前移 |
3.5 指针与数组(重点!)
数组名就是首元素地址
int a[5] = {10, 20, 30, 40, 50};
int *p = a; // p指向a[0],等价于 int *p = &a[0]通过指针访问数组元素
// 以下四种方式完全等价!
a[0] // 下标法
*a // 指针法
p[0] // 指针当下标用
*(p+0) // 指针偏移
// 一般情况:a[i] ≡ *(a+i) ≡ *(p+i) ≡ p[i]遍历数组
int a[5] = {10, 20, 30, 40, 50};
int *p;
// 方式1:下标
for (int i = 0; i < 5; i++)
printf("%d ", a[i]);
// 方式2:指针
for (p = a; p < a + 5; p++)
printf("%d ", *p);🚨 红牌警告
数组名是常量指针——不能修改!
cint a[5]; a++; // ❌ 编译错误!a是常量 int *p = a; p++; // ✅ 指针变量可以修改
数组名作为函数参数
实际上传递的是指针:
// 这三个声明完全等价
void func(int arr[]);
void func(int arr[10]); // 写的10没用
void func(int *arr);
// 函数内部:
void func(int arr[]) {
int n = sizeof(arr) / sizeof(arr[0]); // ❌ 错误!
// sizeof(arr) 在这里是4(指针大小),不是数组大小!
}✅ 2022 单选10:数组名作为函数参数传递的是 → B. 首地址(不是长度、元素值、个数) ✅ 2021 判断23:指针变量不可以为函数参数 → 错(指针完全可以作函数参数,且是交换变量的关键)
3.6 指针与函数(2026 单选5 必考)
3.6.1 用指针实现交换
void swap(int *a, int *b) {
int temp = *a; // 取出a指向的值
*a = *b; // 把b指向的值赋给a指向的位置
*b = temp; // temp赋给b指向的位置
}
// 调用:swap(&x, &y); → x和y被交换 ✅3.6.2 哪个函数声明能交换?四选项真题判断(2026 单选5)
真题:下列函数声明中,能够交换调用者两个整型变量的是( )
| 选项 | 声明 | 能否交换 | 原因 |
|---|---|---|---|
| A | void swap(int a, int b) | ❌ | 值传递,形参是拷贝 |
| B | void swap(int *a, int *b) | ✅ | 传地址,函数内 *a *b 解引用改外部变量 |
| C | int swap(int a) | ❌ | 只有一个参数且返回 int,无法交换 |
| D | void swap(const int a, const int b) | ❌ | const 禁止修改 |
✅ 2026 单选5:答案 B。关键:C 参数值传递改不了外部;必须传地址(指针),函数内通过解引用完成交换。 逐行推演 swap 交换过程:
int temp = *a;— 取出 a 指向的值存 temp*a = *b;— 把 b 指向的值赋给 a 指向的位置*b = temp;— temp 赋给 b 指向的位置 三步完成交换(经典三变量交换的指针版)
3.6.3 指针形参返回多个值(2024 填空1)
void add(int x, int y, int *z) {
*z = x + y; // 通过指针"带出"结果
}
// 调用:int s; add(3, 5, &s); → s = 8✅ 2024 填空1:补全
____→*z = x + y(z 是int*,必须解引用*z才能改外部变量)
指针作为函数返回值
int *find_max(int *arr, int n) {
int *max = arr;
for (int i = 1; i < n; i++)
if (arr[i] > *max) max = &arr[i];
return max;
}
// 调用:int *p = find_max(a, 5);🚨 红牌警告
不能返回局部变量的地址!
cint *bad() { int a = 10; return &a; // ❌ a是局部变量,函数结束就销毁 }
3.7 动态内存分配(2023/2024 关联)
| 函数 | 原型 | 功能 |
|---|---|---|
malloc | void *malloc(size_t size) | 分配size字节内存 |
calloc | void *calloc(n, size) | 分配n×size字节并清零 |
realloc | void *realloc(void *ptr, size_t size) | 重新分配大小 |
free | void free(void *ptr) | 释放内存 |
#include <stdlib.h>
int *p = (int *)malloc(5 * sizeof(int)); // 分配5个int的空间
if (p == NULL) { // 检查是否分配成功
printf("内存不足");
return -1;
}
for (int i = 0; i < 5; i++)
p[i] = i * 10;
free(p); // ⚠️ 用完后必须释放!
p = NULL; // 避免野指针🚨 红牌警告
malloc 三要素:
malloc返回void*— 需要强制类型转换为需要的类型- 检查是否返回 NULL(内存不足时返回NULL——2023 真题考过)
- 用完必须 free,否则内存泄漏(2024 关联)
💡 malloc 失败返回 NULL 是考点:分配不到内存时
malloc返回NULL,所以要if (p == NULL)检查——不检查直接使用会崩溃(解引用空指针)。
④ 🧪【真题同源例题】
例题 1:入门基础题(先热热身)
#include <stdio.h>
int main() {
int a = 10;
int *p = &a;
*p = 20;
printf("%d\n", a);
printf("%d\n", *p);
return 0;
}逐变量推演:
int a = 10;— a 的值是 10int *p = &a;— p 存 a 的地址*p = 20;— 通过 p 找到 a,把 a 改成 20printf("%d", a)→ 20(a 已被改)printf("%d", *p)→ 20(p 指向的值)- 输出:
20 20
例题 2:2026 真题改编((*p)++ 优先级)
#include <stdio.h>
int main() {
int x = 10;
int *p = &x;
(*p)++;
printf("%d", x);
return 0;
}逐层推演:
(*p)++:括号优先 → 先*p取 x 的值 10++作用于这个值 → x 的值 10 → 11x变为 11,p不变- 输出:
11
✅ 对应 2026 单选7,答案 B。
(*p)++改的是指向的值。
例题 3:2026 真题改编(*p++ 优先级)
#include <stdio.h>
int main() {
int a[3] = {10, 20, 30};
int *p = a;
printf("%d\n", *p++);
printf("%d\n", *p);
return 0;
}逐层推演:
- 第一条 printf:
*p++等价于*(p++)p++先用后加:先取 p 旧值(指向 a[0]),p 再自增(指向 a[1])*取 p 旧值指向的内容 → 10- 副作用:p 现在指向 a[1]
- 第二条 printf:
*p→ p 指向 a[1] → 20 - 输出:
10 20
对比记忆:
*p++输出 10(指针后移);(*p)++是 x 变 11(值自增);*++p输出 20(先移指针再取值)。
例题 4:2026 真题改编(swap 四选项判断)
下列函数声明中,能够交换调用者两个整型变量的是( )
A. void swap(int a, int b) B. void swap(int *a, int *b) C. int swap(int a) D. void swap(const int a, const int b)
逐项分析:
- A:值传递,形参是实参拷贝 → 换不了 ❌
- B:传地址,
*a/*b解引用改外部变量 → 能换 ✅ - C:单参数返回 int → 换不了 ❌
- D:const 禁止修改 → 换不了 ❌
✅ 答案 B(2026 单选5 原题)。交换三步推演见 3.6.2。
例题 5:2021 真题改编(指针合法初始化)
指针变量 p 不能初始化为( )
A. 地址 B. NULL C. 0 D. 1
逐项分析:
- A 地址:合法 ✅
- B NULL:空指针,合法 ✅
- C 0:等价 NULL,合法 ✅
- D 1:不是合法地址也不是空指针语义 → 非法 ❌
✅ 答案 D(2021 单选6 原题)。
⑤ ⚠️【历年真题高频扣分坑】
| # | 陷阱 | 错误做法 | 正确做法 | 出处 |
|---|---|---|---|---|
| 1 | 指针初始化为 1 | int *p=1; 以为行 | 只能初始化地址/NULL/0 | 2021 单选6 |
| 2 | *(p)++ 当 p++ | 混淆改值/改指针 | (*p)++ 改指向的值;p++ 改指针 | 2026 单选7 |
| 3 | *p++ 优先级 | 以为先解引用再自增指针 | *p++ = *(p++),先取旧值指针后移 | 2026 关联 |
| 4 | 交换用值传递 | swap(int a,int b) 以为能换 | 必须传指针 swap(int *a,int *b) | 2026 单选5 |
| 5 | 指针形参忘解引用 | z=x+y 而非 *z=x+y | 改外部变量必须 *z | 2024 填空1 |
| 6 | sizeof 数组陷阱 | 函数内 sizeof(arr) 当数组大小 | 形参是指针,sizeof=4 | 2024/2026 |
| 7 | 数组名当可变 | 对数组名 a++ | 数组名是常量指针,不能修改 | 经典 |
| 8 | 返回局部变量地址 | return &局部变量 | 函数结束局部变量销毁 | 经典 |
| 9 | 忘检查 malloc NULL | 直接使用 malloc 返回值 | 先判 NULL 再使用 | 2023 |
| 10 | 忘 free | 只 malloc 不 free | 用完必须 free 防泄漏 | 2024 |
| 11 | 指针+1 当地址+1 | int* p+1 以为地址+1 | p+1 跳 4 字节(sizeof(int)) | 经典 |
| 12 | 指针不能作参数 | 以为指针不能传参 | 指针完全可以作函数参数(2021 判断23 对) | 2021 判断23 |
📌 最值钱的一条:第 2、3 条(
(*p)++vs*p++)是 2026 单选7 的核心——"括号决定改值还是改指针" 一句口诀拿 3 分;第 4 条(swap 传指针)是 2026 单选5,与函数章联动。
⑥ 📝【课后自测练习题】
1. 指针变量 p 不能初始化为( )(2021 真题风格)
A. &a B. NULL C. 0 D. 1
2. 执行下列程序段后,输出结果是( )(2026 真题风格)
int a[3] = {10, 20, 30};
int *p = a;
printf("%d", *p++);A. 10 B. 20 C. 30 D. 11
3. 能交换调用者两个整型变量的函数声明是( )(2026 真题风格)
A. void swap(int a, int b) B. void swap(int *a, int *b) C. int swap(int a) D. void swap(const int a, int b)
4. 执行 int x=10; int *p=&x; (*p)++; 后,x 的值是( )(2026 真题风格)
A. 10 B. 11 C. 指针地址加 1 D. 语法错误
👆 点击展开参考答案与解析
第1题:D. 1
- 地址、NULL、0 都合法;1 不是合法地址/NULL 语义(2021 单选6 原题)
第2题:A. 10
*p++=*(p++):先取 p 旧值指向的 a[0]=10,再 p 后移指向 a[1]- 输出 10(若第二条再输出 *p 则是 20)
第3题:B. void swap(int *a, int *b)
- 值传递改不了外部变量,必须传地址(2026 单选5 原题)
第4题:B. 11
(*p)++先取 *p(x=10)再自增 → x 变 11(2026 单选7 原题)
📝 原有闭卷真题挑战(保留原内容)
(点击下方空白查看答案)
1. int a[5]; 则 &a[0] 和 a 的关系是?
2. 写出输出:
int a[5]={10,20,30,40,50}, *p=a;
p++; printf("%d", *p);
3. 指针+1,地址增加多少?为什么?
4. 为什么不能返回局部变量的地址?
5. malloc 后忘记 free 会怎样?👆 点击展开答案
- 完全相等(a就是首元素地址 &a[0])
- 20(p先++指向a[1],再取内容)
- 取决于指针类型:
int* +1→ 地址+4,char* +1→ 地址+1。因为指针+1是"跳到下一个元素" - 局部变量在函数返回后被销毁,返回的地址指向无效内存
- 内存泄漏(memory leak)——程序占用的内存越来越多
📖 教材习题对照
| 教材习题 | 知识点 | 难度 |
|---|---|---|
| 习题8.1~8.2 | 指针基本概念 | ⭐⭐ |
| 习题8.4~8.5 | 指针与数组 | ⭐⭐⭐ |
| 习题8.8~8.9 | 指针与函数 | ⭐⭐⭐ |
| 习题8.16 | 指针数组 | ⭐⭐⭐⭐ |
| 习题8.30 | 动态内存分配 | ⭐⭐⭐⭐ |
对应教材:谭浩强《C程序设计》第5版 → 第8章 善于利用指针 🌟(最重要的一章!) 对应考试大纲:考点8 — 指针
📺 配套视频
复习到本考点 → 先看视频补讲,再刷上面「闭卷挑战」。