第十四章 程序的文件结构

14.1 源文件和头文件

14.2 如何创建多个单元文件

14.3 如何写头文件

   14.3.1 在头文件内加入函数声明

   14.3.2 最常见的预编译语句

14.4 如何使用头文件

14.5 变量在多个源文件之间的使用

   14.5.1 变量声明

   14.5.2 多个文件中共享变量的实例

14.6 附:如何单独生成一个头文件

 

程序是由什么组成的? 学习到今天,我们至少有两个答案:

第1,程序由代码语句组成。正是一行行的代码,组成了一个完整的程序。

第2,程序由函数组成。一个个函数之间的互相调用,最终构建出一个完整的程序。

今天我们又有一个新的回答:“程序由文件组成”。

程序为什么需要使用多个文件?

一个小的程序,可以只写一个源文件,但程序稍微一大,就需要将其中不同的逻辑实现放到不同的源文件.对于需要多人一起开发的软件,自然更需要多个源文件。

 

14.1 源文件和头文件

 

和别的一些语言不同,C,C++的代码文件有“头文件”和“代码文件”之分。二者合起来我们称为单元(Unit)文件。

 

扩展名为 .c 或 .cpp 的文件,主要用以实现程序的各种功能,我们称为代码文件。

扩展名为 .h 的文件,称为头文件。在头文件里主要写一些函数、数据(包括数据类型的定义)、等的声明,这样可以在多个.c或.cpp文件内共享这些函数、数据。第12章我们提过到头文件的功能。说它可以起到函数“名片夹”的作用。

 

大都数时候,源文件和头文件是对应出现的,比如有一个 A.cpp 的源文件,就会有一个 A.h 的头文件。这种情况在我们写应用程序时,更是常见。所以C++ Builder对此进行了强化。比如,它支持在同名源文件和头文件之间通过热键来回切换。在CB6.0里,编辑器打开对应的源文件和头文件时,将显示为同一页下的两个子页。

 

我们来实际动手看看源文件与头文件在CB里的对应关系。

 

运行 C++ Builder 6或5。

这一次我们需要一个空白的Windows工程。很有可能,在你打开CB时,它就自动为你打开了一个工程。为了不出错,我们还是亲自建一个。CB6请使用主菜单:File | New | Application;而CB5则使用:File | New Application 新建一个Windows 空白工程 如果在这过程中CB出现是否存盘的询问,请回答不存盘。

 

找到“代码窗口”。如果你看到的是一个叫"Form1"的表单,请按F12,“代码窗口”将跑到前面。它的标题应该是默认的"Unit1.cpp"。正是当前代码文件的文件名。如下图:

 

 

对于CB6,还可以看到在该窗口的底部有这样一个分页:

 

 

源文件:Unit1.cpp 和头文件:Unit1.h 并列着,我们可以方便地选择。至于 "Diagram",称为“图解”。这是一个给这个源文件加配套注解,及表单上各控件的依赖关系的地方。如果是一个开发小组在进行共同开发,严格地要求每个成员为每个单元文件写上“Diagram”,可以更好地实现程序员之间的沟通。

CB5没有这些,不过下面的热键操作两个版本均一样的,要求大家记住。

Ctrl + F6 可以在源文件和头文件之间来回切换。请大家试试。这个简单的操作将在我们今后的编程过程中高频率地使用。

 

14.2 如何创建多个单元文件

 

前面我们在“Windows应用程序工程”中看到了头文件与源文件的匹配关系,在“控制台”的工程中,也同样存在。不过由于控制台经常只用来写一些小小的程序,所以往往只需一个源文件即可。由于只有一个源文件,所以也就不存在函数、数据在多个文件之间“共享”的需要,因此边头文件也就可以不提供。

 

那么,是不是只有在程序很大,或者只有在有很多人同时开发一个软件时,才需要多个源文件呢?

