跳到主要内容
🔍
1.3.1已发布beginner · 约 4 课时 · P0

指针基础与 const 指针

理解"指针即地址",掌握 &/* 运算符与 const 指针三种写法,养成指针初始化与判空习惯,杜绝野指针

  • 来源
  • 纠错

标记说明:【来源】来自上传资料 · 【补充】课程新编 · 【纠错】按勘误表修正 · 【更新】过时内容已现代化 · 【待确认】无法可靠还原

完成标准(本章)

  • 📖 已阅读:滚动 ≥ 80% 且有效阅读 ≥ 180
  • ✏️ 已练习:小练习正确率 ≥ 60%
  • 📝 已通过测验:分数 ≥ 60
  • 🛠️ 已掌握还需完成实践任务
学习状态:未开始

1.3.1 指针基础与 const 指针

本章来源:核心内容来自《第一阶段讲义》19.1/19.2/19.7 节【来源】,经重新组织与勘误后编写;通俗解释、练习、测验与实践任务为新编【补充】。本章含 1 条【纠错】(野指针表述),详见"常见错误"第 1 条与文末来源映射。 平台适用性:universal(通用知识,无平台相关内容)。

① 学习目标

学完本章,你应当能够:

  1. 说出"指针就是存放地址的变量",并区分 &(取地址)与 *(解引用)两个运算符;
  2. 独立写出传址交换函数,解释为什么传值交换不生效;
  3. 一眼读出 const int *pint *const pconst int *const p 三种声明各自"谁不可改";
  4. 说出野指针的成因与危害,并养成"定义即初始化、使用前判空、free 后置 NULL"的习惯;
  5. 通过本章测验(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 后程序随机崩溃悬挂指针/重复 freefree(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 的指针常量”(指针本身不可改、指向的数据可改),声明语句是 ______。(填空)

    ◌ 未作答

⑨ 章节测验

章节测验

5 题题库 · 随机抽 5 题 · 及格线 60 分 · 前端判分(学习自测)
开始测验 →

⑩ 实战任务

实践任务

指针演练场:写一个"地址观察器"小程序

独立编写一个单文件 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。

    原因:原表述会让初学者误以为'读野指针不危险、只是不能写';'有时可以被查看'正是不确定性的体现,教学上必须一刀切禁止。