打卡第十三集-简易多任务处理
extern 是 C 语言中用于声明外部变量或函数的关键字,告诉编译器:“这个变量/函数在其他文件中定义,请链接时去找。”
主要用途
1. 跨文件共享全局变量
假设有两个文件:main.c 和 counter.c,想在 main.c 中使用 counter.c 中定义的全局变量 count。
这样 main.c 就可以访问 count 了。
2. 在头文件中声明全局变量
通常做法:在头文件 .h 中用 extern 声明变量,在某个 .c 文件中实际定义。
-
counter.h:
c
extern int count; // 声明
void increment(void);
-
counter.c:
c
int count = 0; // 定义
void increment(void) {
count++;
}
-
main.c 包含头文件即可:
c
#include "counter.h"
count = 10; // 可以直接访问
3. 函数声明
函数默认是 extern 的,即可以跨文件调用。显式写 extern 不是必须的,但可增强可读性。
c
// 这两种写法等价
void func(void);
extern void func(void);
重要原则
- 声明 vs 定义:
extern 只做声明,不分配内存;定义才分配内存。一个变量只能定义一次,但可以声明多次。
- 作用域:
extern 声明的变量具有文件作用域,从声明处开始到文件末尾有效。
- 与
static 对立:static 修饰的全局变量只能在本文件内使用,extern 用于跨文件访问。
常见误区
- 在头文件中直接定义变量(不加
extern)会导致多个源文件包含该头文件时出现重复定义错误。
extern 不能用于局部变量(函数内部),只能用于全局变量和函数。
一句话总结:extern 是 C 语言中实现跨文件共享全局变量的关键手段,遵循“头文件声明,源文件定义”的原则。
static 在 C 语言中根据修饰对象不同,有三种作用:
1. 修饰局部变量(静态局部变量)
- 生命周期:从程序启动到结束,不随函数调用结束而销毁。
- 作用域:仍局限在函数内部,外部无法访问。
- 初始化:只执行一次,默认初始化为 0。
c
void func() {
static int count = 0; // 只初始化一次
count++;
printf("%d", count); // 每次调用递增
}
2. 修饰全局变量(静态全局变量)
- 作用域:限制在当前文件内,其他文件无法通过
extern 访问。
- 用于实现文件内部私有变量,防止命名冲突。
c
// file1.c
static int secret = 100; // 仅 file1.c 可见
3. 修饰函数(静态函数)
- 作用域:限制在当前文件内,其他文件无法调用。
- 用于实现文件内部私有函数,仅被本文件的其他函数使用。
c
// file1.c
static void helper() { // 仅 file1.c 可调用
// ...
}
模块化编程就是将程序拆分成独立、可复用、职责清晰的模块,每个模块负责一个特定的功能。
一、模块化的核心思想
- 接口与实现分离:头文件(
.h)公开接口(函数声明、宏、类型),源文件(.c)隐藏实现细节(函数定义、私有变量)。
- 信息隐藏:模块内部的变量和辅助函数对外不可见,通过
static 实现。
- 依赖清晰:模块之间通过头文件建立依赖,避免全局变量直接共享。
二、典型模块结构
以 timer 模块为例:
timer.h(对外接口)
c
#ifndef TIMER_H
#define TIMER_H
// 公共函数声明
void timer_init(void);
void timer_start(void);
unsigned int timer_get_ms(void);
#endif
timer.c(内部实现)
c
#include "timer.h"
// 静态全局变量(仅本文件可见)
static unsigned int ms_counter = 0;
static void (*callback)(void) = NULL; // 私有数据
// 静态函数(内部辅助)
static void timer_isr(void) {
ms_counter++;
if (callback) callback();
}
// 公共函数定义
void timer_init(void) {
// 配置硬件定时器
// 注册中断,调用 timer_isr
}
void timer_start(void) {
// 启动定时器
}
unsigned int timer_get_ms(void) {
return ms_counter;
}
main.c(使用模块)
c
#include "timer.h"
int main(void) {
timer_init();
timer_start();
while(1) {
unsigned int t = timer_get_ms();
// ...
}
}
三、模块化中的关键修饰符
| 修饰符 |
在模块化中的作用 |
static |
修饰全局变量或函数,将其作用域限制在本文件,实现私有成员。 |
extern |
在头文件中声明变量/函数,供其他模块使用。通常与 #include 配合,无需显式写 extern。 |
四、模块化的好处
- 可维护性:修改一个模块不影响其他模块。
- 可复用性:模块可以在不同项目中直接复用。
- 可测试性:可以单独编译和测试每个模块。
- 协作开发:不同开发者负责不同模块,通过接口约定协作。
在模块化编程中,为重要函数添加清晰的说明注释,是保证代码可读性、可维护性的关键。通常采用 Doxygen 风格的注释,放在函数声明(头文件)或定义(源文件)之前。
标准函数说明应包含的内容
- 功能简述:函数是做什么的
- 参数:每个参数的含义、取值范围、输入/输出方向
- 返回值:返回值的含义
- 注意事项:调用前提、副作用、线程/中断安全等
示例(头文件中)
c
/**
* @brief 初始化定时器模块
* @param freq 定时器工作频率,单位 Hz,范围 1000~100000
* @param mode 工作模式:0=单次模式,1=循环模式
* @return 0: 成功, -1: 参数错误, -2: 硬件初始化失败
* @note 必须在调用 timer_start() 之前调用本函数
* @warning 若 freq 超出范围,函数会返回 -1 并保持原有配置不变
*/
int timer_init(unsigned int freq, int mode);
常用标签
| 标签 |
用途 |
@brief |
简要描述 |
@param |
参数说明(可标注 [in]/[out]) |
@return 或 @retval |
返回值说明 |
@note |
补充说明 |
@warning |
警告信息 |
@see |
关联其他函数 |
为什么要加这些说明?
- 方便他人理解:不需要阅读函数实现就知道怎么用
- 自动生成文档:配合 Doxygen 等工具可直接生成 API 文档
- 减少使用错误:明确参数范围、注意事项,降低误用风险
- 方便代码审查:注释清晰,审查者能快速判断接口设计是否合理