这就好像你家里只有两本书:《红楼梦》和《格林童话》,是把它们放在同一个抽屉里呢?还是分开放到两个抽屉里?我觉得后者是比较好的选择。因为我们常常希望家里看《格林童话》的人,最好不要去看《红楼梦》。

程序也一样,最好把不同的逻辑实现,放到不同的源文件中。

 

下面我们做一个实例。例子的代码我们都已经学过。目标是实现一个可以求统计值和平均值的程序。

根据我们现在所学的情况,我把这个工程中的代码分为三个源代码:

其一:主程序。就是main()函数所在的代码。这个源文件实现总的流程。我将该文件取为 main.cpp

其二:计算总和及计算平均值的代码。这个源文件负责用户计算过程,也包括每个过程所需输入输出。该文件将被存盘为mainfunc.cpp 意为主要功能。

其三: assifunc.cpp。表示辅助功能函数所在代码。它只提供一个函数:将用户输入的大写或小写的字母'Y''N' 确保转换为大写。这个函数将main()主函数内,判断用户是否继续时用到。

 

新CB新建一个控制台程序(如果你还开着上个程序,先选File | Close All关闭它)。CB会自动生成第一个文件,不过现在的名字为“Unit1.cpp"

接下来是一项新工作,我们来添加两人新的单元文件,即上面说的“其二”和“其三”。

CB6 :File | New | Unit;CB5:File | New Unit

请进行两次以上操作,CB将为我们生成新的两个单元文件:Unit2.cppUnit3.cpp。大家可以再试试 Ctrl + F6(注意,第一个单元文件:Unit1.cpp 没有配套的.h文件,所以不要在该文件里尝试Ctrl + F6)

然后选择File | Save All。全部存盘,最好不要存在CB默认的目录下。记得按以下关系重命名:

Unit1.cpp 存盘为 main.cpp

Unit2.cpp 存盘为 mainfunc.cpp

Unit3.cpp 存盘为 assifunc.cpp

至于总的工程,随你便,我图方便,还是叫:Project1.bpr

 

(现在我们第一次在一个工程中用到多个源文件。所以你得学会如何快速打开一个工程中某个源文件——当然,现在这三个文件都已经打开着,不过假设你有点事关闭CB,我们不希望下回打开这个工程时,你“找”不到第2和第3个文件了——请点击CB工具栏上的这个图标:,将出现源文件列表对话框,如左图)

 

 

接下来讲在这三个文件中,我们分别写些什么?大多数代码我们都已经在前面学过,所以我对代码的功能不作太多的解释。我们的重点是:三个源文件之间的代码如何实现“沟通”。

 

第一个文件:main.cpp 用来实现程序的主流程。

在 main.cpp 中的main()函数内,我们加入代码。

 

#include <iostream.h>

... ...

int main(int argc, char* argv[])

{

   char ch;

   int count; //求总和或平均值时,需要输入的成绩个数

 

   do

   {

      cout << "1)求总和" << endl;

      cout << "2)求平均" << endl;

    

      cout << "请选择(1 或 2)";

      cin >> ch;

     

      //输入有误,重输:

      if(ch != '1' && ch != '2')

      {

         cout << "输入有误,请重新输入!" << endl;

         continue;

      }

     

      cout << "请输入个数:";

      cin >> count;

     

      //根据用户的选择,调用不同函数:

      switch(ch)

      {

         case '1' :

            CalcTotal(count); //需要调用的函数之一

            break;

         case '2' :

            CalcAverage(count); //需要调用的函数之一

            break;

      }

        

      //是否继续:

      cout << "是否继续?(y/n)";

      cin >> ch;

     

      //确保转换为大写:

      ch = ToUpper(ch); //需要调用的函数之一

   }

   while(ch == 'Y');

  

   return 0;

}

 

代码中,红色部分的注释表明,主函数main()需要调用到三个自定义函数。但现在我们一个也没有定义。和往常把所有的函数定义在同一个代码文件中不同,今天我们需要把它们分开到不同的代码文件。

 

