Team LiB
Previous Section Next Section

12.1. Class Definitions and Declarations

12.1. 类的定义和声明

Starting from Chapter 1, our programs have used classes. The library types we've usedvector, istream, stringare all class types. We've also defined some simple classes of our own, such as the Sales_item and TextQuery classes. To recap, let's look again at the Sales_item class:

从第一章开始,程序中就已经使用了类。已经用过的标准库类型,比如 vectoristreamstring,都是类类型。还定义了一些简单的类,如 Sales_itemTextQuery 类。为了扼要秣,再来看年 Sales_item 类:

class Sales_item {
public:
    // operations on Sales_item objects
    double avg_price() const;
    bool same_isbn(const Sales_item &rhs) const
        { return isbn == rhs.isbn; }
    // default constructor needed to initialize members of built-in type
    Sales_item(): units_sold(0), revenue(0.0) { }
private:
    std::string isbn;
    unsigned units_sold;
    double revenue;
};

double Sales_item::avg_price() const
{
    if (units_sold)
        return revenue/units_sold;
    else
        return 0;
}

12.1.1. Class Definitions: A Recap

12.1.1. 类定义:扼要重述

In writing this class in Section 2.8 (p. 63) and Section 7.7 (p. 258), we already learned a fair bit about classes.

第 2.8 节第 7.7 节中编写这个类时,已经学习了有关类的一些知识。

Most fundamentally, a class defines a new type and a new scope.

最简单地说,类就是定义了一个新的类型和一个新作用域。



Class Members
类成员

Each class defines zero or more members. Members can be either data, functions, or type definitions.

每个类可以没有成员,也可以定义多个成员,成员可以是数据、函数或类型别名。

A class may contain multiple public, private, and protected sections. We've already used the public and private access labels: Members defined in the public section are accessible to all code that uses the type; those defined in the private section are accessible to other class members. We'll have more to say about protected when we discuss inheritance in Chapter 15.

一个类可以包含若干公有的、私有的和受保护的部分。我们已经使用过 publicprivate 访问标号:在 public 部分定义的成员可被使用该类型的所有代码访问;在 private 部分定义的成员可被其他类成员访问。在第十五章讨论继承时将进一步探讨 protected

All members must be declared inside the class; there is no way to add members once the class definition is complete.

所有成员必须在类的内部声明,一旦类定义完成后,就没有任何方式可以增加成员了。

Constructors
构造函数

When we create an object of a class type, the compiler automatically uses a constructor (Section 2.3.3, p. 49) to initialize the object. A constructor is a special member function that has the same name as the class. Its purpose is to ensure that each data member is set to sensible initial values.

创建一个类类型的对象时,编译器会自动使用一个构造函数(第 2.3.3 节)来初始化该对象。构造函数是一个特殊的、与类同名的成员函数,用于给每个数据成员设置适当的初始值。

A constructor generally should use a constructor initializer list (Section 7.7.3, p. 263), to initialize the data members of the object:

构造函数一般就使用一个构造函数初始化列表(第 7.7.3 节),来初始化对象的数据成员:

// default constructor needed to initialize members of built-in type
Sales_item(): units_sold(0), revenue(0.0) { }

The constructor initializer list is a list of member names and parenthesized initial values. It follows the constructor's parameter list and begins with a colon.

Member Functions
成员函数

Member functions must be declared, and optionally may be defined, inside the class; functions defined inside the class are inline (Section 7.6, p. 256) by default.

在类内部,声明成员函数是必需的,而定义成员函数则是可选的。在类内部定义的函数默认为 inline第 7.6 节)。

Member functions defined outside the class must indicate that they are in the scope of the class. The definition of Sales_item::avg_price uses the scope operator (Section 1.2.2, p. 8) to indicate that the definition is for the avg_price function of the Sales_item class.

在类外部定义的成员函数必须指明它们是在类的作用域中。Sales_item::avg_price 的定义使用作用域操作符(第 1.2.2 节)来指明这是 Sales_item 类中 avg_price 函数的定义。

