0. 类的成员函数

函数原型定义了所有和函数相关的类型信息:
  函数返回类型是什么、函数的名字、应该给这个函数传递什么类型的实参。

类的成员函数,其原型必须在类中定义。 但是,函数体则既可以在类中也可以在类外定义。

以下是一个 Sales_item 类的定义。

1 class Sales_item {
 2     // operations on Sales_item objects
 3     public:
 4         double avg_price() const;
 5         bool same_isbn(const Sales_item &rhs) const
 6         { return isbn == rhs.isbn; }
 7 
 8     // private members
 9     private:
10         std::string isbn;
11         unsigned units_sold;
12         double revenue;
13 };

 

1. 定义成员函数的函数体

类的所有成员都必须在类定义的花括号里面声明,此后,就不能再为类增加任何成员。

类的成员函数必须如声明的一般定义。类的成员函数既可以在类的定义内也可以在类的定义外定义。

编译器隐式地将在类内定义的成员函数当作内联函数。

再详细观察函数 same_isbn

1 bool same_isbn(const Sales_item &rhs) const {
2     return isbn == rhs.isbn;    
3 }

在这个函数中,块中只有一个语句,比较两个 Sales_item 对象的数据成员 isbn

 

首先要注意的是:尽管类的成员变量 isbn 是 private的。上述语句却没有任何错误!But, Why?

( 因为,类的成员函数可以访问该类的 private成员变量的。)

更有意思的是,函数从哪个 Sales_item

函数涉及到 isbn 和 rhs.isbn。很明显,rhs.isbn 使用的是传递给此函数的实参的 isbn

而这个没有前缀的 isbn 指的是用于调用函数的对象的 isbn 

2. 成员函数含有额外的、隐含的形参

调用成员函数时,实际上是使用对象来调用的。例如通过名为 total 的对象来执行 same_isbn

if (total.same_isbn(trans))

在这个调用中,传递了对象 trans。作为执行调用的一部分,使用对象 trans 初始化形参 rhs。于是,rhs.isbn 是 trans.isbn

而没有前缀的 isbn 使用了相同的实参绑定过程,使之与名为 total

每个成员函数都有一个额外的、隐含的形参将该成员函数与调用该函数的类对象捆绑在一起。

当调用名为 total 的对象的 same_isbn

而 same_isbn 函数使用 isbn 时,就隐式地使用了调用该函数的对象的 isbn

这个函数调用的效果是比较 total.isbn 和 trans.isbn 

3. this 指针的引用

每个成员函数(除了 static成员函数外)都有一个额外的、隐含的形参 this。

在调用成员函数时,形参 this初始化为调用函数的对象的地址。

为了理解成员函数的调用,可考虑下面的语句:

total.same_isbn(trans);

就如编译器这样重写这个函数调用:

// pseudo-code illustration of how a call to a member function is translated
Sales_item::same_isbn(&total, trans);

 在这个调用中,函数 same_isbn 中的数据成员 isbn 属于对象 total。

4. const 成员函数的引用

现在,可以理解跟在 Sales_item 成员函数声明的形参表后面的 const 所起的作用了:
const 改变了隐含的 this形参的类型。
  在调用 total.same_isbn(trans) 时,隐含的 this形参将是一个指向 total 对象的 const 类型的指针。
  就像如下编写 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);
}

用这种方式使用 const的函数称为 常量成员函数

由于 this是指向 const对象的指针,const成员函数不能修改调用该函数的对象。

因此,函数 avg_price 和函数 same_isbn

注意:const对象、指向 const对象的指针或引用只能用于调用其 const成员函数,不能用它们来调用非 const成员函数。) 

5. this 指针的使用

在成员函数中,不必显式地使用 this指针来访问被调用函数所属对象的成员。

对这个类的成员的任何没有前缀的引用,都被假定为通过指针 this实现的引用:

1 bool same_isbn(const Sales_item &rhs) const {
2   return isbn == rhs.isbn;
3 }

在这个函数中 isbn 的用法与 this->units_sold 或 this->revenue

由于 this指针是隐式定义的,因此不需要在函数的形参表中包含 this

但是,在函数体中可以显式地使用 this指针。如下定义函数 same_isbn

1 bool same_isbn(const Sales_item &rhs) const { 
2     return this->isbn == rhs.isbn; 
3 }

6. 在类外定义成员函数

在类的定义外面定义成员函数必须指明它们是类的成员:

1 double Sales_item::avg_price() const
2 {
3     if (units_sold)
4         return revenue/units_sold;
5     else
6         return 0;
7 }

该函数返回类型为 double,在函数名后面的圆括号起了一个空的形参表。

函数名:Sales_item::avg_price使用作用域操作符指明函数 avg_price 是在类 Sales_item

形参表后面的 const则反映了在类 Sales_item

