讀古今文學網 > C語言解惑 > 23.4 多文件結構 >

23.4 多文件結構

多文件結構可以含有多個頭文件和源文件。前兩種結構均是多文件結構的特例。嚴格講,結構化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.一般的多文件模式

對一般比較大的程序設計而言,常常分成幾個源文件,每個源文件有自己的頭文件,然後組成工程文件。作為一個程序員,必須熟悉這種結構並正確運用它。