12.1. Class Definitions and Declarations12.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: 从第一章开始,程序中就已经使用了类。已经用过的标准库类型,比如 vector,istream 和 string,都是类类型。还定义了一些简单的类,如 Sales_item 和 TextQuery 类。为了扼要秣,再来看年 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 Recap12.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 节中编写这个类时,已经学习了有关类的一些知识。
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. 一个类可以包含若干公有的、私有的和受保护的部分。我们已经使用过 public 和 private 访问标号:在 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 必须同时出现在声明和定义中,若只出现在其中一处,就会出现一个编译时错误。 12.1.2. Data Abstraction and Encapsulation12.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 节)来定义类的抽象接口和实施封装。一个类可以没有访问标号,也可以包含多个访问标号:
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 关键字是定义的,则这些成员是私有的。
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. 在简单的应用程序中,类的使用者和设计者也许是同一个人。即使在这种情况下,保持角色区分也是有益的。设计类的接口时,设计者应该考虑的是如何方便类的使用;使用类的时候,设计者就不应该考虑类如何工作。
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 类的”用户“,指的就是使用类编写应用程序的程序员。如果提到书店应用程序的”用户“,那么指的是运行应用程序的书店管理人员。
12.1.3. More on Class Definitions12.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 的一个好处是可以使得类比较容易阅读。
12.1.4. Class Declarations versus Definitions12.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 是一个类型,但不知道包含哪些成员。
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; };
12.1.5. Class Objects12.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: 定义了一个类类型之后,可以按以下两种方式使用。
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;
|