Member functions take an extra implicit argument that binds the function to the object on behalf of which the function is calledwhen we write

成员函数有一个附加的隐含实参,将函数绑定到调用函数的对象——当我们编写下面的函数时:

trans.avg_price()

we are calling the avg_price function on the object named trans. If trans is a Sales_item object, then references to a member of the Sales_item class inside the avg_price function are to the members in trans.

就是在调用名 trans 的对象的 avg_price 函数。如果 trans 是一个 Sales_item 对象,则在 avg_price 函数内部对 Sales_item 类成员引用就是对 trans 成员的引用。

Member functions may be declared const by putting the const keyword following the parameter list:

将关键字 const 加在形参表之后,就可以将成员函数声明为常量:

double avg_price() const;

A const member may not change the data members of the object on which it operates. The const must appear in both the declaration and definition. It is a compile-time error for the const to be indicated on one but not the other.

const 成员不能改变其所操作的对象的数据成员。const 必须同时出现在声明和定义中,若只出现在其中一处,就会出现一个编译时错误。

Exercises Section 12.1.1

Exercise 12.1:

Write a class named Person that represents the name and address of a person. Use a string to hold each of these elements.

编写一个名为 Person 的类,表示人的名字和地址。使用 string 来保存每个元素。

Exercise 12.2:

Provide a constructor for Person that takes two strings.

Person 提供一个接受两个 string 参数的构造函数。

Exercise 12.3:

Provide operations to return the name and address. Should these functions be const? Explain your choice.

提供返回名字和地址的操作。这些函数应为 const 吗?解释你的选择。

Exercise 12.4:

Indicate which members of Person you would declare as public and which you would declare as private. Explain your choice.

指明 Person 的哪个成员应声明为 public,哪个成员应声明为 private。解释你的选择。


12.1.2. Data Abstraction and Encapsulation

12.1.2. 数据抽象和封装

The fundamental ideas behind classes are data abstraction and encapsulation.

类背后蕴涵的基本思想是数据抽象封装

Data abstraction is a programming (and design) technique that relies on the separation of interface and implementation. The class designer must worry about how a class is implemented, but programmers that use the class need not know about these details. Instead, programmers who use a type need to know only the type's interface; they can think abstractly about what the type does rather than concretely about how the type works.

数据抽象是一种依赖于接口和实现分离的编程(和设计)技术。类设计者必须关心类是如何实现的,但使用该类的程序员不必了解这些细节。相反,使用一个类型的程序员仅需了解类型的接口,他们可以抽象地考虑该类型做什么,而不必具体地考虑该类型如何工作。

Encapsulation is a term that describes the technique of combining lower-level elements to form a new, higher-level entity. A function is one form of encapsulation: The detailed actions performed by the function are encapsulated in the larger entity that is the function itself. Encapsulated elements hide the details of their implementationwe may call a function but have no access to the statements that it executes. In the same way, a class is an encapsulated entity: It represents an aggregation of several members, and most (well-designed) class types hide the members that implement the type.

封装是一项低层次的元素组合起来的形成新的、高层次实体珠技术。函数是封装的一种形式:函数所执行的细节行为被封装在函数本身这个更大的实体中。被封装的元素隐藏了它们的实现细节——可以调用一个函数但不能访问它所执行的语句。同样地,类也是一个封装的实体:它代表若干成员的聚焦,大多数(良好设计的)类类型隐藏了实现该类型的成员。

If we think about the library vector type, it is an example of both data abstraction and encapsulation. It is abstract in that to use it, we think about its interfaceabout the operations that it can perform. It is encapsulated because we have no access to the details of how the type is representated nor to any of its implementation artifacts. An array, on the other hand, is similar in concept to a vector but is neither abstract nor encapsulated. We manipulate an array directly by accessing the memory in which the array is stored.

标准库类型 vector 同时具备数据抽象和封装的特性。在使用方面它是抽象的,只需考虑它的接口,即它能执行的操作。它又是封装的,因为我们既无法了解该类型如何表示的细节,也无法访问其任意的实现制品。另一方面,数组在概念上类似于 vector,但既不是抽象的,也不是封装的。可以通过访问存放数组的内存来直接操纵数组。

