C 语言头文件与 #include
更新时间:2026-08-26。本文是
languages/c/主题入门层第 58 篇。程序超过几百行,就该拆成多个 .c 文件;多个文件要共享函数声明、类型定义、宏——靠的就是头文件(.h)和#include。它是 C 工程组织的基石:stdio.h、stdlib.h你天天见,但"头文件里到底该放什么、为什么重复包含会炸",是这一篇的主题。
本文要回答的问题
#include到底做了什么?- 尖括号
<...>和双引号"..."有什么区别? - include guard 为什么是必须的?
- 头文件该放什么、不该放什么?
一、#include 就是"原样粘贴"
c
#include <stdio.h>预处理阶段把 stdio.h 的全部内容插入到这一行位置。gcc -E 展开后能看到几百行标准库声明——#include 本质上就是文本包含。
二、尖括号 vs 双引号
| 写法 | 查找路径 | 用途 |
|---|---|---|
#include <stdio.h> | 系统头文件目录(/usr/include 等) | 标准库/系统库 |
#include "my.h" | 当前目录优先,找不到再去系统目录 | 自己的头文件 |
c
#include <stdio.h> // 标准库
#include <string.h> // 标准库
#include "student.h" // 自己写的,用双引号三、include guard:防止重复包含
同一个头文件被两个源文件包含时没问题;但被间接包含两次呢?
c
// math_utils.h
struct Point { int x, y; };
// main.c
#include "math_utils.h"
#include "shapes.h" // shapes.h 里又 #include "math_utils.h"struct Point 被定义两次 → 编译错误 "redefinition"。解法是每个头文件加 include guard:
c
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
struct Point { int x, y; };
#endif /* MATH_UTILS_H */原理:第一次包含时定义 MATH_UTILS_H,第二次进来发现已定义,整个内容跳过。现代编译器也支持 #pragma once(一行搞定,GCC/Clang/MSVC 都认),但跨编译器最稳的还是标准的三重奏写法。
四、头文件该放什么、不该放什么
| 应该放 | 不应该放 |
|---|---|
| 函数声明(原型) | 函数定义(实现) |
| 结构体/联合体/枚举定义 | 变量定义(除非 extern 声明) |
| typedef | static 变量 |
| 宏定义 | 可执行代码 |
| extern 变量声明 | 临时用的小常量(能用就用) |
c
// student.h —— 典型头文件
#ifndef STUDENT_H
#define STUDENT_H
typedef struct {
int id;
char name[32];
} Student;
void student_print(const Student *s); // 声明
#endifc
// student.c —— 实现
#include "student.h"
#include <stdio.h>
void student_print(const Student *s) {
printf("%d %s\n", s->id, s->name);
}规则:声明进 .h,实现进 .c,各司其职。变量定义放头文件会导致多个 .c 文件各有一份——链接时重复定义。
五、多文件编译:从 -c 到链接
bash
gcc -c student.c -o student.o # 1. 编译成目标文件
gcc -c main.c -o main.o # 2. 另一个目标文件
gcc student.o main.o -o app # 3. 链接成可执行文件或一步到位 gcc student.c main.c -o app。原理与更细的流程(预处理→编译→汇编→链接)见编译与链接全过程。
六、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 忘 include guard | redefinition | 三重奏模板 |
| 变量定义放头文件 | 链接重复定义 | 只放 extern 声明 |
| 函数实现放头文件 | 多份拷贝/链接错误 | 实现放 .c |
| 用尖括号包含自己的头文件 | 找不到文件 | 自己的用双引号 |
| 头文件互相包含 | 循环依赖编译错 | 前置声明/重构 |
七、与本站主线衔接
- 宏定义与条件编译,见宏定义 #define、条件编译;
- 大型工程的头文件设计(前置声明、纯头文件库),见多文件工程与头文件设计;
- 编译链接完整流程,见编译与链接全过程。
一句话总结
#include 是预处理期的文本粘贴:标准库用尖括号、自己的头文件用双引号;头文件只放声明与类型定义,每个都加 include guard(#ifndef 三重奏或 #pragma once)防重复包含——多文件工程的秩序,全靠头文件这层"合同"来维系。