3.9 KiB
2.7 多文件开发
我们的示例都很简单,很容易将它们放到一个文件中。但实际的项目往往非常复杂,上千行的代码放到一个文件中将是非常恐怖的事情,更别提那些几十万甚至上千万行代码的软件系统了。
解决这种情况,就需要将代码分散到不同文件中去。如果设计得合理,我们甚至可以将某些文件应用在不同项目里,这有点儿像程序库,提高了代码的复用程度。
合理的划分文件,将使项目结构更清晰,进而提高项目的易维护性。但将项目分解到不同源码文件中去后,遇到最大的问题便是如何在编译链接时,将他们再次合并到一起去。这将涉及到前面提及的编译原理,以及宏保护和变量声明等内容。
2.7.1 多个源文件的合并
前面提到,编译器将多个源码文件编译成对应的多个目标文件,连接器将多个目标文件链接成可执行的二进制文件。可见,多个源文件的合并,是在编译的基础上通过链接实现的。
以下是一个用来生成 hello.exe 程序的源码文件和链接关系:
object | source |
---|---|
a.o | a.cpp |
b.o | b.c |
dir/c.o | dir/c.c |
2.7.2 多个源码文件产生的问题
在项目中使用多个源码文件将面临一些难题。
首先,我们必须明确定义与声明的区别。由于定义会为变量/函数分配实际的存储空间,而声明不会。因此,程序中不允许对同一个文件进行重复定义,但可以重复声明(可以理解为计算机无法将同一个变量名对应到一个以上不同的内存地址上)。
如果,在两个文件中都要使用同一个变量 a,那么只能在一个变量中定义这个变量,而在另一个变量中声明这个变量(告诉编译器这个变量在其他地方定义了)。
/**
* @file a.c
*/
int arry[3]={0, 1, 2}; // 定义数组变量.
char var; // 定义 char 变量.
void funa(void)
{
printf("This funa.\n");
}
/**
* @file main.c
*/
#include <stdio.h>
extern int arry[3]; // 声明外部数组变量.
extern char var; // 声明外部 char 变量.
extern void funa(void); // 声明外部函数.
int main(void)
{
var = 0;
funa();
return arry[0];
}
众所周知,声明函数可以不使用 extern 关键字(变量必须使用该关键字),并且一些被其他文件使用的函数或数据结构可以放置到头文件中。因此,我们可以像下面这样编写程序。
/**
* @file a.h
*/
#ifndef _A_H
#define _A_H
typedef struct _STA
{
int X;
union
{
short Y;
long Z;
}
}STA;
void funa(void);
int funb(int idx, int val);
#endif // _A_H
/**
* @file a.c
*/
#include <stdio.h>
static int arry[3]={0, 1, 2}; // 定义数组变量.
STA sta;
void funa(void)
{
printf("This funa.\n");
}
int funb(int idx, int val)
{
if(2<idx || 0>idx)
{
return -1;
}
arry[idx] = val;
return 0;
}
/**
* @file main.c
*/
#include "a.h"
extern STA sta;
int main(void)
{
int ret;
sta.X = 5;
ret = funb(1, sta.X);
funa();
return ret;
}
注意,上面 a.h 中使用了 ifndef……endif 条件编译对头文件实施了保护。 另外,a.c 中数组变量 arry[3] 是 static 类型,这限制了 arry 只能被用在 a.c 中,如果在另一文件中使用 arry 变量将是不合法的。static 限制了变量的使用,起到了数据封装的作用。
可以在头文件中声明变量,但不允许在头文件中定义变量。这是因为当这样的头文件被多个源文件包含时,将导致定义多个同名变量,而多个同名变量是不允许存在于同一个程序中的。
练习
参考 2.6 中的练习题,编写一个 RS485 通讯程序,并在主程序中调用收发接口,实现周期通讯。