Team LiB
Previous Section Next Section

2.9. Writing Our Own Header Files

2.9. 编写自己的头文件

We know from Section 1.5(p. 20)that ordinarily class definitions go into a header file. In this section we'll see how to define a header file for the Sales_item class.

我们已经从第 1.5 节了解到,一般类定义都会放入头文件。在本节中我们将看到怎样为 Sales_item 类定义头文件。

In fact, C++ programs use headers to contain more than class definitions. Recall that every name must be declared or defined before it is used. The programs we've written so far handle this requirement by putting all their code into a single file. As long as each entity precedes the code that uses it, this strategy works. However, few programs are so simple that they can be written in a single file. Programs made up of multiple files need a way to link the use of a name and its declaration. In C++ that is done through header files.

事实上,C++ 程序使用头文件包含的不仅仅是类定义。回想一下,名字在使用前必须先声明或定义。到目前为止,我们编写的程序是把代码放到一个文件里来处理这个要求。只要每个实体位于使用它的代码之前,这个策略就有效。但是,很少有程序简单到可以放置在一个文件中。由多个文件组成的程序需要一种方法连接名字的使用和声明,在 C++ 中这是通过头文件实现的。

To allow programs to be broken up into logical parts, C++ supports what is commonly known as separate compilation. Separate compilation lets us compose a program from several files. To support separate compilation, we'll put the definition of Sales_item in a header file. The member functions for Sales_item, which we'll define in Section 7.7 (p. 258), will go in a separate source file. Functions such as main that use Sales_item objects are in other source files. Each of the source files that use Sales_item must include our Sales_item.h header file.

为了允许把程序分成独立的逻辑块,C++ 支持所谓的分别编译。这样程序可以由多个文件组成。为了支持分别编译,我们把 Sales_item 的定义放在一个头文件里面。我们后面在第 7.7 节中定义的 Sales_item 成员函数将放在单独的源文件中。像 main 这样使用 Sales_item 对象的函数放在其他的源文件中,任何使用 Sales_item 的源文件都必须包含 Sales_item.h 头文件。

2.9.1. Designing Our Own Headers

2.9.1. 设计自己的头文件

A header provides a centralized location for related declarations. Headers normally contain class definitions, extern variable declarations, and function declarations, about which we'll learn in Section 7.4 (p. 251). Files that use or define these entities include the appropriate header(s).

头文件为相关声明提供了一个集中存放的位置。头文件一般包含类的定义、extern 变量的声明和函数的声明。函数的声明将在第 7.4 节介绍。使用或定义这些实体的文件要包含适当的头文件。

Proper use of header files can provide two benefits: All files are guaranteed to use the same declaration for a given entity; and should a declaration require change, only the header needs to be updated.

头文件的正确使用能够带来两个好处:保证所有文件使用给定实体的同一声明;当声明需要修改时,只有头文件需要更新。

Some care should be taken in designing headers. The declarations in a header should logically belong together. A header takes time to compile. If it is too large programmers may be reluctant to incur the compile-time cost of including it.

设计头文件还需要注意以下几点:头文件中的声明在逻辑上应该是统一的。编译头文件需要一定的时间。如果头文件太大,程序员可能不愿意承受包含该头文件所带来的编译时代价。

To reduce the compile time needed to process headers, some C++ implementations support precompiled header files. For more details, consult the reference manual of your C++ implementation.

为了减少处理头文件的编译时间,有些 C++ 的实现支持预编译头文件。欲进一步了解详细情况,请参考你的 C++ 实现的手册。

Compiling and Linking Multiple Source Files

编译和链接多个源文件

To produce an executable file, we must tell the compiler not only where to find our main function but also where to find the definition of the member functions defined by the Sales_item class. Let's assume that we have two files: main.cc, which contains the definition of main, and Sales_item.cc, which contains the Sales_item member functions. We might compile these files as follows:

要产生可执行文件,我们不但要告诉编译器到哪里去查找 main 函数,而且还要告诉编译器到哪里去查找 Sales_item 类所定义的成员函数的定义。假设我们有两个文件:main.cc 含有 main 函数的定义,Sales_item.cc 含有 Sales_item 的成员函数。我们可以按以下方式编译这两个文件:

     $ CC -c main.cc Sales_item.cc # by default generates a.exe
                                   # some compilers generate a.out

     # puts the executable in main.exe
     $ CC -c main.cc Sales_item.cc -o main

