Skip to content

.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 (聪明办法):
    1. 创建一个 motor.h
    2. 把所有能给别人用的函数声明,都写在 motor.h 里。
    3. main.c 只需要写一句 #include "motor.h"
    4. 编译器会自动把菜单里的内容抄过去。

结论:头文件就是为了“模块化编程”和“省事”而生的。


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. 结束防卫宏
#endif

4. 那个 #ifndef 是干嘛的?

这叫 “头文件卫士” (Header Guard)

场景:

  • main.c 包含了 A.h
  • main.c 也包含了 B.h
  • 但是,B.h 内部也包含了 A.h

如果不加卫士,main.c 最终会包含两次A.h。编译器会看到两次 struct User {...},然后报错“重定义”。

卫士的工作原理:

  1. 编译器第一次遇到 LED.h:检查 __LED_H 定义了吗?没有 -> 进去定义它 -> 读完代码。
  2. 编译器第二次遇到 LED.h:检查 __LED_H 定义了吗?定义过了 -> 直接跳过#endif

5. < >" " 的区别

你在代码里经常看到两种 include 写法:

  1. #include <stm32f10x.h> (尖括号)
    • 含义:编译器安装目录/系统库目录 里找。
    • 用途: 引用标准库 (stdio.h) 或官方库。
  2. #include "LED.h" (双引号)
    • 含义: 先在 你自己当前的工程目录 里找,找不到再去系统目录找。
    • 用途: 引用你自己写的头文件。

6. 预处理的魔法

当你点击“编译”按钮时,第一步叫 预处理 (Preprocessing)

在这个阶段,编译器其实做了一个简单的 “复制粘贴” 动作:

它找到 #include "LED.h" 这行字,把它删掉,然后把 LED.h 文件里的所有内容,原封不动地粘贴到 main.c 的那个位置。

所以,对于编译器来说,它根本不知道什么是 .h,它最后看到的只有一个巨大的、拼装好的 .c 文件。

总结

  • .h 是对外营业的窗口:想让别人用的函数,在这里声明。
  • .c 是封闭的后厨:具体的逻辑实现,不想给别人看的静态函数 (static),都放在这里。
  • 记得加卫士#ifndef ... #define ... #endif 是标配。
  • 不要写代码:头文件里不要写函数体,只写声明。