第二个文件:mainfunc.cpp 存放和计算有关的两个过程(函数)。

先看:CalcTotal()和CalcAverage()。这两个函数我们将在mainfunc.cpp文件内定义。你可能又忘了“定义”这个术语?呵,就是“实现”,更白点,就是在mainfunc.cpp文件内“写”这两个函数。

 

下面是mainfunc.cpp的内容。在我们输入以下代码时,mainfunc.cpp已经有了一些必要的内容,下面的代码,除了“#include ..”一行在文件最首外,其它均在原有内容之后添加。

 

#include <iostream.h> //在文件最首行

... ...

//---------------------------------------------------------------------------

//求总和的过程

//参数:n 用户需要输入的个数

void CalcTotal(int n)

{

   int num;

   int sum = 0;

  

   for(int i=0;i<n;i++)

   {

      cout << "请输入第" << i+1 <<"个整数:";

      cin >> num;

     

      sum += num;

   }

  

   cout << "总和为:" << sum << endl;

}

//---------------------------------------------------------------------------

//求平均值的过程

//参数:n 用户需要输入的个数

void CalcAverage(int n)

{

   int num;

   int sum = 0;

   float ave;

   for(int i=0;i<n;i++)

   {

      cout << "请输入第" << i+1 <<"个整数:";

      cin >> num;

  

      sum += num;

   }

   //注意不要除0出错:

   if( n >=0 )

   {

      ave = (float)sum / n;

      cout << "平均值:" << ave << endl;

   }

   else

   {

      cout << "个数为0,不能求平均。" << endl;

   }

}

//---------------------------------------------------------------------------

 

第三个文件:assifunc.cpp 用以存放辅助作用的函数,现在只有一个。

现在还差一个函数:ToUpper()。这个函数用来将用户输入的某个小写字母转换为大写。当然,如果用户输入的不是小写字母,那就不用转换。和上面的两个函数不同,它需要返回值。

我们把ToUpper()函数单独放在assifunc.cpp里。同样,下面的代码加在该文件中原有的代码之后。不过本文件不需要include <iostream.h> ,因为没有用到 cin,cout等。

 

//小写字母转换为大写

//参数: c 待转换的字符

//返回值: 转换后的字符,如果原字符不是小写字母,则为原字符

char ToUpper(char c)

{

   int ca = 'A' - 'a'; //大写字母和小写字母之间差距多少?

   if(c >= 'a' && c <= 'z')

      c += ca;
 

   return c;

}

 

至此,所有自定义函数都已完成定义(实现),而三个文件的主要内容也以确定。让我们看看示意图:

 

 

main.cpp中的main()函数调用了三个函数。回忆我们学习过的“如何调用函数”的知识,当前代码在调用一个函数时,必须能“看到”这个函数。尽管CalcTotal()、CalcAverage()、ToUpper()三个函数所在文件都在同一工程里,但是在main.cpp里的代码,还是看不到它们。想一想我们以前说的“请修理工”的比喻。现在情况是:在你所住的小区,甚至就是同一楼道里,就有一个电视修理工,但可惜你们互不认识,所以当你电视坏了,想“调用”一个修理工时,你还是找不到修理工。哎!要是有它的名片就好了。

让我们试试看,按Ctrl + F9,编辑该工程。出错!

正好是三个错。分别告诉我们调用了三个没有定义的函数(Call to undefined function ...)。

 

(如果你出现的是一堆错,那有可能是你没有在前两个文件内最首行写:

“#include <iostream.h>”

或者是你有些代码输入有误。)

 

如何消除这三个错?两种方法。

第一种方法就是以前我们在讲“如何调用函数”的时候所说的,直接在调用直接声明要调用的函数。这里写出代码,算做是一次复习,然后我们将讲该方法不好之处。

 

