指针基础与 const 指针
理解"指针即地址",掌握 &/* 运算符与 const 指针三种写法,养成指针初始化与判空习惯,杜绝野指针
- 来源
- 纠错
标记说明:【来源】来自上传资料 · 【补充】课程新编 · 【纠错】按勘误表修正 · 【更新】过时内容已现代化 · 【待确认】无法可靠还原
完成标准(本章)
- 📖 已阅读:滚动 ≥ 80% 且有效阅读 ≥ 180 秒
- ✏️ 已练习:小练习正确率 ≥ 60%
- 📝 已通过测验:分数 ≥ 60 分
- 🛠️ 已掌握还需完成实践任务
1.3.1 指针基础与 const 指针
本章来源:核心内容来自《第一阶段讲义》19.1/19.2/19.7 节【来源】,经重新组织与勘误后编写;通俗解释、练习、测验与实践任务为新编【补充】。本章含 1 条【纠错】(野指针表述),详见"常见错误"第 1 条与文末来源映射。 平台适用性:universal(通用知识,无平台相关内容)。
① 学习目标
学完本章,你应当能够:
- 说出"指针就是存放地址的变量",并区分
&(取地址)与*(解引用)两个运算符; - 独立写出传址交换函数,解释为什么传值交换不生效;
- 一眼读出
const int *p、int *const p、const int *const p三种声明各自"谁不可改"; - 说出野指针的成因与危害,并养成"定义即初始化、使用前判空、free 后置 NULL"的习惯;
- 通过本章测验(60 分及格)并完成实践任务。
② 前置知识
- 必修:1.2.5 数组——数组名就是首元素地址,是理解指针的最好铺垫;
- 建议:1.2.2 数据类型与常量变量——sizeof 与变量地址概念;
- 已能解释"变量住在内存的哪个地址"可直接开始;完全没概念请先补 0.1.4 内存布局概念。
③ 核心概念【来源】
| 概念 | 定义 |
|---|---|
| 指针(pointer) | 专门用来存放内存地址的变量。int *p; 中的 p 就是指针变量 |
取地址 & | 一元运算符,取变量的首地址:p = &a; |
解引用 * | 通过地址访问目标变量:*p = 10; 等价于 a = 10; |
| 空指针 NULL | 值为 0 的指针,表示"没有指向任何对象",不能解引用 |
| 野指针 | 指向未知/无效地址的指针(未初始化、已 free、越界等) |
常量指针 const int *p | 指向的数据不可改,指针本身可重新指向 |
指针常量 int *const p | 指针本身不可改,指向的数据可改 |
常量指针常量 const int *const p | 两者都不可改 |
记忆口诀:const 离谁近就锁谁。
const在*左边锁数据;在*右边锁指针本身。
④ 通俗解释【补充】
把内存想象成一栋宿舍楼,每个房间(内存单元)有房间号(地址):
- 变量是"住在一个房间里的东西";
- 指针是一张写着房间号的纸条——纸条本身有它自己的存放位置,但它记录的永远是"某个房间的号";
- 取地址
&a= "查一下 a 住哪个房间"; - 解引用
*p= "按纸条上的房间号敲门,进去拿东西"; - 野指针 = 纸条上写着一个不存在的房间号,敲门(解引用)可能没人、可能是别人的房间,甚至整栋楼断电(崩溃)——所以永远不要敲。
⑤ 示例代码【来源+补充,代码全部重写并实测】
代码示例与验证记录
- examples/ex1-pointer-basics.c地址、解引用与传址交换✓ 已实测(gcc 15.2.0 / MinGW-w64 x86_64, 2026-08-15(D-33 统一 -std=c11 -Wpedantic 复验))编译:
gcc ex1-pointer-basics.c -o ex1 -std=c11 -Wall -Wextra -Wpedantic适用环境:LinuxWindows(MinGW)展开预期输出(实测)
x 的值 = 10,x 的地址 = 000000051D1FF834 px 保存的地址 = 000000051D1FF834,*px 解引用 = 10 本平台 sizeof(int*) = 8 字节 swap_by_value 内部:a=20, b=10 传值交换后:x=10, y=20(main 中未交换) 传址交换后:x=20, y=10(main 中已交换)
差异说明:地址值每次运行不同;sizeof(int*) 在 32 位系统为 4
完整源码见 /code 代码示例页
- examples/ex2-const-pointers.cconst 指针三种写法✓ 已实测(gcc 15.2.0 / MinGW-w64 x86_64, 2026-08-15(D-33 统一 -std=c11 -Wpedantic 复验))编译:
gcc ex2-const-pointers.c -o ex2 -std=c11 -Wall -Wextra -Wpedantic适用环境:LinuxWindows(MinGW)展开预期输出(实测)
p1 -> 1 p1 重新指向后 -> 2 经 p2 修改后 a = 11 p3 -> 11
差异说明:三处注释掉的非法赋值取消注释后,报错信息已实测核对(见验证记录)
完整源码见 /code 代码示例页
- examples/ex3-null-checks.c空指针检查与 free 后置 NULL✓ 已实测(gcc 15.2.0 / MinGW-w64 x86_64, 2026-08-15(D-33 统一 -std=c11 -Wpedantic 复验))编译:
gcc ex3-null-checks.c -o ex3 -std=c11 -Wall -Wextra -Wpedantic适用环境:LinuxWindows(MinGW)展开预期输出(实测)
p 是空指针,跳过解引用 *p = 42
差异说明:无环境差异
完整源码见 /code 代码示例页
⑥ 编译与运行方法
每个示例的编译命令、适用环境与实测输出见上方示例卡(数据源为 examples.yaml,全部经 gcc 15.2.0 实测并记录验证环境)。要点:三个示例均为独立文件,统一 -std=c11 -Wall -Wextra -Wpedantic 零警告(M12-D 按 D-33 统一口径复验);ex2 中注释掉的非法赋值行取消注释后会得到对应的 const 报错(报错文本已实测核对并写入注释)。
⑦ 常见错误
| 症状 | 原因 | 解决 |
|---|---|---|
| 【纠错】认为"野指针读一下看看没关系" | 原讲义 19.2.2 表述"*p 可以查看" | 解引用野指针是未定义行为:可能崩溃、可能读到垃圾值、也可能"看起来正常"。一律禁止,先初始化再判空 |
| 程序崩溃:Segmentation fault(段错误) | 解引用了未初始化指针或 NULL | 定义即初始化;使用前 if (p != NULL) |
| swap 交换不生效 | 传值:函数拿到的是副本 | 传地址 swap(&x, &y),函数内用指针解引用 |
| 编译报 read-only 错误 | const 方向写反(想锁数据却锁了指针) | 记住口诀:const 在 * 左边锁数据、右边锁指针 |
| free 后程序随机崩溃 | 悬挂指针/重复 free | free(p); p = NULL; |
%p 打印警告 | 未强转 (void *) | printf("%p", (void *)p); |
⑧ 小练习
小练习
学习自测:提交后才显示答案与解析(前端判分,不作为正式考试)ex-a-1.代码 `int a = 5; int *p = &a;` 中,表达式 `*p` 的值是?(单选)
◌ 未作答ex-a-2.判断:`const int *p` 表示指针 p 本身不可修改,但可以通过 p 修改它指向的数据。(判断)
◌ 未作答ex-a-3.填空:要把变量 p 声明为“指向 int 的指针常量”(指针本身不可改、指向的数据可改),声明语句是 ______。(填空)
◌ 未作答
⑨ 章节测验
章节测验
⑩ 实战任务
实践任务
指针演练场:写一个"地址观察器"小程序
独立编写一个单文件 C 程序,把本章三个核心能力——传址交换、地址观察、const 三种写法的编译期验证—— 整合到一个可演示、可解释的小程序里。
输入与输出
无输入。程序输出:①两个变量的值与其地址;②传值交换与传址交换前后的对比; ③const 三种写法各自"合法用法"的运行结果。输出格式自定,但必须能让读者看懂每一步。
功能要求
- 实现 swap_by_address(传址交换),并对入参做 NULL 检查
- 打印至少两个变量的地址与值(%p),并输出 sizeof(int*) 说明本平台指针大小
- 分别声明常量指针/指针常量/常量指针常量,各写一行合法用法
- 把三种写法的非法赋值语句保留为注释,并在注释里写明编译器将报出的错误信息
- 所有指针定义即初始化,使用前判空
限制条件
- 单文件、总行数 ≤ 150 行
- 必须用 -std=c11 -Wall -Wextra -Wpedantic 编译且零警告(故意注释掉的非法行不算)
- 不得使用 gets()、scanf("%s") 等不安全输入方式
- 标准 C11,Windows(MinGW) 与 Linux 均可编译运行
验收步骤(自检清单 0/4)
验收标准
- 零警告编译通过(验收步骤 1)
- 运行输出正确且自解释(验收步骤 2)
- 三条非法赋值均能正确解释(验收步骤 3)
- 能口头讲清传值与传址的区别(验收步骤 4)
- 代码提交到个人 Git 仓库并打 tag v0.1
常见失败原因
- 调用 swap 时忘写 &(写成 swap(x, y)),交换不生效
- const 位置写反:想限制数据却写成了 int *const p
- 指针未初始化就解引用,程序崩溃或输出垃圾值
- 误以为传值交换能生效,验收步骤 4 答不出
可选扩展
- 增加二级指针:写一个函数交换两个指针各自的指向
- 用常量指针遍历一个 const 数组,体会 const 保护数据的意义
完成必要清单后才能计入"已完成实践"(学习状态自动推导,不提供一键完成)
⑪ 面试问题
面试问题
常量指针与指针常量的区别?高频C/C++ · easy
要点:const 在 * 左侧锁数据(常量指针),在 * 右侧锁指针本身(指针常量),两侧都有则全锁。
`const int *p`:p 可以重新指向别的变量,但不能通过 p 修改它所指向的数据——适合"只读遍历"(如函数参数加 const 保护); `int *const p`:p 一旦指向某个变量就不能再指向别处,但可以通过 p 修改数据——适合"固定句柄"(如硬件寄存器地址); `const int *const p`:两者都不可改。记忆口诀:const 离谁近就锁谁。
追问:- 追问:给函数传结构体指针时为什么要加 const?
- 追问:const int *p 与 int const *p 等价吗?(等价,const 位置在 * 左侧即修饰数据)
评分要点:- 两种写法各给出正确声明代码
- 说出 const 在 * 左/右侧的锁定对象
- 各举一个应用场景
野指针是怎么产生的?如何避免?高频C/C++ · easy
要点:未初始化、free 后未置空、越界访问都会产生野指针;对策是初始化、判空、free 后置 NULL。
野指针 = 指向未知/无效地址的指针。三大来源:①定义后未初始化就使用;②free(p) 后继续使用(悬挂指针); ③数组越界导致指针指向非法区域。解引用野指针是未定义行为——可能崩溃、可能读到垃圾值、也可能"看起来正常"(原讲义 "*p 可以查看"的表述已被本课程勘误,见 err-7.3-11)。避免方法:定义即初始化(NULL 或有效地址)、使用前判空、 free 后立即置 NULL、不保存指向栈上临时变量的指针。
追问:- 追问:free(p) 后把 p 置 NULL 为什么能防重复 free?
评分要点:- 说出至少两种成因
- 强调未定义行为(读也不行)
- 给出至少两条工程对策
sizeof(指针) 是多少?为什么?C/C++ · easy
要点:与平台地址宽度相关:64 位系统通常 8 字节、32 位系统 4 字节,与指针指向的类型无关。
指针变量里存的是地址,所以指针的大小由"地址需要多少位"决定,而不是由指向的数据类型决定。 因此 `sizeof(int*) == sizeof(char*) == sizeof(void*)`。回答时务必加上平台前提(32/64 位), 并说明应该用 sizeof 实测而不是硬编码 8 或 4。
评分要点:- 说出平台相关性(32 位 4 字节 / 64 位 8 字节)
- 说明与指向类型无关
⑫ 延伸阅读
- 《C 程序设计语言(K&R)》第 5 章:指针与数组(经典原文);
- man 手册:
man 3 printf(%p 格式); - cppreference.com:Pointer declaration(英文);
- 下一章预告:1.3.2 指针与数组/字符串/多级指针——用指针遍历数组、理解
int *p[3]与int (*p)[3]。
力扣推荐题
外部题目为「引用与推荐」模式(reference_only):仅保留题号、题名、难度与推荐理由,完整题面与提交请前往力扣;完成状态为自报记录,未与力扣账户验证,不影响章节掌握度判定。
- #344 反转字符串Reverse String核心必做(适配度 90)力扣难度:easy课程难度:L2预计 20 分钟章节必做前置:ch-1-3-1推荐理由:
- 直接训练"传址交换":用双指针对字符数组元素做 swap,正是本章 swap_by_address 的原位应用
- 与本章 const 指针知识衔接(可加 const 保护输入)
- 适合比较递归与迭代两种写法
匹配知识点:pointerswaparray - #283 移动零Move Zeroes推荐练习(适配度 80)力扣难度:easy课程难度:L2预计 25 分钟前置:ch-1-3-1推荐理由:
- 双指针"读写分离"是对本章指针指向可变(常量指针可重新指向)的实战训练
- 训练在数组上移动指针的边界意识
匹配知识点:pointerarraytwo-pointers
内容来源映射
| 内容部分 | 资料 | 位置 | 标记 | 说明 |
|---|---|---|---|---|
| 核心概念(指针定义、& 与 *、const 指针三种写法、NULL/空指针) | 第一阶段讲义 | 19.1/19.2/19.7 节(PAGE 272-278) | 【来源】 | 正文在原资料基础上重新组织表述,示例代码全部重写并实测 |
| 野指针概念 | 第一阶段讲义 | 19.2.2 节(PAGE 274) | 【纠错】 | 原文"*p 可以查看(有时可以被查看)"→对野指针解引用是未定义行为(见 corrections.yaml 的 err-7.3-11) |
| 通俗解释(门牌号类比) | 无 | 【补充】 | 原资料无类比讲解,本节新编 | |
| 小练习 / 章节测验 / 实战任务 / 面试问题 | 无 | 【补充】 | 原资料该章无成体系练习(全书明确"练习"仅 3 处),本章练习/测验/任务为新编 | |
| 延伸阅读 | 无 | 【补充】 | 外链清单新编 |
本章勘误与更新记录(【纠错】/【更新】)
err-7.3-11 · 表述修正 · 出处 19.2.2 节(PAGE 274)
原文:野指针:*p 可以查看(有时可以被查看,如果 p 的值为 0,依然不能看)
正确:对野指针解引用是未定义行为(UB):程序可能崩溃、可能输出垃圾值、也可能'看起来正常',任何情况下都不应解引用野指针。正确做法:指针定义时初始化(NULL 或有效地址)、使用前判空、free 后置 NULL。
原因:原表述会让初学者误以为'读野指针不危险、只是不能写';'有时可以被查看'正是不确定性的体现,教学上必须一刀切禁止。