Access Labels Enforce Abstraction and Encapsulation
访问标号实施抽象和封装

In C++ we use access labels (Section 2.8, p. 65) to define the abstract interface to the class and to enforce encapsulation. A class may contain zero or more access labels:

在 C++ 中,使用访问标号第 2.8 节)来定义类的抽象接口和实施封装。一个类可以没有访问标号,也可以包含多个访问标号:

  • Members defined after a public label are accessible to all parts of the program. The data-abstraction view of a type is defined by its public members.

    程序的所有部分都可以访问带有 public 标号的成员。类型的数据抽象视图由其 public 成员定义。

  • Members defined after a private label are not accessible to code that uses the class. The private sections encapsulate (e.g., hide) the implementation from code that uses the type.

    使用类的代码不可以访问带有 private 标号的成员。private 封装了类型的实现细节。

There are no restrictions on how often an access label may appear. Each access label specifies the access level of the succeeding member definitions. The specified access level remains in effect until the next access label is encountered or the closing right brace of the class body is seen.

一个访问标号可以出现的次数通常是没有限制的。每个访问标号指定了随后的成员定义的访问级别。这个指定的访问级别持续有效,直到遇到下一个访问标号或看到类定义体的右花括号为止。

A class may define members before any access label is seen. The access level of members defined after the open curly of the class and before the first access label depend on how the class is defined. If the class is defined with the struct keyword, then members defined before the first access label are public; if the class is defined using the class keyword, then the members are private.

可以在任意的访问标号出现之前定义类成员。在类的左花括号之后、第一个访问标号之前定义成员的访问级别,其值依赖于类是如何定义的。如果类是用 struct 关键字定义的,则在第一个访问标号之前的成员是公有的;如果类是用 class 关键字是定义的,则这些成员是私有的。

Advice: Concrete and Abstract Types

建议:具体类型和抽象类型

Not all types need to be abstract. The library pair class is a good example of a useful, well-designed class that is concrete rather than abstract. A concrete class is a class that exposes, rather than hides, its implementation.

并非所有类型都必须是抽象的。标准库中的 pair 类就是一个实用的、设计良好的具体类而不是抽象类。具体类会暴露而非隐藏其实现细节。

Some classes, such as pair, really have no abstract interface. The pair type exists to bundle two data members into a single object. There is no need or advantage to hiding the data members. Hiding the members in a class like pair would only complicate the use of the type.

一些类,例如 pair,确实没有抽象接口。pair 类型只是将两个数据成员捆绑成单个对象。在这种情况下,隐藏数据成员没有必要也没有明显的好处。在像 pair 这样的类中隐藏数据成员只会造成类型使用的复杂化。

Even so, such types often have member functions. In particular, it is a good idea for any class that has data members of built-in or compound type to define constructor(s) to initialize those members. The user of the class could initialize or assign to the data members but it is less error-prone for the class to do so.

尽管如此,这样的类型通常还是有成员函数。特别地,如果类具有内置类型或复合类型数据成员,那么定义构造函数来初始化这些成员就是一个好主意。类的使用都也可以初始化或赋值数据成员,但由类来做更不易出错。


Different Kinds of Programming Roles
编程角色的不同类别

Programmers tend to think about the people who will run their applications as "users." Applications are designed for and evolve in response to feedback from those who ultimately "use" the applications. Classes are thought of in a similar way: A class designer designs and implements a class for "users" of that class. In this case, the "user" is a programmer, not the ultimate user of the application.

程序员经常会将运行应用程序的人看作“用户”。应用程序为最终“使用”它的用户而设计,并响应用户的反馈而完善。类也类似:类的设计者为类的“用户”设计并实现类。在这种情况下,“用户”是程序员,而不是应用程序的最终用户。

Authors of successful applications do a good job of understanding and implementing the needs of the application's users. Similarly, well-designed, useful classes are designed with a close attention to the needs of the users of the class.