在 main.cpp 的 main()函数之前加入如下三行函数声明:

 

void CalcTotal(int n);

void CalcAverage(int n);

char ToUpper(char c);

 

int main(int argc, char* argv[])

{

   ... ...

}

 

(上例中,尽管你可以将三行函数声明写在 main()函数体内,但不建议这样做)。

 

如果你一切输入正确的话,现在按Ctrl + F9 或 F9将可以完成编译或运行。

对于现在这个工程,这种方法确实也不能指责它有何不利之处。问题在于,如果我们还有其它文件中代码需要调用到这三个函数,我们就不得不在其它文件中也一一写上这三行声明。所以另一种方法是:把源文件中需要对外“共享”的函数声明统一写到某个头文件,然后凡是需要用到的其它文件,直接使用“#include"语句来包含该头文件,从而获得这些函数声明。

 

14.3 如何写头文件

 

在CB中,如果你通过上小节的方法新建个单元文件,那么CB将自动同时生成源文件和头文件。其实在CB里,源文件和头文件合称为单元文件,它们有相同的文件名,而扩展名一者为.cpp,另一为.h。

 

14.3.1 在头文件内加入函数声明

 

头文件:mainfunc.h

CalcTotal()和CalcAverage()函数定义在 mainfunc.cpp文件里,所以它们的声明最好写在对应的头文件mainfunc.h内。

下面我们就来看如何在头文件mainfunc.h 内增加函数声明。

一开始,头文件内有以下这些代码。另外,我增加了一行用于标明我们新加的代码应写在哪里。

//---------------------------------------------------------------------------

#ifndef mainfuncH

#define mainfuncH

//---------------------------------------------------------------------------

/* !!!头文件中,我们新增的代码必须写在此处!!!  */

#endif

 

和源文件中新增代码添加在最后不一样,头文件中新加代码 必须在#endif之前插入。所以本例中,加完函数声明的代码应如其下所示。(另外,CB总是在头文件的第二行留了一行空白行,我不知道它这是有意还是无意。总之这里是我们写本文件总体注释的好地方。记住,头文件像名片,用于让别人看,很有必要写得详细点)

//---------------------------------------------------------------------------
   //主要操作函数

#ifndef mainfuncH

#define mainfuncH

//---------------------------------------------------------------------------

//计算总和:

void CalcTotal(int n);

//计算平均值:

void CalcAverage(int n);

//---------------------------------------------------------------------------

#endif

 

这就是“在头文件中声明函数”的整个过程。下面是另外一个头文件。

 

头文件:mainfunc.h

//---------------------------------------------------------------------------

//辅助操作函数

#ifndef assifuncH

#define assifuncH

//---------------------------------------------------------------------------

//将字符转换成大写

char ToUpper(char c);

#endif

 

今天我们学的是如何在头文件内声明函数,以后我们需要在头文件内声明变量,或者定义新的数据类型,它们都一样需要在上述的#endif之前加入。

 

14.3.2 最常见的预编译语句

 

现在来解释这三行话:

#ifndef mainfuncH

#define mainfuncH

 

#endif

 

中间的 #define mainfuncH 我们有点脸熟。在第五章《变量与常量》中,我们讲过用宏表示常数。语法为:

#define 宏名称 宏值

 

比如,定义一个∏值:

#define PAI 3.14159

 

这里我们学的是宏定义的另一种用法:仅仅定义一个宏,不需要给出它的值,语法为:

 

#define 宏名称

 

比如:#define mainfuncH

 

定义了一个宏:mainfuncH。如果你无法理解“宏”这个词,不妨就当把它解释成“记号”。即编译器通过该语句,做了一个记号,记号名称为:mainfucH。

这么做的作用是什么呢?我们继续看上下文。

 

#ifndef 中, if 是“如果”,n 是 no,即“还没有”,def是 define,即“定义”,那么:

#ifndef mainfuncH 意为:“如果还没有定义mainfuncH这个宏”,那么……

那么之后做什么呢?就是一直到 #endif之间的语句。

 

总的再来看一遍:

 

mainfunc.h 中的主要内容:

 

#ifndef mainfuncH

#define mainfuncH

 

void CalcTotal(int n);

void CalcAverage(int n);

 

#endif

 

当编译第一次编译mainfunc.h文件时,宏 mainfuncH 还没有定义,因些,编译器通过对 #define mainfuncH的编译而产生了宏 mainfuncH。当编译器第二次编译到 mainfunc.h文件时,宏mainfuncH 已经存在,所以该头文件被直接跳过,不会重复处理该头文件中内容,比如上面的两个函数声明。

你可能会问两个问题:第一,为什么编译器可能多次编译到同一个头文件?第二,为什么源文件,比如mainfunc.cpp就不需要用到#ifndef... #endif?

这两个问题只要回答了其中一个,另一个也就自然消解。

 

这是由头文件特性所决定的。头文件是用来被别人包含(include)的。谁都可以指定要包含某一头文件,这样就可能造成对该头文件的重复包含。

假设有头文件head.h。如果A文件包含了head.h,而B文件也包含了head.h,那么编译器不会在编译A和编译B时,都要对该头文件尝试编译一次。

另外,头文件本身也可以包含另一个头文件,这种情况下,各文件之间互相嵌套包含的情况就更多了。

 

源文件(.c或.cpp)尽管可以,但一般不被用来被别的文件包含,所以不需要在源文件中加这些语句。当然,如果需要,你也可以源文件中使用 #ifndef...#endif。

 

每生成一个头文件,包括在重命名它时,CB会为我们取好该头文件中,上述的宏名称,它取该头文件的全小写文件名,加上一个大写的‘H’字母,比如: "mainfuncH"。请大家不要改变该宏的名称,以免出错。

 

除了 #ifndef ... #endif 语句外,还有它的相反逻辑的语句: 

#ifdef ... #endif 它是在如果有定义某个宏,那么,编译将继续其后的语句。

 

另外就像有if 语句,还有 if...else...语句一样,有 #ifdef ... #endif,也就还有这个语句:

#ifdef

... ...

#else

... ...

#endif

 

不过这些都和我们这里的头文件相关不大,我们暂时不讲。最后我们来解释一个名词“预编译”。

编译器在编译代码时,至少需要两遍的编译处理,其中第一次,就是专门用于处理所有以 #开头的语句,如上述的#ifndef...#endif、#define等等。这一遍处理,我们称为预编译

 

14.4 如何使用头文件

 

事实上我们经常在使用头文件。不过,以前我们一直在使用别人的头文件,今天是第一次使用我们自已的写的头件。

以前,我们几乎每个例子,包括今天的例子中,都需要在源文件的顶部写上一行:

#include <iostream.h>

或者:

#include <stdio.h>

 

iostream.h和stdio.h都是CB提供给我们的头文件。这些头文件随CB安装时,被保存在固定的文件夹内

今天的例子中,main.cpp 需要使用到在 mainfunc.h 和 assifunc.h。这是我们自己写的头文件,它们保存在我们自定的文件夹中

包含自已写的头文件,和包含CB提供的头文件并无多大区别。

请在 main.cpp 代码顶部,加入以下黑体部分:

 

#include <iostream.h>

#include "mainfunc.h"

#include "assifunc.h"

//---------------------------------------------------------------------------

 

二者的细小区别是,包含CB提供的头文件时,用尖括号<>;而包含我们自已的头文件时,使用双引号“”。CB据此判断如何找到指定的头文件。<>相当于告诉CB,这是你自已提供的头文件,到你安装时的头文件目录下找去吧,而“”则是告诉CB,是这我自已写的头文件,请首先到我当前工程所在目录下查找,如果找不到,再到别的可能的头文件目录下找这个文件。(别的还有什么目录可能存放当前工程的头文件呢? 稍后会讲。)

 