where $ is our system prompt and # begins a command-line comment. We can now run the executable file, which will run our main program.

其中 $ 是我们的系统提示符,# 开始命令行注释。现在我们可以运行可执行文件,它将运行我们的 main 程序。

If we have only changed one of our .cc source files, it is more efficient to recompile only the file that actually changed. Most compilers provide a way to separately compile each file. This process usually yields a .o file, where the .o extension implies that the file contains object code.

如果我们只是修改了一个 .cc 源文件,较有效的方法是只重新编译修改过的文件。大多数编译器都提供了分别编译每一个文件的方法。通常这个过程产生 .o 文件,.o 扩展名暗示该文件含有目标代码。

The compiler lets us link object files together to form an executable. On the system we use, in which the compiler is invoked by a command named CC, we would compile our program as follows:

编译器允许我们把目标文件链接在一起以形成可执行文件。我们所使用的系统可以通过命令名 CC 调用编译。因此可以按以下方式编译程序:

     $ CC -c main.cc              # generates main.o
     $ CC -c Sales_item.cc        # generates Sales_item.o
     $ CC main.o Sales_item.o     # by default generates a.exe;
                                  # some compilers generate a.out

     # puts the executable in main.exe
     $ CC main.o Sales_item.o -o main

You'll need to check with your compiler's user's guide to understand how to compile and execute programs made up of multiple source files.

你需要检查所用编译器的用户手册,了解如何编译和执行由多个源文件组成的程序。

Many compilers offer an option to enhance the error detection of the compiler. Check your compiler's user's guide to see what additional checks are available.

许多编译器提供了增强其错误检测能力的选项。查看所用编译器的用户指南,了解有哪些额外的检测方法。

Headers Are for Declarations, Not Definitions
头文件用于声明而不是用于定义

When designing a header it is essential to remember the difference between definitions, which may only occur once, and declarations, which may occur multiple times (Section 2.3.5, p. 52). The following statements are definitions and therefore should not appear in a header:

当设计头文件时,记住定义和声明的区别是很重要的。定义只可以出现一次,而声明则可以出现多次(第 2.3.5 节)。下列语句是一些定义,所以不应该放在头文件里:

     extern int ival = 10;      // initializer, so it's a definition
     double fica_rate;          // no extern, so it's a definition

Although ival is declared extern, it has an initializer, which means this statement is a definition. Similarly, the declaration of fica_rate, although it does not have an initializer, is a definition because the extern keyword is absent. Including either of these definitions in two or more files of the same program will result in a linker error complaining about multiple definitions.

虽然 ival 声明为 extern,但是它有初始化式,代表这条语句是一个定义。类似地,fica_rate 的声明虽然没有初始化式,但也是一个定义,因为没有关键字 extern。同一个程序中有两个以上文件含有上述任一个定义都会导致多重定义链接错误。

Because headers are included in multiple source files, they should not contain definitions of variables or functions.

因为头文件包含在多个源文件中,所以不应该含有变量或函数的定义。

There are three exceptions to the rule that headers should not contain definitions: classes, const objects whose value is known at compile time, and inline functions (Section 7.6 (p. 256) covers inline functions) are all defined in headers. These entities may be defined in more than one source file as long as the definitions in each file are exactly the same.

对于头文件不应该含有定义这一规则,有三个例外。头文件可以定义类、值在编译时就已知道的 const 对象和 inline 函数(第 7.6 节介绍 inline 函数)。这些实体可在多个源文件中定义,只要每个源文件中的定义是相同的。

These entities are defined in headers because the compiler needs their definitions (not just declarations) to generate code. For example, to generate code that defines or uses objects of a class type, the compiler needs to know what data members make up that type. It also needs to know what operations can be performed on these objects. The class definition provides the needed information. That const objects are defined in a header may require a bit more explanation.

在头文件中定义这些实体,是因为编译器需要它们的定义(不只是声明)来产生代码。例如:为了产生能定义或使用类的对象的代码,编译器需要知道组成该类型的数据成员。同样还需要知道能够在这些对象上执行的操作。类定义提供所需要的信息。在头文件中定义 const 对象则需要更多的解释。

Some const Objects Are Defined in Headers
一些 const 对象定义在头文件中

