(二十六)C++篇-面向对象编程(二)

2022-07-12  本文已影响0人  GoodTekken

(1)派生类虚函数调用基类版本时,必须显式使用作用域操作符。如果派生类函数忽略了这样做,则函数调用会在运行时确定并且将是一个自身调用,从而导致无穷递归。
Item_base *baseP = &derived;
double d = baseP->Item_base::net_price(42);

(如果是私有继承,基类的的所有成员在派生类中为 private 成员)
(2)派生类可以恢复继承成员的访问级别,但不能使访问级别比基类中原来指定的更严格或更宽松。(使用using,虽然是私有继承,但能恢复到基类的访问等级)

class Derived : private Base {
public:
// maintain access levels for members related to the size of the
object
using Base::size;
protected:
using Base::n;
// ...
};

(3)派生类的构造函数受继承关系的影响,每个派生类构造函数除了初始化自己的数据成员之外,还要初始化基类。

(4)定义默认构造函数

class Bulk_item : public Item_base {
public:
Bulk_item(): min_qty(0), discount(0.0) { }
// as before
};

运行这个构造函数的效果是,首先使用 Item_base 的默认构造函数初始化Item_base 部分,那个构造函数将 isbn 置为空串并将 price 置为 0。Item_base 的构造函数执行完毕后,再初始化 Bulk_item 部分的成员并执行构造函数的函数体(函数体为空)。

(5)向基类构造函数传递实参
派生类构造函数的初始化列表只能初始化派生类的成员,不能直接初始化继承成员。 相反派生类构造函数通过将基类包含在构造函数初始化列表中来间接初始化继承成员。

class Bulk_item : public Item_base {
public:
Bulk_item(const std::string& book, double sales_price,
std::size_t qty = 0, double disc_rate = 0.0):
Item_base(book, sales_price),
min_qty(qty), discount(disc_rate) { }
// as before
};

构造函数初始化列表为类的基类和成员提供初始值,它并不指定初始化的执行次序。首先初始化基类,然后根据声明次序初始化派生类的成员。

(6)构造函数只能初始化其直接基类的原因是每个类都定义了自己的接口。定义 Disc_item 时,通过定义它的构造函数指定了怎样初始化Disc_item 对象。一旦类定义了自己的接口,与该类对象的所有交互都应该通过该接口,即使对象是派生类对象的一部分也不例外。同样, 派生类构造函数不能初始化基类的成员且不应该对基类成员赋值。如果那些成员为 public 或 protected,派生构造函数可以在构造函数函数体中给基类成员赋值,但是,这样做会违反基类的接口。派生类应通过使用基类构造函数尊重基类的初始化意图,而不是在派生类构造函数函数体中对这些成员赋值。

(7)复制控制和继承
只包含类类型或内置类型数据成员、不含指针的类一般可以使用合成操作,复制、赋值或撤销这样的成员不需要特殊控制。具有指针成员的类一般需要定义自己的复制控制来管理这些成员。

(8)析构函数
对象的撤销顺序与构造顺序相反:首先运行派生析构函数,然后按继承层次依次向上调用各基类析构函数。

(9)虚析构函数
删除指向动态分配对象的指针时,需要运行析构函数在释放对象的内存之前清除对象。处理继承层次中的对象时,指针的静态类型可能与被删除对象的动态类型不同,可能会删除实际指向派生类对象的基类类型指针。

如果删除基类指针,则需要运行基类析构函数并清除基类的成员,如果对象实际是派生类型的,则没有定义该行为。要保证运行适当的析构函数,基类中的析构函数必须为虚函数:

class Item_base {
public:
// no work, but virtual destructor needed
// if base pointer that points to a derived object is ever deleted
virtual ~Item_base() { }
};

如果析构函数为虚函数,那么通过指针调用时,运行哪个析构函数将因指针所指对象类型的不同而不同:

Item_base *itemP = new Item_base; // same static and dynamic type
delete itemP; // ok: destructor for Item_base called
itemP = new Bulk_item; // ok: static and dynamic types differ
delete itemP; // ok: destructor for Bulk_item called

像其他虚函数一样,析构函数的虚函数性质都将继承。因此,如果层次中根类的析构函数为虚函数,则派生类析构函数也将是虚函数,无论派生类显式定义析构函数还是使用合成析构函数,派生类析构函数都是虚函数。基类析构函数是三法则的一个重要例外。三法则指出,如果类需要析构函数,则类几乎也确实需要其他复制控制成员。基类几乎总是需要构造函数,从而可以将析构函数设为虚函数。如果基类为了将析构函数设为虚函数则具有空析构函数,那么,类具有析构函数并不表示也需要赋值操作符或复制构造函数。即使析构函数没有工作要做,继承层次的根类也应该定义一个虚析构函数。

(10)构造函数和赋值操作符不是虚函数
在复制控制成员中,只有析构函数应定义为虚函数,构造函数不能定义为虚函数。构造函数是在对象完全构造之前运行的,在构造函数运行的时候,对象的动态类型还不完整。

(11)名字查找在编译时发生

Bulk_item bulk;
Bulk_item *bulkP = &bulk; // ok: static and dynamic types are the same
Item_base *itemP = &bulk; // ok: static and dynamic types differ
bulkP->discount_policy(); // ok: bulkP has type Bulk_item*
itemP->discount_policy(); // error: itemP has type Item_base*

重新定义 itemP 的访问是错误的,因为基类类型的指针(引用或对象)只能访问对象的基类部分,而在基类中没有定义 discount_policy 成员。

(12)名字冲突与继承
与基类成员同名的派生类成员将屏蔽对基类成员的直接访问。

struct Base {
Base(): mem(0) { }
protected:
int mem;
};
struct Derived : Base {
Derived(int i): mem(i) { } // initializes Derived::mem
int get_mem() { return mem; } // returns Derived::mem
protected:
int mem; // hides mem in the base
};

get_mem 中对 mem 的引用被确定为使用 Derived 中的名字。如果编写如下代码:

Derived d(42);
cout << d.get_mem() << endl; // prints 42

则输出将是 42。

可以使用作用域操作符访问被屏蔽的基类成员:

int get_base_mem() { return Base::mem; }
};

作用域操作符指示编译器在 Base 中查找 mem。设计派生类时,只要可能,最好避免与基类成员的名字冲突。

上一篇下一篇

猜你喜欢

热点阅读