.h 头文件
.h 头文件 (Header File) 是 C 语言中用来做 “接口声明” 的文件。
如果说 .c 文件是 “厨房”(负责真正干活、把菜做出来),那么 .h 文件就是 “菜单”(只列出菜名,告诉别人这里有什么)。
为了让你彻底理解它的作用和写法,我们从**“为什么要用它”**开始。
1. 为什么要用头文件?
想象一下,你写了一个很牛的电机驱动程序 motor.c,里面有 1000 行代码。
现在 main.c 想要控制电机,它需要知道 motor.c 里有哪些函数可以用。
- 做法 A (笨办法): 把
motor.c里的函数名在main.c里全都手写一遍声明(extern ...)。如果有 100 个函数,你要累死,而且一旦motor.c改了名字,main.c就报错。 - 做法 B (聪明办法):
- 创建一个
motor.h。 - 把所有能给别人用的函数声明,都写在
motor.h里。 main.c只需要写一句#include "motor.h"。- 编译器会自动把菜单里的内容抄过去。
- 创建一个
结论:头文件就是为了“模块化编程”和“省事”而生的。
2. 头文件里到底该写什么?
头文件遵循 “只动口(声明),不动手(定义)” 的原则。
| 应该放进 .h 的 (菜单) | 绝对不要放进 .h 的 (做菜) |
|---|---|
函数声明(void LED_Init(void);) | 函数体(void LED_Init(void) { ... }) |
宏定义(#define LED_PIN 13) | 全局变量定义(int count = 0;) |
结构体/枚举定义(struct,enum) | |
外部变量声明(extern int count;) | |
包含其他头文件(#include "stm32f10x.h") |
致命错误警告:
如果你在 .h 里写了函数体(也就是带 {} 的具体代码),当有两个 .c 文件同时包含这个 .h 时,链接器会报错:"Multiple definition" (重复定义)。因为你相当于在两个文件里都造了同一个函数,编译器不知道听谁的。
3. 头文件的标准结构 (模板)
我们在 STM32 开发中,每一个外设模块(比如 LED, Serial, Key)都应该有一套标准的 .c 和 .h。
这里有一个万能模板,特别是那个 “防卫宏” (Include Guard),是必须写的!
文件名:LED.h
C
// 1. 防止重复包含 (Include Guard) —— 必须有!
// 格式通常是 __文件名_H
#ifndef __LED_H
#define __LED_H
// 2. 包含这个模块需要的其他头文件
#include "stm32f10x.h"
// 3. 宏定义 (对外公开的参数)
#define LED_PIN GPIO_Pin_13
#define LED_PORT GPIOC
// 4. 全局变量声明 (如果有的话,要加 extern)
// extern int LED_Status;
// 5. 函数声明 (告诉别人怎么用)
void LED_Init(void);
void LED_ON(void);
void LED_OFF(void);
void LED_Turn(void);
// 6. 结束防卫宏
#endif4. 那个 #ifndef 是干嘛的?
这叫 “头文件卫士” (Header Guard)。
场景:
main.c包含了A.h。main.c也包含了B.h。- 但是,
B.h内部也包含了A.h。
如果不加卫士,main.c 最终会包含两次A.h。编译器会看到两次 struct User {...},然后报错“重定义”。
卫士的工作原理:
- 编译器第一次遇到
LED.h:检查__LED_H定义了吗?没有 -> 进去定义它 -> 读完代码。 - 编译器第二次遇到
LED.h:检查__LED_H定义了吗?定义过了 -> 直接跳过到#endif。
5. < > 和 " " 的区别
你在代码里经常看到两种 include 写法:
#include <stm32f10x.h>(尖括号)- 含义: 去 编译器安装目录/系统库目录 里找。
- 用途: 引用标准库 (
stdio.h) 或官方库。
#include "LED.h"(双引号)- 含义: 先在 你自己当前的工程目录 里找,找不到再去系统目录找。
- 用途: 引用你自己写的头文件。
6. 预处理的魔法
当你点击“编译”按钮时,第一步叫 预处理 (Preprocessing)。
在这个阶段,编译器其实做了一个简单的 “复制粘贴” 动作:
它找到 #include "LED.h" 这行字,把它删掉,然后把 LED.h 文件里的所有内容,原封不动地粘贴到 main.c 的那个位置。
所以,对于编译器来说,它根本不知道什么是 .h,它最后看到的只有一个巨大的、拼装好的 .c 文件。
总结
- .h 是对外营业的窗口:想让别人用的函数,在这里声明。
- .c 是封闭的后厨:具体的逻辑实现,不想给别人看的静态函数 (
static),都放在这里。 - 记得加卫士:
#ifndef ... #define ... #endif是标配。 - 不要写代码:头文件里不要写函数体,只写声明。