C 函数声明与头文件
更新时间:2026-08-26。本文是
languages/c/主题入门层第 21 篇。单文件程序练手够用,但真正的工程是"很多文件"。头文件就是文件之间的"通讯录"——每个模块公开自己的接口,别人照着用。
本文要回答的问题
- 函数"声明"和"定义"有什么区别?
- 头文件里放什么、
.c文件里放什么? #ifndef头文件守卫是干什么的?
一、声明 vs 定义
c
int add(int a, int b); // 声明(原型):"有这个东西,签名是这样"
int add(int a, int b) { // 定义:"这是它的实现"
return a + b;
}| 声明 | 定义 | |
|---|---|---|
| 作用 | 告诉编译器"函数存在" | 提供函数体 |
| 占空间 | 否 | 是(生成代码) |
| 出现次数 | 可多次 | 全程序一次 |
| 结尾 | 分号 | 花括号 |
C 要求使用前先声明(C99 起"隐式声明"直接是错误)。声明放头文件,定义放源文件,是这个分工的基础。
二、头文件与源文件的分工
以"计算器模块"为例:
c
// calc.h —— 头文件:只放声明(接口)
#ifndef CALC_H
#define CALC_H
int add(int a, int b);
int sub(int a, int b);
#endifc
// calc.c —— 源文件:放定义(实现)
#include "calc.h"
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }c
// main.c —— 使用方:包含头文件即可
#include <stdio.h>
#include "calc.h"
int main(void) {
printf("%d\n", add(2, 3)); // 5
return 0;
}bash
gcc -O0 -g -Wall -o calc main.c calc.c规则就两条:
- 头文件只放声明(函数原型、
extern变量、结构体类型、宏); .c文件放定义(函数体、全局变量定义),并#include自己的头文件(让编译器核对声明和定义一致)。
<stdio.h> 用尖括号(系统目录),"calc.h" 用双引号(先找当前目录)。
三、头文件守卫:#ifndef
一个头文件可能被多个文件包含,甚至间接包含两次,就会"重复声明"。守卫防止这种情况:
c
#ifndef CALC_H // 如果没定义过 CALC_H
#define CALC_H // 定义它
... // 头文件内容只在这里面
#endif // 结束预处理逻辑:第一次包含时 CALC_H 未定义 → 进入内容,定义 CALC_H;第二次包含时已定义 → 跳过整个内容。每写一个新头文件,三行守卫是标配。缺了守卫,#include 两次同一头文件 → redefinition 错误。
更现代的
#pragma once也能实现同样效果,GCC/Clang/MSVC 都支持,但#ifndef写法最老、最可移植,教学用这种。
四、多文件编译
bash
gcc -O0 -g -Wall -o prog main.c calc.c other.c # 一次性编译所有源文件小工程这样就行。文件多了用 Makefile 管理增量编译——make 只重编改过的文件,见高手层多文件工程与 Makefile。gcc 的常用选项回顾见gcc 编译链接与运行。
五、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 忘 include 头文件 | implicit declaration | 使用前 include |
| 头文件没守卫 | redefinition 错误 | 三行守卫写上 |
| 头文件里放定义 | 链接重复符号 | 定义只放 .c |
"x.h" 写 <x.h> | 找不到自己头文件 | 自己的用双引号 |
| 声明和定义签名不一致 | 编译通过但行为错 | 让 .c include 自己的头文件 |
六、与本站主线衔接
- 预处理阶段
#include怎么展开、宏的细节,见预处理、宏与调试; - 多文件链接时符号怎么合并(重复定义 vs 强符号/弱符号),见专家层编译、链接与 ELF;
- 工程级模块划分实践,见高手层多文件工程与模块化。
一句话总结
头文件 = 接口通讯录(只放声明,三行守卫标配),源文件 = 实现(放定义并 include 自己的头文件);"使用前声明、多文件一起编译"这两条做到,单文件到工程的跨越就完成了。