成功的应用程序的创建者会很好地理解和实现用户的需求。同样地,良好设计的、实用的类,其设计也要贴近类用户的需求。

In another way, the division between class designer and class user reflects the division between users of an application and the designers and implementors of the application. Users care only if the application meets their needs in a cost-effective way. Similarly, users of a class care only about its interface. Good class designers define a class interface that is intuitive and easy to use. Users care about the implementation only in so far as the implementation affects their use of the class. If the implementation is too slow or puts burdens on users of the class, then the users must care. In well-designed classes, only the class designer worries about the implementation.

另一方面,类的设计者与实现者之间的区别,也反映了应用程序的用户与设计和实现者之间的区分。用户只关心应用程序能否以合理的费用满足他们的需求。同样地,类的使用者只关心它的接口。好的类设计者会定义直观和易用的类接口,而使用者只关心类中影响他们使用的部分实现。如果类的实现速度太慢或给类的使用者加上负担,则必然引起使用者的关注。在良好设计的类中,只有类的设计者会关心实现。

In simple applications, the user of a class and the designer of the class might be one and the same person. Even in such cases, it is useful to keep the roles distinct. When designing the interface to a class, the class designer should think about how easy it will be to use the class. When using the class, the designer shouldn't think about how the class works.

在简单的应用程序中,类的使用者和设计者也许是同一个人。即使在这种情况下,保持角色区分也是有益的。设计类的接口时,设计者应该考虑的是如何方便类的使用;使用类的时候,设计者就不应该考虑类如何工作。

C++ programmers tend to speak of "users" interchangably as users of the application or users of a class.

注意,C++ 程序员经常会将应用程序的用户和类的使用者都称为“用户”。



When referring to a "user," the context makes it clear which kind of user is meant. If we speak of "user code" or the "user" of the Sales_item class, we mean a programmer who is using a class in writing an application. If we speak of the "user" of the bookstore application, we mean the manager of the store who is running the application.

提到“用户”时,应该由上下文清楚地标明所指的是哪类用户。如果提到“用户代码”或 Sales_item 类的”用户“,指的就是使用类编写应用程序的程序员。如果提到书店应用程序的”用户“,那么指的是运行应用程序的书店管理人员。

Key Concept: Benefits of Data Abstraction and Encapsulation

关键概念:数据抽象和封装的好处

Data abstraction and encapsulation provide two important advantages:

数据抽象和封装提供了两个重要优点:

  • Class internals are protected from inadvertent user-level errors, which might corrupt the state of the object.

    避免类内部出现无意的、可能破坏对象状态的用户级错误。

  • The class implementation may evolve over time in response to changing requirements or bug reports without requiring change in user-level code.

    随时间推移可以根据需求改变或缺陷(bug)报告来完美类实现,而无须改变用户级代码。

By defining data members only in the private section of the class, the class author is free to make changes in the data. If the implementation changes, only the class code needs to be examined to see what affect the change may have. If data are public, then any function that directly accesses the data members of the old representation might be broken. It would be necessary to locate and rewrite all those portions of code that relied on the old representation before the program could be used again.

仅在类的私有部分定义数据成员,类的设计者就可以自由地修改数据。如果实现改变了,那么只需检查类代码来了解此变化可能造成的影响。如果数据为仅有的,则任何直接访问原有数据成员的函数都可能遭到破坏。在程序可重新使用之前,有必要定位和重写依赖原有表示的那部分代码。

Similarly, if the internal state of the class is private, then changes to the member data can happen in only a limited number of places. The data is protected from mistakes that users might introduce. If there is a bug that corrupts the object's state, the places to look for the bug are localized: When data are private, only a member function could be responsible for the error. The search for the mistake is limited, greatly easing the problems of maintenance and program correctness.

同样地,如果类的内部状态是私有的,则数据成员的改变只可能在有限的地方发生。避免数据中出现用户可能引入的错误。如果有缺陷会破坏对象的状态,就在局部位置搜寻缺陷:如果数据是私有的,那么只有成员函数可能对该错误负责。对错误的搜寻是有限的,从而大大方便了程序的维护和修正。