在任何函数定义中,返回类型和形参表必须和函数声明(如果有的话)一致。类的成员函数也不例外。

如果函数被声明为 const成员函数,那么函数定义时形参表后面也必须有 const。

现在可以完全理解第一行代码了:
  这行代码说明现在正在定义类 Sales_item 的函数 avg_price,
  而且这是一个 const 成员函数,这个函数没有(显式的)形参,返回 double

函数体更加容易理解:
  检查 units_sold 是否为 0,如果不为 0,返回 revenue 除以 units_sold 的结果;
  如果 units_sold 是 0,不能安全地进行除法运算——除以 0 是未定义的行为。
  此时程序返回 0,表示没有任何销售时平均售价为 0。根据异常错误处理策略,也可以抛出异常来代替刚才的处理。 

7. 类的构造函数

可以在定义类时不显示的初始化它的数据成员,而是通过构造函数来初始化其数据成员。

构造函数是特殊的成员函数,与其他成员函数不同,构造函数和类同名,而且没有返回类型。

而与其他成员函数相同的是,构造函数也有形参表(可能为空)和函数体。

一个类可以有多个构造函数,每个构造函数必须有与其他构造函数不同数目或类型的形参。

构造函数的形参指定了创建类类型对象时使用的初始化式。

通常,这些初始化式会用于初始化新创建对象的数据成员。构造函数通常应确保其每个数据成员都完成了初始化。

Sales_item 类只需要显式定义一个构造函数:没有形参的默认构造函数

默认构造函数说明当定义对象却没有为它提供(显式的)初始化式时应该怎么办:

vector<int> vi;       // default constructor: empty vector
     string s;             // default constructor: empty string
     Sales_item item;      // default constructor: ???

我们知道 string和 vector

string的默认构造函数会产生空字符串上,相当于 ""。vector的默认构造函数则生成一个没有元素的 vector向量对象。

同样地,我们希望类 Sales_items 的默认构造函数为它生成一个空的 Sales_item

这里的“空”意味着对象中的 isbn 是空字符串,units_sold 和 revenue 

8. 构造函数的定义

和其他成员函数一样,构造函数也必须在类中声明,但是可以在类中或类外定义。

1 class Sales_item {
 2     // operations on Sales_item objects
 3     public:
 4         double avg_price() const;
 5         bool same_isbn(const Sales_item &rhs) const{ 
 6             return isbn == rhs.isbn; 
 7         }
 8         // default constructor needed to initialize members of built-in type
 9         Sales_item(): units_sold(0), revenue(0.0) { }
10 
11         // private members as before
12     private:
13         std::string isbn;
14         unsigned units_sold;
15         double revenue;
16 };

在解释任何构造函数的定义之前,注意到构造函数是放在类的 public

通常构造函数会作为类的接口的一部分,这个例子也是这样。

毕竟,我们希望使用类 Sales_item 的代码可以定义和初始化类 Sales_item

如果将构造函数定义为 private的,则不能定义类 Sales_item

对于定义本身:

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

在冒号和花括号之间的代码称为构造函数的初始化列表

构造函数的初始化列表为类的一个或多个数据成员指定初值。它跟在构造函数的形参表之后,以冒号开关。

构造函数的初始化式是一系列成员名,每个成员后面是括在圆括号中的初始值。多个成员的初始化用逗号分隔。 

上述例题的初始化列表表明 units_sold 和 revenue

每当创建 Sales_item 对象时,它的这两个成员都以初值 0 出现。而 isbn

除非在初始化列表中有其他表述,否则具有类类型的成员皆被其默认构造函数自动初始化。

于是,isbn 由 string类的默认构造函数初始化为空串。当然,如果有必要的话,也可以在初始化列表中指明 isbn

解释了初始化列表后,就可以深入地了解这个构造函数了:

  它的形参表和函数体都为空。形参表为空是因为正在定义的构造函数是默认调用的,无需提供任何初值。

units_sold 和 revenue
units_sold 和 revenue 初始化为 0,并隐式地将 isbn
Sales_item

注意:如果没有为一个类显式定义任何构造函数,编译器将自动为这个类生成默认构造函数。

由编译器创建的默认构造函数通常称为默认构造函数,它将依据如同变量初始化的规则初始化类中所有成员。

对于具有类类型的成员,如 isbn,则会调用该成员所属类自身的默认构造函数实现初始化。

内置类型成员的初值依赖于对象如何定义。

如果对象在全局作用域中定义(即不在任何函数中)或定义为静态局部对象,则这些成员将被初始化为 0。

如果对象在局部作用域中定义,则这些成员没有初始化。

除了给它们赋值之外,出于其他任何目的对未初始化成员的使用都没有定义。

由于合成的默认构造函数不会自动初始化内置类型的成员,所以此处必须明确定义 Sales_item