Recall that by default a const variable (Section 2.4, p. 57) is local to the file in which it is defined. As we shall now see, the reason for this default is to allow const variables to be defined in header files.

回想一下,const 变量(第 2.4 节)默认时是定义该变量的文件的局部变量。正如我们现在所看到的,这样设置默认情况的原因在于允许 const 变量定义在头文件中。

In C++ there are places where constant expression (Section 2.7, p. 62) is required. For example, the initializer of an enumerator must be a constant expression. We'll see other cases that require constant expressions in later chapters.

在 C++ 中,有些地方需要放置常量表达式(第 2.7 节)。例如,枚举成员的初始化式必须是常量表达式。在以后的章节中将会看到其他需要常量表达式的例子。

Generally speaking, a constant expression is an expression that the compiler can evaluate at compile-time. A const variable of integral type may be a constant expression when it is itself initialized from a constant expression. However, for the const to be a constant expression, the initializer must be visible to the compiler. To allow multiple files to use the same constant value, the const and its initializer must be visible in each file. To make the initializer visible, we normally define such consts inside a header file. That way the compiler can see the initializer whenever the const is used.

一般来说,常量表达式是编译器在编译时就能够计算出结果的表达式。当 const 整型变量通过常量表达式自我初始化时,这个 const 整型变量就可能是常量表达式。而 const 变量要成为常量表达式,初始化式必须为编译器可见。为了能够让多个文件使用相同的常量值,const 变量和它的初始化式必须是每个文件都可见的。而要使初始化式可见,一般都把这样的 const 变量定义在头文件中。那样的话,无论该 const 变量何时使用,编译器都能够看见其初始化式。

However, there can be only one definition (Section 2.3.5, p. 52) for any variable in a C++ program. A definition allocates storage; all uses of the variable must refer to the same storage. Because, by default, const objects are local to the file in which they are defined, it is legal to put their definition in a header file.

但是,C++ 中的任何变量都只能定义一次(第 2.3.5 节)。定义会分配存储空间,而所有对该变量的使用都关联到同一存储空间。因为 const 对象默认为定义它的文件的局部变量,所以把它们的定义放在头文件中是合法的。

There is one important implication of this behavior. When we define a const in a header file, every source file that includes that header has its own const variable with the same name and value.

这种行为有一个很重要的含义:当我们在头文件中定义了 const 变量后,每个包含该头文件的源文件都有了自己的 const 变量,其名称和值都一样。

When the const is initialized by a constant expression, then we are guaranteed that all the variables will have the same value. Moreover, in practice, most compilers will replace any use of such const variables by their corresponding constant expression at compile time. So, in practice, there won't be any storage used to hold const variables that are initialized by constant expressions.

当该 const 变量是用常量表达式初始化时,可以保证所有的变量都有相同的值。但是在实践中,大部分的编译器在编译时都会用相应的常量表达式替换这些 const 变量的任何使用。所以,在实践中不会有任何存储空间用于存储用常量表达式初始化的 const 变量。

When a const is initialized by a value that is not a constant expression, then it should not be defined in header file. Instead, as with any other variable, the const should be defined and initialized in a source file. An extern declaration for that const should be made in the header, enabling multiple files to share that variable.

如果 const 变量不是用常量表达式初始化,那么它就不应该在头文件中定义。相反,和其他的变量一样,该 const 变量应该在一个源文件中定义并初始化。应在头文件中为它添加 extern 声明,以使其能被多个文件共享。

Exercises Section 2.9.1

Exercise 2.31:

Identify which of the following statements are declarations and which ones are definitions. Explain why they are declarations or definitions.

判别下列语句哪些是声明,哪些是定义,请解释原因。

     (a) extern int ix = 1024;
     (b) int iy;
     (c) extern int iz;
     (d) extern const int &ri;

Exercise 2.32:

Which of the following declarations and definitions would you put in a header? In a source file? Explain why.

下列声明和定义哪些应该放在头文件中?哪些应该放在源文件中?请解释原因。

     (a) int var;
     (b) const double pi = 3.1416;
     (c) extern int total = 255;
     (d) const double sq2 = sqrt(2.0);

Exercise 2.33:

Determine what options your compiler offers for increasing the warning level. Recompile selected earlier programs using this option to see whether additional problems are reported.

确定你的编译器提供了哪些提高警告级别的选项。使用这些选项重新编译以前选择的程序,查看是否会报告新的问题。