If the data are private and if the interface to the member functions does not change, then user functions that manipulate class objects require no change.

如果数据是私有的并且没有改变成员函数的接口,则操纵类对象的用户函数无须改变。

Because changing a class definition in a header file effectively changes the text of every source file that includes that header, code that uses a class must be recompiled when the class changes.

改变头文件中的类定义可有效地改变包含该头文件的每个源文件的程序文本,所以,当类发生改变时,使用该类的代码必须重新编译。




Exercises Section 12.1.2

Exercise 12.5:

What are the access labels supported by C++ classes? What kinds of members should be defined after each access label? What, if any, are the constraints on where and how often an access label may appear inside a class definition?

C++ 类支持哪些访问标号?在每个访问标号之后应定义哪种成员?如果有的话,在类的定义中,一个访问标号可以出现在何处以及可出现多少次?约束条件是什么?

Exercise 12.6:

How do classes defined with the class keyword differ from those defined as struct?

class 关键字定义的类和用 struct 定义的类有什么不同?

Exercise 12.7:

What is encapsulation? Why it is useful?

什么是封装?为什么封装是有用的?


12.1.3. More on Class Definitions

12.1.3. 关于类定义的更多内容

The classes we've defined so far have been simple; yet they have allowed us to explore quite a bit of the language support for classes. There remain a few more details about the basics of writing a class that we shall cover in the remainder of this section.

迄今为止,所定义的类都是简单的,然而通过这些类我们已经了解到 C++ 语言为类所提供的相当多的支持。本节的其余部分将阐述编写类的更多基础知识。

Multiple Data Members of the Same Type
同一类型的多个数据成员

As we've seen, class data members are declared similarly to how ordinary variables are declared. One way in which member declarations and ordinary declarations are the same is that if a class has multiple data members with the same type, these members can be named in a single member declaration.

正如我们所见,类的数据成员的声明类似于普通变量的声明。如果一个类具有多个同一类型的数据成员,则这些成员可以在一个成员声明中指定,这种情况下,成员声明和普通变量声明是相同的。

For example, we might define a type named Screen to represent a window on a computer. Each Screen would have a string member that holds the contents of the window, and three string::size_type members: one that specifies the character on which the cursor currently rests, and two others that specify the height and width of the window. We might define the members of this class as:

例如,可以定义一个名为 Screen 的类型表示计算机上的窗口。每个 Screen 可以有一个保存窗口内容的 string 成员,以及三个 string::size_type 成员:一个指定光标当前停留的字符,另外两个指定窗口的高度和宽度。可以用如下方式这个类的成员:

     class Screen {
     public:
         // interface member functions
     private:
         std::string contents;
         std::string::size_type cursor;
         std::string::size_type height, width;
     };

Using Typedefs to Streamline Classes
使用类型别名来简化类

In addition to defining data and function members, a class can also define its own local names for types. Our Screen will be a better abstraction if we provide a typedef for std::string::size_type:

除了定义数据和函数成员之外,类还可以定义自己的局部类型名字。如果为 std::string::size_type 提供一个类型别名,那么 Screen 类将是一个更好的抽象:

     class Screen {
     public:
         // interface member functions
         typedef std::string::size_type index;
     private:
         std::string contents;
         index cursor;
         index height, width;
     };

Type names defined by a class obey the standard access controls of any other member. We put the definition of index in the public part of the class because we want users to use that name. Users of class Screen need not know that we use a string as the underlying implementation. By defining index, we hide this detail of how Screen is implemented. By making the type public, we let our users use this name.

类所定义的类型名遵循任何其他成员的标准访问控制。将 index 的定义放在类的 public 部分,是因为希望用户使用这个名字。Screen 类的使用者不必了解用 string 实现的底层细节。定义 index 来隐藏 Screen 的实现细节。将这个类型设为 public,就允许用户使用这个名字。

Member Functions May Be Overloaded
成员函数可被重载

Another way our classes have been simple is that they have defined only a few member functions. In particular, none of our classes have needed to define over-loaded versions of any of their member functions. However, as with nonmember functions, a member function may be overloaded (Section 7.8, p. 265).

