16 · 预处理器与头文件

2026.09.27 · 🟡 中

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     # 一起编译链接

关联

22-项目工程结构 · 04-常量与存储类 · 13-共用体-位域-枚举-typedef