2.9.2. A Brief Introduction to the Preprocessor

2.9.2. 预处理器的简单介绍

Now that we know what we want to put in our headers, our next problem is to actually write a header. We know that to use a header we have to #include it in our source file. In order to write our own headers, we need to understand a bit more about how a #include directive works. The #include facility is a part of the C++ preprocessor. The preprocessor manipulates the source text of our programs and runs before the compiler. C++ inherits a fairly elaborate preprocessor from C. Modern C++ programs use the preprocessor in a very restricted fashion.

既然已经知道了什么应该放在头文件中,那么我们下一个问题就是真正地编写头文件。我们知道要使用头文件,必须在源文件中#include该头文件。为了编写头文件,我们需要进一步理解 #include 指示是怎样工作的。#include 设施是C++ 预处理器的一部分。预处理器处理程序的源代码,在编译器之前运行。C++ 继承了 C 的非常精细的预处理器。现在的 C++ 程序以高度受限的方式使用预处理器。

A #include directive takes a single argument: the name of a header. The pre-processor replaces each #include by the contents of the specified header. Our own headers are stored in files. System headers may be stored in a compiler-specific format that is more efficient. Regardless of the form in which a header is stored, it ordinarily contains class definitions and declarations of the variables and functions needed to support separate compilation.

#include 指示只接受一个参数:头文件名。预处理器用指定的头文件的内容替代每个 #include。我们自己的头文件存储在文件中。系统的头文件可能用特定于编译器的更高效的格式保存。无论头文件以何种格式保存,一般都含有支持分别编译所需的类定义及变量和函数的声明。

Headers Often Need Other Headers
头文件经常需要其他头文件

Headers often #include other headers. The entities that a header defines often use facilities from other headers. For example, the header that defines our Sales_item class must include the string library. The Sales_item class has a string data member and so must have access to the string header.

头文件经常 #include 其他头文件。头文件定义的实体经常使用其他头文件的设施。例如,定义 Sales_item 类的头文件必须包含 string 库。Sales_item 类含有一个 string 类型的数据成员,因此必须可以访问 string 头文件。

Including other headers is so common that it is not unusual for a header to be included more than once in the same source file. For example, a program that used the Sales_item header might also use the string library. That program wouldn'tindeed shouldn'tknow that our Sales_item header uses the string library. In this case, the string header would be included twice: once by the program itself and once as a side-effect of including our Sales_item header.

包含其他头文件是如此司空见惯,甚至一个头文件被多次包含进同一源文件也不稀奇。例如,使用 Sales_item 头文件的程序也可能使用 string 库。该程序不会(也不应该)知道 Sales_item 头文件使用了 string 库。在这种情况下,string 头文件被包含了两次:一次是通过程序本身直接包含,另一次是通过包含 Sales_item 头文件而间接包含。

Accordingly, it is important to design header files so that they can be included more than once in a single source file. We must ensure that including a header file more than once does not cause multiple definitions of the classes and objects that the header file defines. A common way to make headers safe uses the preprocessor to define a header guard. The guard is used to avoid reprocessing the contents of a header file if the header has already been seen.

因此,设计头文件时,应使其可以多次包含在同一源文件中,这一点很重要。我们必须保证多次包含同一头文件不会引起该头文件定义的类和对象被多次定义。使得头文件安全的通用做法,是使用预处理器定义头文件保护符。头文件保护符用于避免在已经见到头文件的情况下重新处理该头文件的内容。

Avoiding Multiple Inclusions
避免多重包含

Before we write our own header, we need to introduce some additional preprocessor facilities. The preprocessor lets us define our own variables.

在编写头文件之前,我们需要引入一些额外的预处理器设施。预处理器允许我们自定义变量。

Names used for preprocessor variables must be unique within the program. Any uses of a name that matches a preprocessor variable is assumed to refer to the preprocessor variable.

预处理器变量 的名字在程序中必须是唯一的。任何与预处理器变量相匹配的名字的使用都关联到该预处理器变量。

To help avoid name clashes, preprocessor variables usually are written in all uppercase letters.

为了避免名字冲突,预处理器变量经常用全大写字母表示。

A preprocessor variable has two states: defined or not yet defined. Various preprocessor directives define and test the state of preprocessor variables. The #define directive takes a name and defines that name as a preprocessor variable. The #ifndef directive tests whether the specified preprocessor variable has not yet been defined. If it hasn't, then everything following the #ifndef is processed up to the next #endif.