这些类之所以简单,另一个方面也是因为它们只定义了几个成员函数。特别地,这些类都不需要定义其任意成员函数的重载版本。然而,像非成员函数一样,成员函数也可以被重载(第 7.8 节)。

With the exception of overloaded operators (Section 14.9.5, p. 547)which have special rulesa member function overloads only other member functions of its own class. A class member function is unrelated to, and cannot overload, ordinary nonmember functions or functions declared in other classes. The same rules apply to overloaded member functions as apply to plain functions: Two overloaded members cannot have the same number and types of parameters. The function-matching (Section 7.8.2, p. 269) process used for calls of nonmember overloaded functions also applies to calls of overloaded member functions.

重载操作符(第 14.9.5 节)有特殊规则,是个例外,成员函数只能重载本类的其他成员函数。类的成员函数与普通的非成员函数以及在其他类中声明的函数不相关,也不能重载它们。重载的成员函数和普通函数应用相同的规则:两个重载成员的形参数量和类型不能完全相同。调用非成员重载函数所用到的函数匹配(第 7.8.2 节)过程也应用于重载成员函数的调用。

Defining Overloaded Member Functions
定义重载成员函数

To illustrate overloading, we might give our Screen class two overloaded members to return a given character from the window. One version will return the character currently denoted by the cursor and the other returns the character at a given row and column:

为了举例说明重载,可以给出 Screen 类的两个重载成员,用于从窗口返回一个特定字符。两个重载成员中,一个版本返回由当前光标指示的字符,另一个返回指定行列处的字符:

class Screen {
public:
    typedef std::string::size_type index;
    // return character at the cursor or at a given position
    char get() const { return contents[cursor]; }
    char get(index ht, index wd) const;
    // remaining members
private:
    std::string contents;
    index cursor;
    index height, width;
};

As with any overloaded function, we select which version to run by supplying the appropriate number and/or types of arguments to a given call:

与任意的重载函数一样,给指定的函数调用提供适当数目和/或类型的实参来选择运行哪个版本:

     Screen myscreen;
     char ch = myscreen.get();// calls Screen::get()
     ch = myscreen.get(0,0);  // calls Screen::get(index, index)

Explicitly Specifying inline Member Functions
显式指定 inline 成员函数

Member functions that are defined inside the class, such as the get member that takes no arguments, are automatically treated as inline. That is, when they are called, the compiler will attempt to expand the function inline (Section 7.6, p. 256). We can also explicitly declare a member function as inline:

在类内部定义的成员函数,例如不接受实参的 get 成员,将自动作为 inline 处理。也就是说,当它们被调用时,编译器将试图在同一行内扩展该函数(第 7.6 节)。也可以显式地将成员函数声明为 inline

     class Screen {
     public:
         typedef std::string::size_type index;
         // implicitly inline when defined inside the class declaration
         char get() const { return contents[cursor]; }
         // explicitly declared as inline; will be defined outside the class declaration
         inline char get(index ht, index wd) const;
         // inline not specified in class declaration, but can be defined inline later
         index get_cursor() const;
         // ...
      };
     // inline declared in the class declaration; no need to repeat on the definition
     char Screen::get(index r, index c) const
     {
         index row = r * width;    // compute the row location
         return contents[row + c]; // offset by c to fetch specified character
     }
     // not declared as inline in the class declaration, but ok to make inline in definition
     inline Screen::index Screen::get_cursor() const
     {
         return cursor;
     }

We can specify that a member is inline as part of its declaration inside the class body. Alternatively, we can specify inline on the function definition that appears outside the class body. It is legal to specify inline both on the declaration and definition. One advantage of defining inline functions outside the class is that it can make the class easier to read.

可以在类定义体内部指定一个成员为inline,作为其声明的一部分。或者,也可以在类定义外部的函数定义上指定 inline。在声明和定义处指定 inline 都是合法的。在类的外部定义 inline 的一个好处是可以使得类比较容易阅读。

