头文件与源文件
多文件编程是 C 语言组织大型项目的基本方式。头文件(.h)包含类型定义、宏、函数声明等接口信息;源文件(.c)包含函数实现。这种分离实现了信息隐藏和模块化,让代码更易于维护和复用。
文件分工
头文件(.h):
- 类型定义(
struct、union、enum、typedef) - 宏定义(
#define) - 函数声明(原型)
- 外部变量声明(
extern) - 内联函数定义(C99
static inline)
源文件(.c):
- 函数实现
- 全局变量定义
static函数和变量(内部实现)
/* math_utils.h */
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
/* 类型定义 */
typedef struct {
double real;
double imag;
} Complex;
/* 宏 */
#define PI 3.141592653589793
/* 函数声明 */
Complex complex_add(Complex a, Complex b);
Complex complex_mul(Complex a, Complex b);
double complex_abs(Complex c);
#endif
/* math_utils.c */
#include "math_utils.h"
#include <math.h>
/* 函数实现 */
Complex complex_add(Complex a, Complex b)
{
Complex result = {a.real + b.real, a.imag + b.imag};
return result;
}
Complex complex_mul(Complex a, Complex b)
{
Complex result;
result.real = a.real * b.real - a.imag * b.imag;
result.imag = a.real * b.imag + a.imag * b.real;
return result;
}
double complex_abs(Complex c)
{
return sqrt(c.real * c.real + c.imag * c.imag);
}
/* main.c */
#include "math_utils.h"
#include <stdio.h>
int main(void)
{
Complex a = {3.0, 4.0};
Complex b = {1.0, 2.0};
Complex sum = complex_add(a, b);
printf("Sum: %.2f + %.2fi\n", sum.real, sum.imag);
printf("|a| = %.2f\n", complex_abs(a)); /* 5.0 */
return 0;
}
编译多文件项目
/* 分别编译 */
gcc -c math_utils.c -o math_utils.o
gcc -c main.c -o main.o
/* 链接 */
gcc math_utils.o main.o -o program
/* 一步完成 */
gcc math_utils.c main.c -o program
每个 .c 文件是一个独立的编译单元,编译器只处理该文件及其包含的头文件。链接器负责将多个目标文件合并为可执行文件。
头文件中的内容规则
应该放在头文件中:
/* 类型定义 */
struct Point { int x, y; };
typedef struct Point Point;
/* 宏 */
#define MAX_SIZE 100
/* 函数声明 */
int add(int a, int b);
/* 外部变量声明 */
extern int global_count;
/* 内联函数 */
static inline int max(int a, int b)
{
return (a > b) ? a : b;
}
不应该放在头文件中:
/* 变量定义(会导致重复定义) */
int shared = 0; /* 错误! */
/* 函数定义(除非 static inline) */
void func(void) { } /* 错误! */
/* 非 const 数组定义 */
int table[100]; /* 错误! */
内部实现隐藏
用 static 将内部函数和变量限制在源文件内:
/* string_utils.c */
#include "string_utils.h"
/* 内部辅助函数,不暴露 */
static int is_vowel(char c)
{
return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
}
/* 外部接口 */
int count_vowels(const char *str)
{
int count = 0;
for (int i = 0; str[i] != '\0'; i++)
if (is_vowel(str[i]))
count++;
return count;
}
常见错误
头文件中定义变量:
/* bad.h */
int global = 0; /* 每个包含此头文件的 .c 文件都有定义 */
/* 链接错误:multiple definition */
循环包含:
/* a.h */
#include "b.h"
/* b.h */
#include "a.h" /* 循环包含! */
头文件缺少保护:
/* unprotected.h */
struct Point { int x, y; }; /* 如果被包含两次,重复定义 */
/* 需要头文件保护 */
最佳实践
- 头文件只放声明,不放定义(
static inline除外) - 源文件包含自己的头文件(验证一致性)
- 用
static隐藏内部实现 - 使用头文件保护防止重复包含
- 一个头文件对应一个模块
- 头文件依赖最小化,避免不必要的包含