多文件结构可以含有多个头文件和源文件。前两种结构均是多文件结构的特例。严格讲,结构化C程序设计应该使用多文件结构。如果只使用一个文件,即使它的函数设计很符合结构化设计,但也给查错和维护带来不便。试想一下,几千行的程序都在一个文件中,能算是好的设计方法吗?
要进行模块化和结构化设计,必须掌握多文件编程的知识。这主要涉及如何使用函数原型、头文件和工程文件等方面的知识,而且与所使用的集成环境也有关系。
1.使用多个文件进行模块化设计
假设要求编制两个函数,分别计算两个数的最大值和平均值,然后使用主函数调用它们。将这两个函数分别设计在max.c和mean.c文件中,主函数在find.c文件中。这样,每个文件是一个单独模块,功能单一,查错容易。两个函数模块互不牵扯。然后将任务分派给3个人去完成。
但如何将这些文件组成一个整体呢?一般把这个整体称为工程,目前VC又称其为项目。使它们协调工作的方法不止一种,建议使用头文件和原型声明,充分利用编辑器的严格检查来组织实施。下面编制的程序就是考虑到这些实施方法而设计的。虽然程序很小,但已经能说明问题的实质。3个人编写的程序内容如下。
// 第1 个人编写的求最大值函数文件:max.c double max (double m1 , double m2 ) { if (m1 > m2 ) return m1 ; else return m2 ; }
这个文件自成系统,所以最简单。其实,工程应用时,许多文件就是以函数为单位的。这个模块的正确性可以自己验证,验证正确无误后,就可提交使用。
// 第2 个人的求平均值函数文件:mean.c // 求平均值函数mean #include "mean.h " // 包含自定义的头文件 double mean (double m1 , double m2 ) { return ((m2+m1 )*DIV2 ); } // 求平均值函数的头文件mean.h const double DIV2 = 0.5 ;
为了说明使用const定义常数问题,特让mean.c文件中的mean函数使用常系数DIV2。常数设计在它的头文件中,这里把它命名为mean.h。调试成功后,提供这两个文件。
// 第3 个人的主函数文件:find.c // 主函数main #include\"find.h\" // 包含自定义的头文件 void main ( ) { double a ,b ; printf (\"Input a and b :n\" ); scanf (\"%lf%lf\" ,&a ,&b ); printf ( \"max=%lfn\" ,max ( a ,b )); printf ( \"mean=%lfn\" ,mean ( a ,b )); } // 主函数使用的头文件find.h #include <stdio.h> double max (double ,double ); double mean (double , double );
总共有3个C程序源文件和2个头文件,共5个文件。
2.头文件和函数原型的作用
一般是将所有的函数原型和外部变量的声明,以及常数的定义都放在一个头文件里,需要这些头文件的源文件,就可以将它们包含进去。虽然求最大值文件没有头文件,但也要在主程序的头文件find.h中声明它的函数原型,以保证find.c的main函数能正确分辨它。
常数DIV2定义在mean.h中,在mean.c中使用如下语句包含它。
#include \"mean.h\"
因为只有mean函数使用这个常数,又为了说明自带头文件的方法,所以单独做了这个头文件。主函数使用的函数库的头文件“stdio.h”,也有意放在头文件find.h中。一般来讲,如果是大家共有的常数和变量,可以协商放在一个公共的头文件中。这里之所以做成2个头文件,主要是演示头文件的定义和使用方法。
3.组合为一个工程项目
假设构造的项目为find,使用VC构成find项目的步骤如下。
(1)利用VC构造一个空项目find。如图23-4所示,这里面没有源文件和头文件。

图23-4 利用VC构造一个空项目find示意图
(2)将用户的5个文件拷贝到find目录中,然后将它们添加到项目中。可以使用Projecct菜单Add To Project选项的Files命令,也可以如图23-4所示,单击鼠标右键,选中Add Files Folder命令,弹出如图23-5所示的Insert Files into Project对话框,找到find文件夹,插入所需文件。将C的源文件插入Source Files之下,头文件插入Header Files之下。图23-6给出结果示意图。