As with other inlines, the definition of an inline member function must be visible in every source file that calls the function. The definition for an inline member function that is not defined within the class body ordinarily should be placed in the same header file in which the class definition appears.

像其他 inline 一样,inline 成员函数的定义必须在调用该函数的每个源文件中是可见的。不在类定义体内定义的 inline 成员函数,其定义通常应放在有类定义的同一头文件中。



Exercises Section 12.1.3

Exercise 12.8:

Define Sales_item::avg_price as an inline function.

Sales_item::avg_price 定义为内联函数。

Exercise 12.9:

Write your own version of the Screen class presented in this section, giving it a constructor to create a Screen from values for height, width, and the contents of the screen.

修改本节中给出的 Screen 类,给出一个构造函数,根据屏幕的高度、宽度和内容的值来创建 Screen

Exercise 12.10:

Explain each member in the following class:

解释下述类中的每个成员:

     class Record {
         typedef std::size_t size;
         Record(): byte_count(0) { }
         Record(size s): byte_count(s) { }
         Record(std::string s): name(s), byte_count(0) { }
         size byte_count;
         std::string name;
     public:
         size get_count() const { return byte_count; }
         std::string get_name() const { return name; }
     };


12.1.4. Class Declarations versus Definitions

12.1.4. 类声明与类定义

A class is completely defined once the closing curly brace appears. Once the class is defined, all the class members are known. The size required to store an object of the class is known as well. A class may be defined only once in a given source file. When a class is defined in multiple files, the definition in each file must be identical.

一旦遇到右花括号,类的定义就结束了。并且一旦定义了类,那以我们就知道了所有的类成员,以及存储该类的对象所需的存储空间。在一个给定的源文件中,一个类只能被定义一次。如果在多个文件中定义一个类,那么每个文件中的定义必须是完全相同的。

By putting class definitions in header files, we can ensure that a class is defined the same way in each file that uses it. By using header guards (Section 2.9.2, p. 69), we ensure that even if the header is included more than once in the same file, the class definition will be seen only once.

将类定义在头文件中,可以保证在每个使用类的文件中以同样的方式定义类。使用头文件保护符(header guard)(第 2.9.2 节),来保证即使头文件在同一文件中被包含多次,类定义也只出现一次。

It is possible to declare a class without defining it:

可以声明一个类而不定义它:

     class Screen; // declaration of the Screen class

This declaration, sometimes referred to as a forward declaration, introduces the name Screen into the program and indicates that Screen refers to a class type. After a declaration and before a definition is seen, the type Screen is an incompete typeit's known that Screen is a type but not known what members that type contains.

这个声明,有时称为前向声明(forward declaraton),在程序中引入了类类型的 Screen。在声明之后、定义之前,类 Screen 是一个不完全类型(incompete type),即已知 Screen 是一个类型,但不知道包含哪些成员。

An incomplete type can be used only in limited ways. Objects of the type may not be defined. An incomplete type may be used to define only pointers or references to the type or to declare (but not define) functions that use the type as a paremeter or return type.

不完全类型(incomplete type)只能以有限方式使用。不能定义该类型的对象。不完全类型只能用于定义指向该类型的指针及引用,或者用于声明(而不是定义)使用该类型作为形参类型或返回类型的函数。



A class must be fully defined before objects of that type are created. The class must be definedand not just declaredso that the compiler can know how much storage to reserve for an object of that class type. Similarly, the class must be defined before a reference or pointer is used to access a member of the type.

在创建类的对象之前,必须完整地定义该类。必须定义类,而不只是声明类,这样,编译器就会给类的对象预定相应的存储空间。同样地,在使用引用或指针访问类的成员之前,必须已经定义类。

Using Class Declarations for Class Members
为类的成员使用类声明

A data member can be specified to be of a class type only if the definition for the class has already been seen. If the type is incomplete, a data member can be only a pointer or a reference to that class type.

只有当类定义已经在前面出现过,数据成员才能被指定为该类类型。如果该类型是不完全类型,那么数据成员只能是指向该类类型的指针或引用。