预处理器变量有两种状态:已定义或未定义。定义预处理器变量和检测其状态所用的预处理器指示不同。#define 指示接受一个名字并定义该名字为预处理器变量。#ifndef 指示检测指定的预处理器变量是否未定义。如果预处理器变量未定义,那么跟在其后的所有指示都被处理,直到出现 #endif

We can use these facilities to guard against including a header more than once:

可以使用这些设施来预防多次包含同一头文件:

     #ifndef SALESITEM_H
     #define SALESITEM_H
     // Definition of Sales_itemclass and related functions goes here
     #endif

The conditional directive

条件指示

     #ifndef SALESITEM_H

tests whether the SALESITEM_H preprocessor variable has not been defined. If SALESITEM_H has not been defined, the #ifndef succeeds and all the lines following #ifndef until the #endif is found are processed. Conversely, if the variable SALESITEM_H has been defined, then the #ifndef directive is false. The lines between it and the #endif directive are ignored.

测试 SALESITEM_H 预处理器变量是否未定义。如果 SALESITEM_H 未定义,那么 #ifndef 测试成功,跟在 #ifndef 后面的所有行都被执行,直到发现 #endif。相反,如果 SALESITEM_H 已定义,那么 #ifndef 指示测试为假,该指示和 #endif 指示间的代码都被忽略。

To guarantee that the header is processed only once in a given source file, we start by testing the #ifndef. The first time the header is processed, this test will succeed, because SALESITEM_H will not yet have been defined. The next statement defines SALESITEM_H. That way, if the file we are compiling happens to include this header a second time, the #ifndef directive will discover that SALESITEM_H is defined and skip the rest of the header file.

为了保证头文件在给定的源文件中只处理过一次,我们首先检测 #ifndef。第一次处理头文件时,测试会成功,因为 SALESITEM_H 还未定义。下一条语句定义了 SALESITEM_H。那样的话,如果我们编译的文件恰好又一次包含了该头文件。#ifndef 指示会发现 SALESITEM_H 已经定义,并且忽略该头文件的剩余部分。

Headers should have guards, even if they aren't included by another header. Header guards are trivial to write and can avoid mysterious compiler errors if the header subsequently is included more than once.

头文件应该含有保护符,即使这些头文件不会被其他头文件包含。编写头文件保护符并不困难,而且如果头文件被包含多次,它可以避免难以理解的编译错误。

This strategy works well provided that no two headers define and use a pre-processor constant with the same name. We can avoid problems with duplicate preprocessor variables by naming them for an entity, such as a class, that is defined inside the header. A program can have only one class named Sales_item. By using the class name to compose the name of the header file and the preprocessor variable, we make it pretty likely that only one file will use this preprocessor variable.

当没有两个头文件定义和使用同名的预处理器常量时,这个策略相当有效。我们可以为定义在头文件里的实体(如类)命名预处理器变量来避免预处理器变量重名的问题。一个程序只能含有一个名为 Sales_item 的类。通过使用类名来组成头文件和预处理器变量的名字,可以使得很可能只有一个文件将会使用该预处理器变量。

Using Our Own Headers
使用自定义的头文件

The #include directive takes one of two forms:

#include 指示接受以下两种形式:

     #include <standard_header>
     #include "my_file.h"

If the header name is enclosed by angle brackets (< >), it is presumed to be a standard header. The compiler will look for it in a predefined set of locations, which can be modified by setting a search path environment variable or through a command line option. The search methods used vary significantly across compilers. We recommend you ask a colleague or consult your compiler's user's guide for further information. If the header name is enclosed by a pair of quotation marks, the header is presumed to be a nonsystem header. The search for nonsystem headers usually begins in the directory in which the source file is located.

如果头文件名括在尖括号(< >)里,那么认为该头文件是标准头文件。编译器将会在预定义的位置集查找该头文件,这些预定义的位置可以通过设置查找路径环境变量或者通过命令行选项来修改。使用的查找方法因编译器的不同而差别迥异。建议你咨询同事或者查阅编译器用户指南来获得更多的信息。如果头文件名括在一对引号里,那么认为它是非系统头文件,非系统头文件的查找通常开始于源文件所在的路径。

Team LiB
Previous Section Next Section