举一个例子:
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; };
调用成员函数时,实际上是使用对象来调用的。例如,调用 same_isbn,是通过名为 total 的对象来执行 same_isbn 函数的:
if (total.same_isbn(trans))
定义体如下:
bool same_isbn(const Sales_item &rhs) const { return isbn == rhs.isbn; }
在这个调用中,传递了对象 trans。作为执行调用的一部分,使用对象 trans 初始化形参 rhs。于是,rhs.isbn 是 trans.isbn 的引用。而没有前缀的 isbn 使用了相同的实参绑定过程,使之与名为 total 的对象绑定起来。每个成员函数都有一个额外的、隐含的形参将该成员函数与调用该函数的类对象捆绑在一起。当调用名为 total 的对象的 same_isbn 时,这个对象也传递给了函数。而 same_isbn 函数使用 isbn 时,就隐式地使用了调用该函数的对象的 isbn 成员。这个函数调用的效果是比较 total.isbn 和 trans.isbn 两个值。可以参看下下面将要讲到的this指针的使用。
每个成员函数都有一个额外的、隐含的形参 this。在调用成员函数时,形参 this 初始化为调用函数的对象的地址。
跟在 Sales_item 成员函数声明的形参表后面的 const 所起的作用:const 改变了隐含的 this 形参的类型(即指针所指向的对象的数据不能够被更改)。在调用 total.same_isbn(trans) 时,隐含的 this 形参将是一个指向 total 对象的 const Sales_Item* 类型的指针。就像如下编写 same_isbn 的函数体一样:
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)只能用于调用其 const 成员函数,如果尝试用它们来调用非 const 成员函数,则是错误的。
在成员函数中,不必显式地使用 this 指针来访问被调用函数所属对象的成员。对这个类的成员的任何没有前缀的引用,都被假定为通过指针 this 实现的引用:
bool same_isbn(const Sales_item &rhs) const { return isbn == rhs.isbn; }
由于 this 指针是隐式定义的,因此不需要在函数的形参表中包含 this 指针,实际上,这样做也是非法的。但是,在函数体中可以显式地使用 this 指针。如下定义函数 same_isbn 尽管没有必要,但是却是合法的:
bool same_isbn(const Sales_item &rhs) const { return this->isbn == rhs.isbn; }
还必须编写一个成员,那就是构造函数。在定义类Sales_item时没有初始化它的数据成员,而是通过构造函数来初始化其数据成员。
构造函数是特殊的成员函数,与其他成员函数不同,构造函数和类同名,而且没有返回类型。而与其他成员函数相同的是,构造函数也有形参表(可能为空)和函数体。一个类可以有多个构造函数,每个构造函数必须有与其他构造函数不同数目或类型的形参。构造函数的形参指定了创建类类型对象时使用的初始化式。通常,这些初始化式会用于初始化新创建对象的数据成员。构造函数通常应确保其每个数据成员都完成了初始化。
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 则初始化为 0。
和其他成员函数一样,构造函数也必须在类中声明,但是可以在类中或类外定义。由于我们的构造函数很简单,因此在类中定义它:
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; };
对于定义本身:
Sales_item(): units_sold(0), revenue(0.0) { }
这个构造函数的形参表和函数体都为空。在冒号和花括号之间的代码称为构造函数的初始化列表。构造函数的初始化列表为类的一个或多个数据成员指定初值。它跟在构造函数的形参表之后,以冒号开关。构造函数的初始化式是一系列成员名,每个成员后面是括在圆括号中的初始值。多个成员的初始化用逗号分隔。上述例题的初始化列表表明 units_sold 和 revenue 成员都应初始化为 0。每当创建 Sales_item 对象时,它的这两个成员都以初值 0 出现。而 isbn 成员可以不必准确指明其初值。除非在初始化列表中有其他表述,否则具有类类型的成员皆被其默认构造函数自动初始化。于是,isbn 由 string 类的默认构造函数初始化为空串。当然,如果有必要的话,也可以在初始化列表中指明 isbn 的默认初值。解释了初始化列表后,就可以深入地了解这个构造函数了:它的形参表和函数体都为空。形参表为空是因为正在定义的构造函数是默认调用的,无需提供任何初值。函数体为空是因为除了初始化 units_sold 和 revenue 成员外没有其他工作可做了。初始化列表显式地将 units_sold 和 revenue 初始化为 0,并隐式地将 isbn 初始化为空串。当创建新 Sales_item 对象时,数据成员将以这些值出现。