Because a class is not defined until its class body is complete, a class cannot have data members of its own type. However, a class is considered declared as soon as its class name has been seen. Therefore, a class can have data members that are pointers or references to its own type:

因为只有当类定义体完成后才能定义类,因此类不能具有自身类型的数据成员。然而,只要类名一出现就可以认为该类已声明。因此,类的数据成员可以是指向自身类型的指针或引用:

     class LinkScreen {
         Screen window;
         LinkScreen *next;
         LinkScreen *prev;
     };

A common use of class forward declarations is to write classes that are mutually dependent on one another. We'll see an example of such usage in Section 13.4 (p. 486).

类的前身声明一般用来编写相互依赖的类。在第 13.4 节中,我们将看到用法的一个例子。



Exercises Section 12.1.4

Exercise 12.11:

Define a pair of classes X and Y, in which X has a pointer to Y, and Y has an object of type X.

定义两个类 XYX 中有一个指向 Y 的指针,Y 中有一个 X 类型的对象。

Exercise 12.12:

Explain the difference between a class declaration and definition. When would you use a class declaration? A class definition?

解释类声明与类定义之间的差异。何时使用类声明?何时使用类定义?


12.1.5. Class Objects

12.1.5. 类对象

When we define a class, we are defining a type. Once a class is defined, we can define objects of that type. Storage is allocated when we define objects, but (ordinarily) not when we define types:

定义一个类时,也就是定义了一个类型。一旦定义了类,就可以定义该类型的对象。定义对象时,将为其分配存储空间,但(一般而言)定义类型时不进行存储分配:

     class Sales_item {
     public:
         // operations on Sales_item objects
     private:
         std::string isbn;
         unsigned units_sold;
         double revenue;
     };

defines a new type, but does not allocate any storage. When we define an object

定义了一个新的类型,但没有进行存储分配。当我们定义一个对象

     Sales_item item;

the compiler allocates an area of storage sufficient to contain a Sales_item object. The name item refers to that area of storage. Each object has its own copy of the class data members. Modifying the data members of item does not change the data members of any other Sales_item object.

时,编译器分配了足以容纳一个 Sales_item 对象的存储空间。item 指的就是那个存储空间。每个对象具有自己的类数据成员的副本。修改 item 的数据成员不会改变任何其他 Sales_item 对象的数据成员。

Defining Objects of Class Type
定义类类型的对象

After a class type has been defined, the type can be used in two ways:

定义了一个类类型之后,可以按以下两种方式使用。

  • Using the class name directly as a type name

    将类的名字直接用作类型名。

  • Specifying the keyword class or struct, followed by the class name:

    指定关键字 classstruct,后面跟着类的名字:

         Sales_item item1;       // default initialized object of type Sales_item
         class Sales_item item1; // equivalent definition of item1
    

Both methods of referring to a class type are equivalent. The second method is inherited from C and is also valid in C++. The first, more concise form was introduced by C++ to make class types easier to use.

两种引用类类型方法是等价的。第二种方法是从 C 继承而来的,在 C++ 中仍然有效。第一种更为简练,由 C++ 语言引入,使得类类型更容易使用。

Why a Class Definition Ends in a Semicolon
为什么类的定义以分号结束

We noted on page 64 that a class definition ends with a semicolon. A semicolon is required because we can follow a class definition by a list of object definitions. As always, a definition must end in a semicolon:

我们在第 2.8 节中指出,类的定义分号结束。分号是必需的,因为在类定义之后可以接一个对象定义列表。定义必须以分号结束:

class Sales_item { /* ... */ };
class Sales_item { /* ... */ } accum, trans;

Ordinarily, it is a bad idea to define an object as part of a class definition. Doing so obscures what's happening. It is confusing to readers to combine definitions of two different entitiesthe class and a variablein a single statement.

通常,将对象定义成类定义的一部分是个坏主意。这样做,会使所发生的操作难以理解。对读者而言,将两个不同的实体(类和变量)组合在一个语句中,也会令人迷惑不解。



Team LiB
Previous Section Next Section