图23-5 Insert Files into Project对话框
(3)如图23-6所示,双击左边的文件图标,右边窗口显示相应的源文件。
(4)可以分别编译项目中的各个C程序文件,双击左边的文件图标,让它们出现在右边,就可以编译该文件。也可以一次对所有文件编译并产生执行文件。如果要一次编译,可以选择任意一个C文件,通过产生exe文件的选项(例如Build find.exe菜单项)一次编译并产生exe文件。菜单和相应的工具按钮如图23-6所示。

图23-6 插入所需文件示意图
(5)如图23-6所示,可以使用菜单命令或工具按钮执行程序编译产生find.exe文件(exe文件与项目同名),下面是运行示例。
Input a and b : 235.678 4567.89 max=4567.890000 mean=2401.784000
4.#define和const的异同
其实,在头文件里使用#define和const的作用并不完全等效。如果只是文件本身使用这个头文件,则两者等效。正如上面的文件mean.c一样,在mean.h中,下面两种方式均可。
const double DIV2 = 0.5 ; #define DIV2 0.5
如果只设计一个头文件find.h,就不能简单地将mean.h中的语句移到find.h中。其实,使用const的格式是定义一个内容不会改变的常数变量,所以它遵循变量的使用原则。即在头文件里声明一个外部const变量,在文件里赋初值。修改的find.h和mean.c文件内容如下。
// 取代mean.h 的find.h 文件 #include <stdio.h> extern const double DIV2 ; // 在头文件中声明为外部常量 double max (double ,double ); double mean (double , double ); //mean.c 文件 #include \"find.h\" const double DIV2=0.5 ; // 在使用的文件中定义这个常量 double mean (double m1 , double m2 ) { return (m2+m1 )*DIV2 ;}
5.使用条件编译编写头文件
上一节修改的程序,如果find.c两次包含头文件find.h(头文件过多时,会出现这种情况),在编译这个文件时,就会对头文件处理两次,这种重复包含有时会导致编译程序不能正常完成。为了避免这种情况,可以使用宏定义配合条件编译。假设宏名字为“_H_C6_H”,例如:
// 取代mean.h 的find.h 文件 #ifndef _H_C6_H // 如果没有定义c6.h #define _H_C6_H // 下面定义c6.h #include <stdio.h> extern const double DIV2 ; // 在头文件中声明为外部常量 double max (double ,double ); double mean (double , double ); #endif // 定义结束
至于这个宏的名字“_H_C6_H”,则是随意选择的名字。预处理程序处理完文件开始部分,名字_H_C6_H就有了定义。如果在find.c的预处理中再次遇见到包含find.h的语句,由于_H_C6_H已经有了定义,所以#if至#endif之间的东西都被丢掉。源程序包含的是头文件,头文件里才使用宏,所以这个宏的名字与头文件的名字无关。之所以使用“_H_C6_H”的怪异方式,是避免程序中定义重名的可能性。让字符串中包含字符H,则清晰地表示这是为了处理头文件。
也可以使用另外一种等效(推荐使用)形式。
#if !defined (_H_C6_H ) #define _H_C6_H …….// 这里是原来头文件的内容 #endif
6.使用文件包含的方法
虽然也可以使用将文件包含的方法,但没有上一种结构清晰。建议只作了解,如果碰到这种使用方法,能知道其组成原理即可。
因为在一个工作目录内,VC的项目不能同名,所以为它再建一个名为find1的空项目,将5个文件拷贝到find1目录,然后装入find.c并将它修改为如下的程序。
#include \"find.h\" #include \"max.c\" #include \"mean.c\" void main ( ) { double a ,b ; printf (\"Input a and b :n\" ); scanf (\"%lf%lf\" ,&a ,&b ); printf ( \"max =%lfn\" ,max ( a ,b )); printf ( \"mean=%lfn\" ,mean ( a ,b )); }
编译运行find1.exe,结果正确。这时注意一下VC的窗口,如图23-7所示,发现它自动将需要的文件都装入External Dependencies的下面。双击这些文件,显示在右边的窗口中。

图23-7 使用文件包含的VC窗口示意图
7.一般的多文件模式
对一般比较大的程序设计而言,常常分成几个源文件,每个源文件有自己的头文件,然后组成工程文件。作为一个程序员,必须熟悉这种结构并正确运用它。