Skip to content

模块化与头文件

当一个项目的体量越来越大之后,一个main.c文件似乎招架不住了。C语言自然也支持多文件项目,可以帮助你将代码按照逻辑拆分到不同的c源文件中,再在编译时照常生成一个可执行文件。

我们继续以一个命令行计算器程序为例。

头文件

现在我们希望能将代码中的入口main函数与实现计算功能的函数分装在两个源文件中,即类似下面的形式:

c
int main(void) {
    cal_add(6, 9);
    cal_sub(6, 9);
    cal_mul(6, 9);
    cal_div(9, 3);
    return 0;
}
c
#include <stdio.h>

int cal_add(const int a, const int b) {
    const int result = a + b;
    printf("执行加法计算:%d + %d = %d\n", a, b, result);
    return result;
};

int cal_sub(const int a, const int b) {
    const int result = a - b;
    printf("执行减法计算:%d - %d = %d\n", a, b, result);
    return result;
};

int cal_mul(const int a, const int b) {
    const int result = a * b;
    printf("执行乘法计算:%d × %d = %d\n", a, b, result);
    return result;
};

int cal_div(const int a, const int b) {
    const int result = a / b;
    printf("执行除法计算:%d ÷ %d = %d\n", a, b, result);
    return result;
};

在C语言中,若我们想要在main.c中使用在calculator.c中定义的函数、变量等,则需要通过头文件的方式将其引入。为此,我们需要编写一个文本格式的h文件,对于calculator.c我们一般约定其头文件为calculator.h

calculator.h
c
#ifndef CALCULATOR_H
#define CALCULATOR_H

int cal_add(int a, int b);
int cal_sub(int a, int b);
int cal_mul(int a, int b);
int cal_div(int a, int b);

#endif //CALCULATOR_H

然后对已经存在的两个源码文件稍作更改,现在你的项目应当是这样的:

c
#include "calculator.h"

int main(void) {
    cal_add(6, 9);
    cal_sub(6, 9);
    cal_mul(6, 9);
    cal_div(9, 3);
    return 0;
}
c
#include <stdio.h>
#include "calculator.h"

int cal_add(const int a, const int b) {
    const int result = a + b;
    printf("执行加法计算:%d + %d = %d\n", a, b, result);
    return result;
};

int cal_sub(const int a, const int b) {
    const int result = a - b;
    printf("执行减法计算:%d - %d = %d\n", a, b, result);
    return result;
};

int cal_mul(const int a, const int b) {
    const int result = a * b;
    printf("执行乘法计算:%d × %d = %d\n", a, b, result);
    return result;
};

int cal_div(const int a, const int b) {
    const int result = a / b;
    printf("执行除法计算:%d ÷ %d = %d\n", a, b, result);
    return result;
};
c
#ifndef CALCULATOR_H
#define CALCULATOR_H

int cal_add(int a, int b);
int cal_sub(int a, int b);
int cal_mul(int a, int b);
int cal_div(int a, int b);

#endif //CALCULATOR_H

上面的h文件就是头文件。我们在calculator.h头文件中给出了四个函数cal_addcal_subcal_mulcal_div的定义,然后再在calculator.c中给出四个函数的具体实现。之后,我们需要在其他文件(例如main.c)中引入calculator.h头文件,然后就能在main.c中使用这四个函数。

现在此项目将通过编译,并且在运行时拥有正确的输出。

c
执行加法计算:6 + 9 = 15
执行减法计算:6 - 9 = -3
执行乘法计算:6 × 9 = 54
执行除法计算:9 ÷ 3 = 3

引入头文件

你可能已经注意到,在calculator.c中引入两个头文件时的语法略有不同:

c
#include <stdio.h>
#include "calculator.h"

stdio.h是C语言内置的头文件,calculator.h是我们刚刚编写的头文件。在C语言中,我们约定使用<...>表示内置的头文件,使用"..."表示我们自己编写的头文件。

你知道吗?

在编译过程中,我们引入头文件的语句#include ...会被替换成头文件中包含的所有方法的具体实现,无论你有没有调用这些方法。

头文件格式

头文件中最少需要包含有关函数或变量的定义,否则你就没有编写这个头文件的必要了。

不过,我们约定将头文件编写为如下结构:

FILENAME.h
c
#ifndef FILENAME_H  // 人话:如果 FILENAME_H 未被定义
#define FILENAME_H

// 把定义写在这里...

#endif //FILENAME_H

在头文件中包含了条件判断,这样的话可以避免我们意外地重复引入头文件。在上一节的「你知道吗?」中你已经得知引入头文件的语句会被替换成完整的代码,因此重复引入会让编译后的程序体积更大、效率更低。

现代工具会在创建头文件时帮助你完成这些基本的格式。

编译与运行

对于单文件程序,你可以使用一行命令完成编译;而这个多文件的计算器就不允许你简单地照搬这一行代码了。

在项目中包含大量C源文件时,我们需要使用更专业的工具来帮助我们编译整个项目。例如,CMake就是非常成熟的工具了。

如下编写您的CMakeList.txt

CMakeList.txt
c
cmake_minimum_required(VERSION 3.30)
project(Calculator C)

set(CMAKE_C_STANDARD 11)

add_executable(Calculator main.c
        calculator.c
        calculator.h
)

确保你的所有C语言源文件(包括ch文件)都包含在add_executable(...)中,且你的所有源文件中有且仅有一个main函数即可。您的IDE现在应当会正确地提供运行、调试或生成的选项,详请参见这里的教程哦

如果你需要编译一个C++与C的混合项目,请参见C++教程中的与C一同使用章节

贡献者

页面历史