多文件结构可以含有多个头文件和源文件。前两种结构均是多文件结构的特例。严格讲,结构化C程序设计应该使用多文件结构。如果只使用一个文件,即使它的函数设计很符合结构化设计,但也给查错和维护带来不便。试想一下,几千行的程序都在一个文件中,能算是好的设计方法吗?
要进行模块化和结构化设计,必须掌握多文件编程的知识。这主要涉及如何使用函数原型、头文件和工程文件等方面的知识,而且与所使用的集成环境也有关系。
1.使用多个文件进行模块化设计
假设要求编制两个函数,分别计算两个数的最大值和平均值,然后使用主函数调用它们。将这两个函数分别设计在max.c和mean.c文件中,主函数在find.c文件中。这样,每个文件是一个单独模块,功能单一,查错容易。两个函数模块互不牵扯。然后将任务分派给3个人去完成。
但如何将这些文件组成一个整体呢?一般把这个整体称为工程,目前VC又称其为项目。使它们协调工作的方法不止一种,建议使用头文件和原型声明,充分利用编辑器的严格检查来组织实施。下面编制的程序就是考虑到这些实施方法而设计的。虽然程序很小,但已经能说明问题的实质。3个人编写的程序内容如下。
//第1个人编写的求最大值函数文件:max.cdouble 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.89max=4567.890000mean=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.一般的多文件模式
对一般比较大的程序设计而言,常常分成几个源文件,每个源文件有自己的头文件,然后组成工程文件。作为一个程序员,必须熟悉这种结构并正确运用它。