16 · 预处理器与头文件
Java 没有预处理器。C 在真正编译前,先由预处理器做一轮「纯文本替换」。
#include本质是把文件内容原样粘贴进来,不是 Java 那种「引用」。
三大指令
#include <stdio.h> // 系统头文件:从系统目录找
#include "myheader.h" // 自定义头文件:先找当前目录
#define PI 3.14159 // 宏常量
#define MAX(a,b) ((a)>(b)?(a):(b)) // 带参宏(注意括号!)
#ifdef DEBUG // 条件编译
printf("debug...\n");
#endif
⚠️ 带参宏的括号陷阱
#define SQ(x) x * x
SQ(1 + 2) // → 1 + 2 * 1 + 2 = 5 ❌ 不是 9!
#define SQ(x) ((x) * (x)) // ✅ 每个参数和整体都加括号
宏是文本替换,没有类型检查、没有作用域。能用 const / inline 就别用宏。
预定义宏
| 宏 | 含义 |
|---|---|
__FILE__ |
当前文件名 |
__LINE__ |
当前行号 |
__DATE__ / __TIME__ |
编译日期/时间 |
__func__ |
当前函数名 |
头文件保护(防止重复包含)
#ifndef MYHEADER_H // 如果没有定义过
#define MYHEADER_H // 就定义它
/* 头文件内容 */
#endif // 结束
现代也常用 #pragma once(更简洁,但非标准)。
头文件该放什么
- ✅ 函数声明(原型)、宏定义、结构体定义、
typedef、extern变量声明。 - ❌ 不要放函数定义(会导致多文件重复定义链接错误)。
多文件项目结构
// calc.h
#ifndef CALC_H
#define CALC_H
int add(int a, int b);
#endif
// calc.c
#include "calc.h"
int add(int a, int b) { return a + b; }
// main.c
#include "calc.h"
int main(void) { return add(1, 2); }
gcc main.c calc.c -o app # 一起编译链接