现在,我们让main.cpp包含了它想要的头文件,头文件内有它所需函数的正确声明,那么main.cpp中原来的这三行就多余了:

void CalcTotal(int n);

void CalcAverage(int n);

char ToUpper(char c);

请删除。 然后,按F9,程序正确编译,然后运行。这里我们不关心它的运行结果。

 

现在来看一眼在CB中如何设定某一工程的头文件目录。

必须先说清楚,在相当长的一段时间内,我们并不需要去进行此设置。对于CB提供的头文件,它们固定就在CB安装时自动存储的某些目录下,你只要记得包含这些头文件时,使用<>即可。对于我们自已写的头文件,我们都把它们和工程文件存放在同一目录下,暂时还没有什么理由需要把某个或某些头文件“扔”在别的目录下。所以,记住在包含 自己的头文件时,对使用“”即可。

首先保证当前CB正打开着上面的那个例子工程。

然后,主菜单: Project | Options 或按 Ctrl + Shift + F11,打开“工程设置(Project Options)”对话框,并切换到“目录与条件(Directories/Conditionals)”页:

 

图中有关目录的设置共六行,我们说其中常用的四行。

最主要的,当然是今天所说的“头文件目录”。当 CB 编译时,当它遇到这样一行:

 

#include "xxxx.h"

那么,它必须找到文件xxxx.h。如果,你写的是绝对路径:#include "c:\abc\123\xxxx.h",那自然没有查找这一说,不过我们不会喜欢这样写程序,因为我们不希望源代换个位置就得一一去改那些绝对路径。事实上我们不可能把头文件到处放,总是固定那么几个目录,绝大多数就一个:所有源文件和头文件都在当前工程所在目录下。这里可以添加,删除,修改一些目录位置,CB将按本设置中的目录次序去查找头文件。

请点击“头文件目录”右边,带 "..."的小按钮。出来一个新的对话框:

($BCB) 表示Borland C++Builder 的安装目录。

 

在这里,你可以修改(Replace),增加(Add),删除(Delete),调整次序(向上和向下的蓝箭头)各个头文件目录。CB6还提供了对无效目录的判断,如果列表中列出的某个目录实际上并不存在对应的文件夹,则将以灰色显示,并且可以用"Delete Invalid Paths"按钮全部删除。

 

我们什么也不用做。点 Cancel, 放弃就是。

 

其它目录的设定,操作完全一样。

 

关于在工程中如何使用头文件,我们就说这些了。

 

14.5 变量在多个源文件之间的使用

前面讲的是,通过在头文件中声明函数,可以达到让这个函数被其它文件共用的作用。同样地,变量也可以在多个源文件之间“共享”。下面我们就要讲,如何通过声明变量,以达到让其它文件共用 同一个变量的目的。

 

14.5.1 变量声明

先说说“声明变量”。好像以前的课程只教过我们:定义变量,定义函数,声明函数,没有讲过“声明变量”啊?

 

