函数与指针进阶

官方信息技术老师·10 页·深入(追求细节与边界)·0 次浏览·3 天前
函数指针内存模型类型系统边界陷阱

函数与指针进阶

看懂函数类型、指针语义与运行时内存模型

按 空格/→ 演示下一步

1 / 10 页

全部页面点击任意一页,跳回舞台从这页播放

函数指针内存模型类型系统边界陷阱

函数与指针进阶

看懂函数类型、指针语义与运行时内存模型

1第 1 页 · 函数与指针进阶

编译预处理

前几页讲的函数调用、指针解引用,都是运行时行为。但C代码在编译前还要过预处理这一关——它决定了编译器最终看到什么。

本质是文本替换
预处理器不解析C语法,只按指令对源文件做机械的字符串替换
三类核心指令
#include 文件包含、#define 宏定义、#if 条件编译
指令书写规则
必须以#开头、独立成行,行尾不加分号(不是C语句)
头文件保护
用#ifndef/#define/#endif 防止同一头文件被多次展开
Word里批量替换关键词对应 →预处理器的文本操作

把全文"深度学习"改成"机器学习"——纯字符串操作,不理解含义,预处理器也一样

2第 2 页 · 编译预处理

命令行参数

上一节我们讲过指针数组在函数间的传递;今天换个方向——程序从命令行「接过来」的参数,本质也是一束指针,由操作系统和 shell 提前摆好。

argc 参数计数
至少为 1,统计命令行参数的总个数(含程序名)
argv 参数向量
类型为 char**,指向一串字符串指针;二维内存布局
argv[0] 是程序名
存的是自身路径(含调用名),并非用户传入的首个参数
数组末尾 NULL
argv[argc] 约定为 NULL,可直接作为循环退出条件
参数切词在 shell
空格、引号、转义都在 shell 阶段完成,C 只接收切好的串
酒店前台收到一段地址对应 →main 接收命令行参数

前台把整句地址按空格撕成几条,argc 是条数,argv 是那叠条

3第 3 页 · 命令行参数

数组与函数

上页我们讲了函数怎么接收指针,指针能指向单个变量,也能指向一块连续内存——也就是数组。这一页我们看数组和函数打交道时,内部到底发生了什么。

数组名退化
传参时数组名退化为首元素指针,丢失长度信息
长度需另传
函数内部只看到指针,必须额外传长度参数才知道边界
多维数组限制
二维及以上数组传参时,除第一维外所有维度大小必须写死
形参写法等价
int arr[] 与 int *arr 在参数位置完全等价,编译器一视同仁
快递一个衣柜对应 →数组传参

快递只送到门口给个地址(首指针),衣柜几层几件(长度、第二维)得另外告知

4第 4 页 · 数组与函数

指针与函数

上页我们把数组传给函数时,函数收到的其实是首地址——指针在参数传递里无处不在。这页再深一层:函数本身能不能也被指针指?

函数指针
指向函数的指针变量,存的是函数入口地址
指针函数
返回值为指针的函数,注意与函数指针区分
回调函数
把函数当参数传入,由对方在合适时机调用
典型应用
qsort 比较函数、事件处理、策略模式等
餐厅菜单上的菜品编号对应 →函数指针

编号不是菜本身,但点编号就能拿到对应那道菜

返回类型 (指针名)(参数列表)\text{返回类型 }(*\text{指针名})(\text{参数列表})
5第 5 页 · 指针与函数

数据的动态管理

之前我们用的数组和局部变量,大小在编译时就定死了。但现实中你常常不知道用户会输入多少条数据——这就要靠运行时向系统「借」内存,用完再「还」。

静态分配的局限
数组长度编译期固定,运行期无法伸缩
堆内存申请
malloc 返回未初始化内存,calloc 自动清零
realloc 的陷阱
扩容可能搬地址,原指针丢失会泄漏
free 的铁律
只还堆内存,只还一次,还完置 NULL
常见内存错误
泄漏、悬空、重复释放、野指针
租仓库存货对应 →malloc 与 free

申请即租、free 即退租;忘退是泄漏,退错是 double free

6第 6 页 · 数据的动态管理

二级C考点解析之指针与函

二级C考点解析之指针与函数:定义、要点与典型应用

二级C考点解析之指针与函
二级C考点解析之指针与函数:定义、要点与典型应用
7第 7 页 · 二级C考点解析之指针与函

二级C考点解析之编译预处

前面我们了解了预处理的基本流程:源代码先被「改写」一份,再交给编译器。这页我们换到考试视角:三类指令怎么考、宏和函数怎么区分、哪些坑年年都出。

三类预处理指令
宏定义#define、文件包含#include、条件编译#if
宏与函数的区别
宏是文本替换无类型检查;函数有调用开销但类型安全
条件编译两种形式
#ifdef 已定义则编译,#ifndef 未定义则编译
宏参数副作用
SQUARE(a++) 展开成 a++*a++,年年的送分变送命题
改作文前的标注对应 →编译预处理

把「约等于」换成「=」、跳过某段、把模板拉进来——正式批改前先做完这些

8第 8 页 · 二级C考点解析之编译预处

本节要点

串联本节知识点,给出记忆锚点与易错提醒

本节要点
串联本节知识点,给出记忆锚点与易错提醒
9第 9 页 · 本节要点

课后思考

先自己琢磨,再看参考答案。三问分别考察核心区分、实际应用与边界陷阱。

1「指针函数」和「函数指针」只差一个字,本质却完全不同。你能用一句话各说清楚它们是什么吗?

参考答案指针函数是「返回值为指针的函数」;函数指针是「指向函数的指针变量」。关键看「指针」二字修饰的是返回值,还是函数本身。

2假如要写一个通用排序函数,既能排整型数组又能排字符串数组,函数指针在这里能起什么作用?

参考答案把「比较规则」抽成独立函数,用函数指针作为参数传进去。排序时按传入的指针决定大小关系,一套代码就能处理多种数据类型。

3一个函数返回了它内部局部变量的地址,调用者拿到这个指针后还能安全使用吗?为什么?

参考答案不能。局部变量在函数返回时内存即被回收,指针指向的地址成为「野指针」,访问是未定义行为。这也是为什么动态内存要用 malloc 而不能依赖局部变量。

10第 10 页 · 课后思考