7.7. Class Member Functions7.7. 类的成员函数In Section 2.8 (p. 63) we began the definition of the Sales_item class used in solving the bookstore problem from Chapter 1. Now that we know how to define ordinary functions, we can continue to fill in our class by defining the member functions of this class. 第 2.8 节开始定义类 Sales_item,用于解决第一章的书店问题。至此,我们已经了解了如何定义普通函数,现在来定义类的成员函数,以继续完善这个类。 We define member functions similarly to how we define ordinary functions. As with any function, a member function consists of four parts: 成员函数的定义与普通函数的定义类似。和任何函数一样,成员函数也包含下面四个部分:
As we know, the first three of these parts constitute the function prototype. The function prototype defines all the type information related to the function: what its return type is, the function name, and what types of arguments may be passed to it. The function prototype must be defined within the class body. The body of the function, however, may be defined within the class itself or outside the class body. 正如我们知道的,前面三部分组成函数原型。函数原型定义了所有和函数相关的类型信息:函数返回类型是什么、函数的名字、应该给这个函数传递什么类型的实参。函数原型必须在类中定义。但是,函数体则既可以在类中也可以在类外定义。 With this knowledge, let's look at our expanded class definition, to which we've added two new members: the member functions avg_price and same_isbn. The avg_price function has an empty parameter list and returns a value of type double. The same_isbn function returns a bool and takes a single parameter of type reference to const Sales_item. 知道这些后,观察下面扩展的类定义,我们为这个类增加了两个新成员:成员函数 avg_price 和 same_isbn。其中 avg_price 函数的形参表是空的,返回 double 类型的值。而 same_isbn 函数则返回 bool 对象,有一个 const 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; } // private members as before private: std::string isbn; unsigned units_sold; double revenue; }; We'll explain the meaning of the const that follows the parameter lists shortly, but first we need to explain how member functions are defined. 在解释跟在形参表后面的 const 之前,必须先说明成员函数是如何定义的。 7.7.1. Defining the Body of a Member Function7.7.1. 定义成员函数的函数体We must declare all the members of a class within the curly braces that delimit the class definition. There is no way to subsequently add any members to the class. Members that are functions must be defined as well as declared. We can define a member function either inside or outside of the class definition. In Sales_item, we have one example of each: same_isbn is defined inside the Sales_item class, whereas avg_price is declared inside the class but defined elsewhere. 类的所有成员都必须在类定义的花括号里面声明,此后,就不能再为类增加任何成员。类的成员函数必须如声明的一般定义。类的成员函数既可以在类的定义内也可以在类的定义外定义。在类 Sales_item 中,这两种情况各有一例说明:函数 same_isbn 在类内定义,而函数 avg_price 则在类内声明,在类外定义。 A member function that is defined inside the class is implicitly treated as an inline function (Section 7.6, p. 256). 编译器隐式地将在类内定义的成员函数当作内联函数(第 7.6 节)。 Let's look in more detail at the definition of same_isbn: 再详细观察函数 same_isbn 的定义: bool same_isbn(const Sales_item &rhs) const { return isbn == rhs.isbn; } As with any function, the body of this function is a block. In this case, the block contains a single statement that returns the result of comparing the value of the isbn data members of two Sales_item objects. 与任何函数一样,该函数的函数体也是一个块。在这个函数中,块中只有一个语句,比较两个 Sales_item 对象的数据成员 isbn 的值,并返回比较结果。 The first thing to note is that the isbn member is private. Even though these members are private, there is no error. 首先要注意的是:成员 isbn 是 private 的。尽管如此,上述语句却没有任何错误。
More interesting is understanding from which Sales_item objects does the function get the values that it compares. The function refers both to isbn and rhs.isbn. Fairly clearly, rhs.isbn uses the isbn member from the argument passed to the function. The unqualified use of isbn is more interesting. As we shall see, the unqualified isbn refers to the isbn member of the object on behalf of which the function is called. 更有意思的是,函数从哪个 Sales_item 类对象得到这个用于比较的值?函数涉及到 isbn 和 rhs.isbn。很明显,rhs.isbn 使用的是传递给此函数的实参的 isbn 成员。没有前缀的 isbn 的用法更加有意思。正如我们所看见的,这个没有前缀的 isbn 指的是用于调用函数的对象的 isbn 成员。 Member Functions Have an Extra, Implicit Parameter成员函数含有额外的、隐含的形参When we call a member function, we do so on behalf of an object. For example, when we called same_isbn in the bookstore program on page 26, we executed the same_isbn member on the object named total: 调用成员函数时,实际上是使用对象来调用的。例如,调用 第 1.6 节书店程序中的函数 same_isbn,是通过名为 total 的对象来执行 same_isbn 函数的: if (total.same_isbn(trans)) In this call, we pass the object trans. As part of executing the call, the object trans is used to initialize the parameter rhs. Thus, in this call, rhs.isbn is a reference to trans.isbn. 在这个调用中,传递了对象 trans。作为执行调用的一部分,使用对象 trans 初始化形参 rhs。于是,rhs.isbn 是 trans.isbn 的引用。 The same argument-binding process is used to bind the unqualified use of isbn to the object named total. Each member function has an extra, implicit parameter that binds the function to the object on which the function was called. When we call same_isbn on the object named total, that object is also passed to the function. When same_isbn refers to isbn, it is implicitly referring to the isbn member of the object on which the function was called. The effect of this call is to compare total.isbn with Trans.isbn. 而没有前缀的 isbn 使用了相同的实参绑定过程,使之与名为 total 的对象绑定起来。每个成员函数都有一个额外的、隐含的形参将该成员函数与调用该函数的类对象捆绑在一起。当调用名为 total 的对象的 same_isbn 时,这个对象也传递给了函数。而 same_isbn 函数使用 isbn 时,就隐式地使用了调用该函数的对象的 isbn 成员。这个函数调用的效果是比较 total.isbn 和 trans.isbn 两个值。 Introducing thisthis 指针的引入Each member function (except for static member functions, which we cover in Section 12.6 (p. 467)) has an extra, implicit parameter named this. When a member function is called, the this parameter is initialized with the address of the object on which the function was invoked. To understand a member function call, we might think that when we write 每个成员函数(除了在第 12.6 节介绍的 static 成员函数外)都有一个额外的、隐含的形参 this。在调用成员函数时,形参 this 初始化为调用函数的对象的地址。为了理解成员函数的调用,可考虑下面的语句: total.same_isbn(trans); it is as if the compiler rewrites the call as 就如编译器这样重写这个函数调用:
// pseudo-code illustration of how a call to a member function is translated
Sales_item::same_isbn(&total, trans);
In this call, the data member isbn inside same_isbn is bound to the one belonging to total. 在这个调用中,函数 same_isbn 中的数据成员 isbn 属于对象 total。 Introducing const Member Functionsconst 成员函数的引入We now can understand the role of the const that follows the parameter lists in the declarations of the Sales_item member functions: That const modifies the type of the implicit this parameter. When we call total.same_isbn(trans), the implicit this parameter will be a const Sales_Item* that points to total. It is as if the body of same_isbn were written as 现在,可以理解跟在 Sales_item 成员函数声明的形参表后面的 const 所起的作用了:const 改变了隐含的 this 形参的类型。在调用 total.same_isbn(trans) 时,隐含的 this 形参将是一个指向 total 对象的 const Sales_Item* 类型的指针。就像如下编写 same_isbn 的函数体一样: // pseudo-code illustration of how the implicit this pointer is used // This code is illegal: We may not explicitly define the this pointer ourselves // Note that this is a pointer to const because same_isbn is a const member bool Sales_item::same_isbn(const Sales_item *const this, const Sales_item &rhs) const { return (this->isbn == rhs.isbn); } A function that uses const in this way is called a const member function. Because this is a pointer to const, a const member function cannot change the object on whose behalf the function is called. Thus, avg_price and same_isbn may read but not write to the data members of the objects on which they are called. 用这种方式使用 const 的函数称为常量成员函数。由于 this 是指向 const 对象的指针,const 成员函数不能修改调用该函数的对象。因此,函数 avg_price 和函数 same_isbn 只能读取而不能修改调用它们的对象的数据成员。
Using the this Pointerthis 指针的使用Inside a member function, we need not explicitly use the this pointer to access the members of the object on which the function was called. Any unqualified reference to a member of our class is assumed to be a reference through this: 在成员函数中,不必显式地使用 this 指针来访问被调用函数所属对象的成员。对这个类的成员的任何没有前缀的引用,都被假定为通过指针 this 实现的引用: bool same_isbn(const Sales_item &rhs) const { return isbn == rhs.isbn; } The uses of isbn in this function are as if we had written this->units_sold or this->revenue. 在这个函数中 isbn 的用法与 this->units_sold 或 this->revenue 的用法一样。 The this parameter is defined implicitly, so it is unnecessary and in fact illegal to include the this pointer in the function's parameter list. However, in the body of the function we can refer to the this pointer explicitly. It is legal, although unnecessary, to define same_isbn as follows: 由于 this 指针是隐式定义的,因此不需要在函数的形参表中包含 this 指针,实际上,这样做也是非法的。但是,在函数体中可以显式地使用 this 指针。如下定义函数 same_isbn 尽管没有必要,但是却是合法的: bool same_isbn(const Sales_item &rhs) const { return this->isbn == rhs.isbn; } 7.7.2. Defining a Member Function Outside the Class7.7.2. 在类外定义成员函数Member functions defined outside the class definition must indicate that they are members of the class: 在类的定义外面定义成员函数必须指明它们是类的成员: double Sales_item::avg_price() const { if (units_sold) return revenue/units_sold; else return 0; } This definition is like the other functions we've seen: It has a return type of double and an empty parameter list enclosed in parentheses after the function name. What is new is the const following the parameter list and the form of the function name. The function name 上述定义和其他函数一样:该函数返回类型为 double,在函数名后面的圆括号起了一个空的形参表。新的内容则包括跟在形参表后面的 const 和函数名的形式。函数名: Sales_item::avg_price uses the scope operator (Section 1.2.2, p. 8) to say that we are defining the function named avg_price that is defined in the scope of the Sales_item class. 使用作用域操作符(第 1.2.2 节)指明函数 avg_price 是在类 Sales_item 的作用域范围内定义的。 The const that follows the parameter list reflects the way we declared the member funcion inside the Sales_item header. In any definition, the return type and parameter list must match the declaration, if any, of the function. In the case of a member function, the declaration is as it appears in the class definition. If the function is declared to be a const member function, then the const after the parameter list must be included in the definition as well. 形参表后面的 const 则反映了在类 Sales_item 中声明成员函数的形式。在任何函数定义中,返回类型和形参表必须和函数声明(如果有的话)一致。对于成员函数,函数声明必须与其定义一致。如果函数被声明为 const 成员函数,那么函数定义时形参表后面也必须有 const。 We can now fully understand the first line of this code: It says we are defining the avg_price function from the Sales_item class and that the function is a const member. The function takes no (explicit) parameters and returns a double. 现在可以完全理解第一行代码了:这行代码说明现在正在定义类 Sales_item 的函数 avg_price,而且这是一个 const 成员函数,这个函数没有(显式的)形参,返回 double 类型的值。 The body of the function is easier to understand: It tests whether units_sold is nonzero and, if so, returns the result of dividing revenue by units_sold. If units_sold is zero, we can't safely do the divisiondividing by zero has undefined behavior. In this program, we return 0, indicating that if there were no sales the average price would be zero. Depending on the sophistication of our error-handling strategy, we might instead throw an exception (Section 6.13, p. 215). 函数体更加容易理解:检查 units_sold 是否为 0,如果不为 0,返回 revenue 除以 units_sold 的结果;如果 units_sold 是 0,不能安全地进行除法运算——除以 0 是未定义的行为。此时程序返回 0,表示没有任何销售时平均售价为 0。根据异常错误处理策略,也可以抛出异常来代替刚才的处理(第 6.13 节)。 7.7.3. Writing the Sales_item Constructor7.7.3. 编写 Sales_item 类的构造函数There's one more member that we need to write: a constructor. As we learned in Section 2.8 (p. 65), class data members are not initialized when the class is defined. Instead, data members are initialized through a constructor. 还必须编写一个成员,那就是构造函数。正如在第 2.8 节所学习的,在定义类时没有初始化它的数据成员,而是通过构造函数来初始化其数据成员。 Constructors Are Special Member Functions构造函数是特殊的成员函数A constructor is a special member function that is distinguished from other member functions by having the same name as its class. Unlike other member functions, constructors have no return type. Like other member functions they take a (possibly empty) parameter list and have a function body. A class can have multiple constructors. Each constructor must differ from the others in the number or types of its parameters. 构造函数是特殊的成员函数,与其他成员函数不同,构造函数和类同名,而且没有返回类型。而与其他成员函数相同的是,构造函数也有形参表(可能为空)和函数体。一个类可以有多个构造函数,每个构造函数必须有与其他构造函数不同数目或类型的形参。 The constructor's parameters specify the initializers that may be used when creating objects of the class type. Ordinarily these initializers are used to initialize the data members of the newly created object. Constructors usually should ensure that every data member is initialized. 构造函数的形参指定了创建类类型对象时使用的初始化式。通常,这些初始化式会用于初始化新创建对象的数据成员。构造函数通常应确保其每个数据成员都完成了初始化。 The Sales_item class needs to explicitly define only one constructor, the default constructor, which is the one that takes no arguments. The default constructor says what happens when we define an object but do not supply an (explicit) initializer: Sales_item 类只需要显式定义一个构造函数:没有形参的默认构造函数。默认构造函数说明当定义对象却没有为它提供(显式的)初始化式时应该怎么办: vector<int> vi; // default constructor: empty vector string s; // default constructor: empty string Sales_item item; // default constructor: ??? We know the behavior of the string and vector default constructors: Each of these constructors initializes the object to a sensible default state. The default string constructor generates an empty string, the one that is equal to "". The default vector constructor generates a vector with no elements. 我们知道 string 和 vector 类默认构造函数的行为:这些构造函数会将对象初始化为合理的默认状态。string 的默认构造函数会产生空字符串上,相当于 ""。vector 的默认构造函数则生成一个没有元素的 vector 向量对象。 Similarly, we'd like the default constructor for Sales_items to generate an empty Sales_item. Here "empty" means an object in which the isbn is the empty string and the units_sold and revenue members are initialized to zero. 同样地,我们希望类 Sales_items 的默认构造函数为它生成一个空的 Sales_item 对象。这里的“空”意味着对象中的 isbn 是空字符串,units_sold 和 revenue 则初始化为 0。 Defining a Constructor构造函数的定义Like any other member function, a constructor is declared inside the class and may be defined there or outside the class. Our constructor is simple, so we will define it inside the class body: 和其他成员函数一样,构造函数也必须在类中声明,但是可以在类中或类外定义。由于我们的构造函数很简单,因此在类中定义它: 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 members as before private: std::string isbn; unsigned units_sold; double revenue; }; Before we explain the constructor definition, note that we put the constructor in the public section of the class. Ordinarily, and certainly in this case, we want the constructor(s) to be part of the interface to the class. After all, we want code that uses the Sales_item type to be able to define and initialize Sales_item objects. Had we made the constructor private, it would not be possible to define Sales_item objects, which would make the class pretty useless. 在解释任何构造函数的定义之前,注意到构造函数是放在类的 public 部分的。通常构造函数会作为类的接口的一部分,这个例子也是这样。毕竟,我们希望使用类 Sales_item 的代码可以定义和初始化类 Sales_item 的对象。如果将构造函数定义为 private 的,则不能定义类 Sales_item 的对象,这样的话,这个类就没有什么用了。 As to the definition itself 对于定义本身:
// default constructor needed to initialize members of built-in type
Sales_item(): units_sold(0), revenue(0.0) { }
it says that we are defining a constructor for the Sales_item class that has an empty parameter list and an empty function body. The interesting part is the colon and the code between it and the curly braces that define the (empty) function body. 上述语句说明现在正在定义类 Sales_item 的构造函数,这个构造函数的形参表和函数体都为空。令人感兴趣的是冒号和冒号与定义(空)函数体的花括号之间的代码。 Constructor Initialization List构造函数和初始化列表The colon and the following text up to the open curly is the constructor initializer list. A constructor initializer list specifies initial values for one or more data members of the class. It follows the constructor parameter list and begins with a colon. The constructor initializer is a list of member names, each of which is followed by that member's initial value in parentheses. Multiple member initializations are separated by commas. 在冒号和花括号之间的代码称为构造函数的初始化列表。构造函数的初始化列表为类的一个或多个数据成员指定初值。它跟在构造函数的形参表之后,以冒号开关。构造函数的初始化式是一系列成员名,每个成员后面是括在圆括号中的初始值。多个成员的初始化用逗号分隔。 This initializer list says that both the units_sold and revenue members should be initialized to 0. Whenever a Sales_item object is created, these members will start out as 0. We need not specify an initial value for the isbn member. Unless we say otherwise in the constructor initializer list, members that are of class type are automatically initialized by that class' default constructor. Hence, isbn is initialized by the string default constructor, meaning that isbn initially is the empty string. Had we needed to, we could have specified a default value for isbn in the initializer list as well. 上述例题的初始化列表表明 units_sold 和 revenue 成员都应初始化为 0。每当创建 Sales_item 对象时,它的这两个成员都以初值 0 出现。而 isbn 成员可以不必准确指明其初值。除非在初始化列表中有其他表述,否则具有类类型的成员皆被其默认构造函数自动初始化。于是,isbn 由 string 类的默认构造函数初始化为空串。当然,如果有必要的话,也可以在初始化列表中指明 isbn 的默认初值。 Having explained the initializer list, we can now understand the constructor: Its parameter list and the function body are both empty. The parameter list is empty because we are defining the constructor that is run by default, when no initializer is present. The body is empty because there is no work to do other than initializing units_sold and revenue. The initializer list explicitly initializes units_sold and revenue to zero and implicitly initializes isbn to the empty string. Whenever we create a Sales_item object, the data members will start out with these values. 解释了初始化列表后,就可以深入地了解这个构造函数了:它的形参表和函数体都为空。形参表为空是因为正在定义的构造函数是默认调用的,无需提供任何初值。函数体为空是因为除了初始化 units_sold 和 revenue 成员外没有其他工作可做了。初始化列表显式地将 units_sold 和 revenue 初始化为 0,并隐式地将 isbn 初始化为空串。当创建新 Sales_item 对象时,数据成员将以这些值出现。 Synthesized Default Constructor合成的默认构造函数
The compiler-created default constructor is known as a synthesized default constructor. It initializes each member using the same rules as are applied for variable initializations (Section 2.3.4, p. 50). Members that are of class type, such as isbn, are initialized by using the default constructor of the member's own class. The initial value of members of built-in type depend on how the object is defined. If the object is defined at global scope (outside any function) or is a local static object, then these members will be initialized to 0. If the object is defined at local scope, these members are uninitialized. As usual, using an uninitialized member for any purpose other than giving it a value is undefined. 由编译器创建的默认构造函数通常称为默认构造函数,它将依据如同变量初始化(第 2.3.4 节)的规则初始化类中所有成员。对于具有类类型的成员,如 isbn,则会调用该成员所属类自身的默认构造函数实现初始化。内置类型成员的初值依赖于对象如何定义。如果对象在全局作用域中定义(即不在任何函数中)或定义为静态局部对象,则这些成员将被初始化为 0。如果对象在局部作用域中定义,则这些成员没有初始化。除了给它们赋值之外,出于其他任何目的对未初始化成员的使用都没有定义。
Because the synthesized constructor does not automatically initialize members of built-in type, we had to define the Sales_item default constructor explicitly. 由于合成的默认构造函数不会自动初始化内置类型的成员,所以必须明确定义 Sales_item 类的默认构造函数。 7.7.4. Organizing Class Code Files7.7.4. 类代码文件的组织As we saw in Section 2.9 (p. 67), class declarations ordinarily are placed in headers. Usually, member functions defined outside the class are put in ordinary source files. C++ programmers tend to use a simple naming convention for headers and the associated class definition code. The class definition is put in a file named type .h or type .H, where type is the name of the class defined in the file. Member function definitions usually are stored in a source file whose name is the name of the class. Following this convention we put the Sales_item class definition in a file named Sales_item.h. Any program that wants to use the class must include that header. We should put the definition of our Sales_item functions in a file named Sales_item.cc. That file, like any other file that uses the Sales_item type, would include the Sales_item.h header. 正如在第 2.9 节提及的,通常将类的声明放置在头文件中。大多数情况下,在类外定义的成员函数则置于源文件中。C++ 程序员习惯使用一些简单的规则给头文件及其关联的类定义代码命名。类定义应置于名为 type.h 或 type.H 的文件中,type 指在该文件中定义的类的名字。成员函数的定义则一般存储在与类同名的源文件中。依照这些规则,我们将类 Sales_item 放在名为 Sales_item.h 的文件中定义。任何需使用这个类的程序,都必须包含这个头文件。而 Sales_item 的成员函数的定义则应该放在名为 Sales_item.cc 的文件中。这个文件同样也必须包含 Sales_item.h 头文件。
![]() |