我们很早就学过如何定义一个变量。(5.1.2

 

比如:

 

//定义一个整型变量:

int age;

 

//然后,在后面的某处代码中使用这个变量:

... ...

age = 18;

cout << age << endl;

... ...

 

 

但是,我们没有遇到过如何声明一个变量。这是因为,定义一个变量的同时,也就声明了一个变量;绝大多数的时候,我们都是可以需要某个变量时,直接定义它。

 

今天的情况有点不一样。我们需要在某个源文件中定义一个变量,然后,在另外一个源文件中使用这个变量。

 

仍以前面 age 变量为例:

 

//我们在 A.cpp 文件中定义了这个变量:

int age;

 

//然后,在 B.cpp 文件中要使用这个变量:

age = 18;

cout << age << endl;

 

问题就出来了:在编译 B.cpp 文件时,编译器会说:“age 这个变量没有定义啊?”——当编译器在编译 B.cpp时,它并不懂得去A.cpp里去找有关 age 的定义。

那么,能不能在B.cpp里再定义一次age变量呢?

 

//A.cpp文件中:

int age;

 

//B.cpp文件中:

int age;

age = 18;

cout << age << endl;

 

这样,单独编译A.cpp,或B.cpp,都可以通过。但一旦要编译整个工程,编译器又会报错:“怎么有两个 age 变量的定义啊”?

不要嘲笑编译器为什么这么笨笨。C,C++是一门严谨的的计算机语言,我们不能指望编译器会“智能”地猜测程序员的企图。

 

解决方法是,仅在一处定义变量,别的代码需要用到该变量,但无法看到前面的定义时,则改为“声明变量”。

 

声明变量的语法:

extern 数据类型 变量名

 

和定义变量的语法相比,多了前面的 extern 这个关键字。

 

extern 意为“外来的”···它的作用在于告诉编译器:有这个变量,它可能不存在当前的文件中,但它肯定要存在于工程中的某一个源文件中。

 

这就好像:微软公司在北京招人,微软的报名方法是:在北京的应聘者必须当天去面试,而外地应聘者则通过发e-mail先报名,然后以后再去面试。 在C,C++里,不处于当前源文件中的变量被称为外部变量。比喻中,发e-mail就相当于外部变量在某一个源中写个声明。声明什么呢?就是声明“我存在啊!虽然我现在不在这里,但是我真的存在!”

 

上例中,正确的代码应该这样写:

 

//A.cpp文件中:

int age;

 

//B.cpp文件中:

extern int age;

age = 18;

cout << age << endl;

 

变量 age 是在A.cpp文件里定义的,当B.cpp文件要使用它时,必须先声明。这就是我们讲半天课的核心。

 

(有些教材并不认为 extern int age; 是在声明一个变量,它们把这也称为是“定义变量”的一种,只不过它是定义了一个名部变量。我认为这样认为不好,一来它造成了学习者认为“变量可以重复定义”的错误认为,二来它也造成了不统一,函数有“定义”和“声明”两种形式,而变量都没有“声明”。

可能你会说,现在也不统一啊?函数声明没有“extern",而变量却需要?呵呵,其实恰恰相反。函数声明本来也是需要一个“extern”的,比如:

 

extern void CalcTotal(int n);

你在代码里这样完全正确!只不过由于函数声明和函数定义的格式区别很大,(声明没有函数体,定义则必须有函数体),所以这个extern就算不写,也可以让编译器认出来它是一个“声明”。结果就规定可以不写"extern"了。

而变量呢?

extern int age;     //这是声明

int age;            //这是定义

你看看,不写"extern"可不行! 就靠它来区分是定义还是声明了。

如此而已。)

 

14.5.2 多个文件中共享变量的实例

 

做一个最简单的例子。新建一个控制台工程。然后再加一个单元文件。把工程存盘为Project1.bpr,把两个源文件分别存盘为Unit1.cpp、Unit2.cpp (即,都采用默认文件名)。

 

程序内容是:在 Unit1.cpp 内定义一个变量,即:int age,并且,要求用户输入。在Unit2.cpp里,写一函数,OutputAgeText(),它根据 age 的值, 输出一些文本。

 

请问,变量 age 在哪里定义?又在哪里声明?

 

定义指定是在 Unit1.cpp 文件里,而声明,则可以在 Unit2.cpp内直接声明(如上例中的红色代码),也可以是在头文件 Unit1.h 里声明,然后在 Unit2.cpp 内使用 include 来包含 Unit1.h。 事实让,声明也可以放在 Unit2.h内。只要能让Unit2.cpp“看到”这个声明即可。这一点和函数的声明一个道理。

 

我们采用放在Unit2.cpp中的方法,该方法所需代码如下:

 

//Unit1.cpp 内的主要代码:

 

#include <iostream.h>

#include <conio.h>

#pragma hdrstop

#include "Unit2.h"

... ...

//---------------------------------------------------------------------------

int age; //全局变量,年龄

#pragma argsused

int main(int argc, char* argv[])

{

   cout << "请输入您的年龄:" ;

   cin >> age;

 

   //调用Unit2.cpp中的函数,该函数根据age,作出相应输出

   OutAgeText();  

  

   getch();

   return 0;

}

//---------------------------------------------------------------------------

 

 

//Unit2.cpp 中的主要代码:

#include <iostream.h>

... ...

extern int age;  //需要Unit1.cpp内定义的变量

 

//报名参加“没有弯路”的学员各行业,年龄段也各处不同,在此,我们用这个函数作为共勉!

void OutAgeText()

{

   if(age < 15)

      cout  << "计算机要从娃娃抓起!" << endl;

   else if(age < 25)

      cout << "青春年华,正是学习编程的黄金时代!" << endl;

   else if(age < 35)

      cout << "学习编程需要热情,更需要理性!我和您一样,也在这个年龄段!"<< endl;

   else if(age < 45)

      cout << "活到老,学到老!何况您还未老。杀毒王王江民,不也在这个时候才开始学习电脑吗?" << endl;

   else

      cout <<  "前辈,只要您像学书法一样潜心学编程!您一定会有收获!" << endl;

}

//---------------------------------------------------------------------------
 

 

//Unit2.h 的主要代码:

 

//声明OutAgeText()函数,供Unit1.cpp使用

void OutAgeText();

//---------------------------------------------------------------------------
 

请大家完成这个工程,直到能正确运行。

 

现在我们得到一个印象:当我们定义了一个函数或变量之后,似乎所有的源代码文件中都可以使用它,只要你在使用之前写一下相应的声明。

这样会不会带来麻烦了?想象一下,你在A文件定义了一个变量: int i, 那么以后你在别的文件里就不能再定义这个变量了!原因前面已经说过,编译器(或链接器)会说有两个变量重名。函数也一样,尽管它有重载机制,便那也只能是有限制地允许函数重名。

 

事实上,上例中的 int age 是一个全局变量。关于“全局”的解释,需要引起C,C++程序的另一话题:作用范围。这是下一章的内容。在那一章里,我们将看到,大部分变量只在它一定的作用范围内“生存”,不同的作用范围的变量就可以毫无障碍地重名了。

休息休息(该点眼药水了···),然后学习本章附加一节。

 

14.6 附:如何单独生成一个头文件

 

在 14.5.2 试一试在多个文件中共享变量 小节中,我们说,变量的声明可以像函数声明一样放在某个头文件中,然后使用者通过 include语句包含该头文件,从而获得变量的声明。

 

如果你想自已再写一次那个例子(建议),并且改用上述的方法,那么你可能会发现一个小小的故障: Unit1.cpp竟然没有配套的头文件?

 

CB 在生成一个空白的控制台工程时,会自动主函数(main())文件,即默认文件名为:Unit1.cpp的源文件,但它没有为该文件生成对应Unit1.h。

这是因为作为控制台程序,一般都是小程序,一个源文件即可全部解决。

 

现在,作为一个课程例子,我们要来讲讲如何为Unit1.cpp生成一个头文件。

 

CB6中选择菜单:New | Other,CB5中选择 New...,或直接点“”,出现New Item...对话框,选择 New页内的 Header File:

 

确认后,CB生成头文件: File1.h,其内容为空白,我们需要手工加入以下内容:

 

#ifndef unit1H

#define unit1H

 

#endif

 

然后,选择主菜单 File | Save As... 将其另存为: Unit1.h。 完后成,在Unit1.cpp或Unit1.h内,按Ctrl + F6,你就可以看到二者之间的切换了。

 

下一章见!