普通的非 const 成员函数中,this 是一个 const 指针。
指针所保存的地址不能改变,但是可以改变其指向的对象。
但是,const 成员函数中,this 就是一个 const 类型的 const 指针。
基于 const 的重载
为解决上述问题,必须定义两个 display 操作,一个是 const 的,一个是 非const 的。
const 对象只能使用 const 成员。非const 对象可以使用任一成员,非 const 成员是一个更好的匹配。
构造函数不能声明为 const
某些成员必须在构造函数的初始化列表中进行初始化。
比如:没有默认构造函数的类类型成员、 const 或者引用类型 的成员。
非类类型的数据成员,进行赋值,或者,使用初始化式,在结果和性能上,是等价的。
但,有2个例外(const 类型、 引用类型)
成员初始化的次序
成员被初始化的次序,是跟,成员定义的次序一致的。
通常,初始化的次序,无关紧要。但也有例外:
类类型的数据成员的初始化式
指针所保存的地址不能改变,但是可以改变其指向的对象。
但是,const 成员函数中,this 就是一个 const 类型的 const 指针。
既不能改变 this 的指针值,也不能改变其指向的对象。
如果增加 const 成员函数 display ,在 cout 中打印内容。
则:display 返回的是 const 引用,因此,display 将无法嵌入到一个长表达式中。
代码如下:
myScreen.move(4, 0).set('#').display(cout);
myScreen.display(cout).set('#'); // 这里 display 返回的 const 引用,无法使用 set 方法。将报错
基于 const 的重载
为解决上述问题,必须定义两个 display 操作,一个是 const 的,一个是 非const 的。
const 对象只能使用 const 成员。非const 对象可以使用任一成员,非 const 成员是一个更好的匹配。
class Screen
{
public:
typedef string::size_type index;
Screen& display(ostream &os)
{
do_display(os);
return *this;
}
const Screen& display(ostream &os) const
{
do_display(os);
return *this;
}
private:
void do_display(ostream &os) const
{
// 可变数据成员 access_ctr 即使在 const 成员函数中,
// 也是可以修改的
++access_ctr;
os << contents;
}
// 可变数据成员 access_ctr 加上 mutable 关键字
mutable size_t access_ctr;
string contents;
index cursor;
index height, width;
};
Screen myScreen(5,3);
const Screen blank(5,3);
// set 返回的是 非const 对象
// 因此,会匹配 display 的 非const 重载版本
myScreen.set('#').display(cout);
// blank 是 const 对象,匹配 display 的 const 重载版本
blank.display(cout);
构造函数不能声明为 const
class Sales_item
{
public:
Sales_item() const; // 报错
};
构造函数可以定义在 类的内部,或者外部
构造函数的初始化式,只在构造函数的定义中,而不是声明中指定。
class Sales_item
{
public:
Sales_item(const string&);
private:
string isbn;
int units_sold;
double revenue;
};
// 显示初始化
Sales_item::Sales_item(const string &book):
isbn(book), units_sold(0), revenue(0.0){}
// 虽然,没有进行显示初始化,
// 但是,实际上,构造函数在执行之前,都要初始化 isbn 成员。
// 没有为类成员提供初始化,则编译器隐式地使用成员类型的默认构造函数
// 内置类型的成员,不会进行隐式初始化,
// 对于它们,进行初始化、或者在执行阶段进行赋值,都同样的效果。
// 在执行构造函数的函数体时,isbn 已经有值了,只是在执行之后,值被覆盖
// 构造函数分2个阶段执行:初始化阶段、函数体内普通的计算执行阶段
Sales_item::Sales_item(const string &book)
{
isbn = book;
units_sold = 0;
revenue = 0.0;
}
某些成员必须在构造函数的初始化列表中进行初始化。
比如:没有默认构造函数的类类型成员、 const 或者引用类型 的成员。
非类类型的数据成员,进行赋值,或者,使用初始化式,在结果和性能上,是等价的。
但,有2个例外(const 类型、 引用类型)
class ConstRef
{
public:
ConstRef(int ii);
private:
int i;
const int ci;
int &ri;
};
ConstRef::ConstRef(int ii)
{
i = ii;
ci = ii; // Error:const 类型,无法赋值
ri = i; // Error:引用类型,也要在初始化列表中指定
}
ConstRef::ConstRef(int ii):i(ii), ci(ii), ri(ii){}; // 正确
成员初始化的次序
成员被初始化的次序,是跟,成员定义的次序一致的。
通常,初始化的次序,无关紧要。但也有例外:
class X
{
int i;
int j;
public:
// i先被定义,再定义 j
// 因此,以下初始化的实际过程:
// 先用未初始化的 j,来初始化i
// 再用 val 初始化 j
X(int val):j(val), i(j){};
// 建议写法如下:
// 使用形参来初始化成员,初始化列表的顺序,
// 跟成员定义的次序一致。
// 并且避免使用对象的数据成员,来初始化其他成员
// X(int val):i(val), j(val){};
};
Sale_item(const string &book, int cnt, double price):
isbn(book), units_sold(cnt), revenue(cnt * price){}
// 初始化式,可以是任意的表达式(cnt * price)
类类型的数据成员的初始化式
Sale_item():isbn(10, '9'), units_sold(0), revenue(0) {}
// isbn(10, '9') 使用了 string 的构造函数,生成一个string,由10个9组成