返回介绍

9.1 单独编译

发布于 2024-10-08 23:14:07 字数 5007 浏览 0 评论 0 收藏 0

和 C 语言一样,C++也允许甚至鼓励程序员将组件函数放在独立的文件中。第 1 章介绍过,可以单独编译这些文件,然后将它们链接成可执行的程序。(通常,C++编译器既编译程序,也管理链接器。)如果只修改了一个文件,则可以只重新编译该文件,然后将它与其他文件的编译版本链接。这使得大程序的管理更便捷。另外,大多数 C++环境都提供了其他工具来帮助管理。例如,UNIX 和 Linux 系统都具有 make 程序,可以跟踪程序依赖的文件以及这些文件的最后修改时间。运行 make 时,如果它检测到上次编译后修改了源文件,make 将记住重新构建程序所需的步骤。大多数集成开发环境(包括 Embarcadero C++ Builder、Microsoft Visual C++、Apple Xcode 和 Freescale CodeWarrior)都在 Project 菜单中提供了类似的工具。

现在看一个简单的示例。我们不是要从中了解编译的细节(这取决于实现),而是要重点介绍更通用的方面,如设计。

例如,假设程序员决定分解程序清单 7.12 中的程序,将支持函数放在一个独立的文件中。清单 7.12 将直角坐标转换为极坐标,然后显示结果。不能简单地以 main( ) 之后的虚线为界,将原来的文件分为两个。问题在于,main( ) 和其他两个函数使用了同一个结构声明,因此两个文件都应包含该声明。简单地将它们输入进去无疑是自找麻烦。即使正确地复制了结构声明,如果以后要作修改,则必须记住对这两组声明都进行修改。简而言之,将一个程序放在多个文件中将引出新的问题。

谁希望出现更多的问题呢?C 和 C++的开发人员都不希望,因此他们提供了#include 来处理这种情况。与其将结构声明加入到每一个文件中,不如将其放在头文件中,然后在每一个源代码文件中包含该头文件。这样,要修改结构声明时,只需在头文件中做一次改动即可。另外,也可以将函数原型放在头文件中。因此,可以将原来的程序分成三部分。

  • 头文件:包含结构声明和使用这些结构的函数的原型。
  • 源代码文件:包含与结构有关的函数的代码。
  • 源代码文件:包含调用与结构相关的函数的代码。

这是一种非常有用的组织程序的策略。例如,如果编写另一个程序时,也需要使用这些函数,则只需包含头文件,并将函数文件添加到项目列表或 make 列表中即可。另外,这种组织方式也与 OOP 方法一致。一个文件(头文件)包含了用户定义类型的定义;另一个文件包含操纵用户定义类型的函数的代码。这两个文件组成了一个软件包,可用于各种程序中。

请不要将函数定义或变量声明放到头文件中。这样做对于简单的情况可能是可行的,但通常会引来麻烦。例如,如果在头文件包含一个函数定义,然后在其他两个文件(属于同一个程序)中包含该头文件,则同一个程序中将包含同一个函数的两个定义,除非函数是内联的,否则这将出错。下面列出了头文件中常包含的内容。

  • 函数原型。
  • 使用#define 或 const 定义的符号常量。
  • 结构声明。
  • 类声明。
  • 模板声明。
  • 内联函数。

将结构声明放在头文件中是可以的,因为它们不创建变量,而只是在源代码文件中声明结构变量时,告诉编译器如何创建该结构变量。同样,模板声明不是将被编译的代码,它们指示编译器如何生成与源代码中的函数调用相匹配的函数定义。被声明为 const 的数据和内联函数有特殊的链接属性(稍后将介绍),因此可以将其放在头文件中,而不会引起问题。

