为了支持大规模C程序的开发,往往需要把程序分割为一定数量的源文件。C语言的源文件包括两类,一类是实现文件(.c
),一类是头文件(.h
)。一般地,实现文件主要包括函数和变量的定义,而头文件的作用是在多个定义文件中共享函数原型、宏定义和类型定义、变量声明等信息。
将一个程序分为多个源文件的方式组织程序具有很多好处,比如:
- 将相关(例如完成同一功能或关于同一事物)的函数和变量放在单独的文件中,便于理解程序结构。
- 可单独对每个源文件进行编译,避免每次修改都要重新编译整个程序,提高编译速度。
- 可以将程序的一部分文件用于其他程序,有利于函数重用。
头文件
使用文件包含指令#include
为定义文件包含需要的头文件。最好不要在使用#include
时包含绝对路径信息,这样可能会造成在不同机器或操作系统的不可移植。头文件自身可以使用#include
文件包含指令包含其他头文件。通常将#error
放在头文件中用来检查不应该使用该头文件的情况。
利用头文件共享宏定义和类型定义
使用#define
的宏定义和typedef
的类型定义都可以放在头文件中。这样使得程序易于修改,且避免了不同文件中同一个宏名或类型定义不同导致的问题。
/**************************************
* macro_typedef.h *
* *
* C语言共享宏定义和类型定义 *
**************************************/
#define N 100
typedef int BOOL;
/**************************************
* file1.c *
* *
* 引用共享的宏或类型定义 *
**************************************/
#include <stdio.h>
#include "macro_typedef.h"
int main()
{
printf("N = %d\n", N);
BOOL i = 10;
printf("i = %d\n", i);
return 0;
}
/**************************************
* file2.c *
* *
* 引用共享的宏或类型定义 *
**************************************/
#include <stdio.h>
#include "macro_typedef.h"
int main()
{
printf("N = %d\n", N);
BOOL i = 15;
printf("i = %d\n", i);
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
同一个类型定义可以出现在一个程序中的多个文件中。
/**************************************
* file3.c *
* *
* 测试同一个类型定义用到一个C程序的多个文件 *
**************************************/
#include <stdio.h>
#include "macro_typedef.h"
void print()
{
printf("N = %d,", N);
BOOL i = 50;
printf("i = %d\n", i);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
利用头文件共享函数原型
可以把函数定义的原型放在头文件中,在所有调用该函数的实现文件中包含该头文件。只将打算被其他定义文件使用的函数原型放在头文件中。
/**************************************
* function_define.c *
* *
* C语言中的函数定义 *
**************************************/
#include <stdio.h>
int Sum(int n)
{
int i = 1;
int sum = 0;
for (; i <= n; i++)
sum += i;
return sum;
}
void Print(int n, int sum)
{
printf("从1到%d的和为%d\n", n, sum);
}
/***************************************
* function_define.h *
* *
* C语言函数原型声明头文件 *
***************************************/
int Sum(int);
void Print(int, int);
/*************************************
* file1.c *
* *
* 使用其他文件定义的函数 *
*************************************/
#include "function_define.h"
int main()
{
int n = 10;
Print(n, Sum(n));
return 0;
}
/*************************************
* file2.c *
* *
* 使用其他文件定义的函数 *
*************************************/
#include "function_define.h"
int main()
{
int n = 5;
Print(n, Sum(n));
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
利用头文件共享变量声明
把全局变量的定义定义文件中,同时将变量的声明(使用extern
关键字)放在头文件里。通过包含该头文件就可以在其他文件中访问全局变量。确保变量的声明和定义一致。
/**************************************
* global_define.c *
* *
* C语言定义全局变量 *
**************************************/
float velocity = 100.0f;
void Fast()
{
velocity *= 1.1;
}
void Slow()
{
velocity *= 0.9;
}
/**************************************
* global_define.h *
* *
* 定义全局变量声明头文件 *
**************************************/
extern float velocity;
void Fast();
void Slow();
/***************************************
* file1.c *
* *
* C语言共享全局变量 *
***************************************/
#include <stdio.h>
#include "global_define.h"
int main()
{
Fast();
printf("当前速度: %g\n", velocity);
return 0;
}
/***************************************
* file1.c *
* *
* C语言共享全局变量 *
***************************************/
#include <stdio.h>
#include "global_define.h"
int main()
{
Slow();
printf("当前速度: %g\n", velocity);
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
不要多次包含同一个头文件
如果头文件中只包含宏定义、函数原型或变量声明,则多次包含头文件并不会产生编译错误,但是如果头文件中包含类型定义,那么就会产生编译错误。
为了防止多次包含同一个头文件,可以使用#ifndef
和#endif
指令来将文件内容包裹起来,以保证在一个文件中,这段文件内容只被包含一次。
#ifndef 宏名称
#define 宏名称
...文件内容
#endif
- 1
- 2
- 3
- 4
首次包含时,宏应该未定义,所以预处理器保留文件内容,而如果再次包含时,由于宏已被定义,则#ifndef
和#endif
文件内容将被丢弃。
/***************************************
* file1.h *
* *
* 使用#ifndef和#endif避免多次包含 *
***************************************/
#ifndef _FILE1_H_
#define _FILE1_H_
#define N 20
typedef int Integer;
#endif
/**************************************
* file1.c *
* *
* 使用file1.h头文件 *
**************************************/
#include <stdio.h>
#include "file1.h"
int main()
{
printf("N = %d,", N);
Integer i = 100;
printf("i = %d\n", i);
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
C语言程序的划分原则
- 把相关函数集合放在单独的定义文件中(*.c)。
- 创建和定义文件名相同的头文件(*.h),并在定义文件中包含该头文件。
- 将头文件添加到所有需要使用其中声明的函数等信息的文件。
- 添加程序入口函数main函数,并将其写在一个与应用程序名字相同的定义文件内。
大规模C程序的构建
大多数编译器允许单独一步构建C程序,例如gcc的一般格式为:
gcc -o 可执行文件名 文件1.c 文件2.c ... 文件n.c
- 1
编译器首先将这些定义文件编译成目标代码,然后再由链接器将这些代码链接成目标文件,目标文件的名称由-o
选项指定。
makefile
当源文件过多时,上面的编译命令将变得非常复杂,而且当一个源文件发生改变时,所有文件都要重新编译。针对这些问题,为了更容易构建大规模的程序,可以利用makefile
文件。makefile
文件包含了程序部分的文件和文件之间的依赖性。makefile
的基本格式如下:
目标 : 依赖文件列表
目标由依赖文件生成命令
- 1
- 2
在创建了makefile
之后,就可以直接使用make
工具构建程序。
/**************************************
* add.h *
* *
* 加法器,执行加法运算 *
**************************************/
float add(float, float);
/*************************************
* add.c *
* *
* 加法器的实现 *
*************************************/
#include "add.h"
float add(float a, float b)
{
return a + b;
}
/**************************************
* minus.h *
* *
* 减法器 *
**************************************/
float minus(float, float);
/**************************************
* minus.c *
* *
* 减法器的实现 *
**************************************/
#include "minus.h"
float minus(float a, float b)
{
return a - b;
}
/**************************************
* calculate.c *
* *
* 主函数,利用加减法器计算 *
**************************************/
#include <stdio.h>
#include "add.h"
#include "minus.h"
int main()
{
float a, b;
printf("输入两个浮点数:");
scanf("%f%f",&a,&b);
printf("%g和%g的和为%g\n", a, b, add(a, b));
printf("%g和%g的差为%g\n", a, b, minus(a, b));
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
Makefile文件:
calc : calculate.o add.o minus.o
gcc -o calc calculate.o add.o minus.o
calculate.o : calculate.c minus.h add.h
gcc -c calculate.c
minus.o : minus.c minus.h
gcc -c minus.c
add.o : add.c add.h
gcc -c add.c
.PHONEY : clean
clean :
rm *.o calc
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
多文件链接时可能出现的错误
链接期间经常出现的是无法解决外部引用的错误,可能原因有以下几种:
- 变量或函数名拼写错误。
- 连接器找不到指定的文件。
- 链接器找不到程序中使用的库函数。
重新构建的几种情况
- 只有一个定义文件修改。只编译该源文件并重新链接程序。
- 改变头文件。重新编译包含此头文件的所有文件,重新链接程序。
makefile
可根据文件的时间自动重新构建,并且按照以上方式,只编译被修改的源文件。
程序外定义宏
C语言的程序需要依赖一些宏的定义,大多数编译器(包括gcc)支持通过选项-D
定义宏的值,如果没有指定值则默认为1.同时一些编译器也提供了-U
选项,用于取消从外部取消程序中宏的定义。
/***************************************
* external_macro.c *
* *
* C语言使用外部宏 *
***************************************/
#include <stdio.h>
int main()
{
#ifdef DEBUG
printf("现在处于Debug模式\n");
#endif
#if OPTION == 1
printf("当前选项为1\n");
#elif OPTION == 2
printf("当前选项为2\n");
#else
#error wrong options
#endif
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
参考文献
- K.N. King 著,吕秀峰 译. C语言程序设计-现代方法. 人民邮电出版社