程序清单 9.1、程序清单 9.2 和程序清单 9.3 是将程序清单 7.12 分成几个独立部分后得到的结果。注意,在包含头文件时,我们使用“coordin.h”,而不是<coodin.h>。如果文件名包含在尖括号中,则 C++编译器将在存储标准头文件的主机系统的文件系统中查找;但如果文件名包含在双引号中,则编译器将首先查找当前的工作目录或源代码目录(或其他目录,这取决于编译器)。如果没有在那里找到头文件,则将在标准位置查找。因此在包含自己的头文件时,应使用引号而不是尖括号。

图 9.1 简要地说明了在 UNIX 系统中将该程序组合起来的步骤。注意,只需执行编译命令 CC 即可,其他步骤将自动完成。g++和 gpp 命令行编译器以及 Borland C++命令行编译器(bcc32.exe)的行为类似。Apple Xcode、Embarcadero C++ Builderr 和 Microsoft Visual C++基本上执行同样的步骤,但正如第 1 章介绍的,启动这个过程的方式不同——使用能够创建项目并将其与源代码文件关联起来的菜单。注意,只需将源代码文件加入到项目中,而不用加入头文件。这是因为#include 指令管理头文件。另外,不要使用#include 来包含源代码文件,这样做将导致多重声明。

警告:

在 IDE 中,不要将头文件加入到项目列表中,也不要在源代码文件中使用#include 来包含其他源代码文件。

程序清单 9.1 coordin.h

图 9.1 在 UNIX 系统中编译由多个文件组成的 C++程序

头文件管理

在同一个文件中只能将同一个头文件包含一次。记住这个规则很容易,但很可能在不知情的情况下将头文件包含多次。例如,可能使用包含了另外一个头文件的头文件。有一种标准的 C/C++技术可以避免多次包含同一个头文件。它是基于预处理器编译指令#ifndef(即 if not defined)的。下面的代码片段意味着仅当以前没有使用预处理器编译指令#define 定义名称 COORDINH时,才处理#ifndef 和#endif 之间的语句:

通常,使用#define 语句来创建符号常量,如下所示:

但只要将#define 用于名称,就足以完成该名称的定义,如下所示:

程序清单 9.1 使用这种技术是为了将文件内容包含在#ifndef 中:

编译器首次遇到该文件时,名称 COORDINH没有定义(我们根据 include 文件名来选择名称,并加上一些下划线,以创建一个在其他地方不太可能被定义的名称)。在这种情况下,编译器将查看#ifndef 和#endif 之间的内容(这正是我们希望的),并读取定义 COORDINH的一行。如果在同一个文件中遇到其他包含 coordin.h 的代码,编译器将知道 COORDINH已经被定义了,从而跳到#endfi 后面的一行上。注意,这种方法并不能防止编译器将文件包含两次,而只是让它忽略除第一次包含之外的所有内容。大多数标准 C 和 C++头文件都使用这种防护(guarding)方案。否则,可能在一个文件中定义同一个结构两次,这将导致编译错误。

程序清单 9.2 file1.cpp

程序清单 9.3 file2.cpp

将这两个源代码文件和新的头文件一起进行编译和链接,将生成一个可执行程序。下面是该程序的运行情况:

顺便说一句,虽然我们讨论的是根据文件进行单独编译,但为保持通用性,C++标准使用了术语翻译单元(translation unit),而不是文件;文件并不是计算机组织信息时的唯一方式。出于简化的目的,本书使用术语文件,您可将其解释为翻译单元。

多个库的链接

C++标准允许每个编译器设计人员以他认为合适的方式实现名称修饰(参见第 8 章的旁注“什么是名称修饰”),因此由不同编译器创建的二进制模块(对象代码文件)很可能无法正确地链接。也就是说,两个编译器将为同一个函数生成不同的修饰名称。名称的不同将使链接器无法将一个编译器生成的函数调用与另一个编译器生成的函数定义匹配。在链接编译模块时,请确保所有对象文件或库都是由同一个编译器生成的。如果有源代码,通常可以用自己的编译器重新编译源代码来消除链接错误。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文