一、多重继承
1)子类同时拥有两个或两个以上的基类,同时继承了所有基类的属性和行为。
销售员 经理
\ /
销售经理
汽车 客用特性 商用特性
\ | /
公共汽车
2)内存结构,按照继承表的顺序,从低到高地址一次排列各个基类子对象。将子类对象的地址隐式或者静态转换为基类指针,编译器会做地址计算,以保证基类指针的类型和其所指向的对象一致,但是重解释类型转换(reinterpret_cast)不做此计算。
3)防止名字冲突。或者由用户程序通过作用域限定解决冲突,或者借助using声明以重载的方式解决冲突,再或者通过汇聚替代,在汇聚子类中提供对产生冲突的函数的隐藏版本,并在该隐藏版本中通过正确的逻辑选择特定基类的实现。
4)钻石继承问题:公共基类子对象在最终子类对象中存在多个实例,沿着不同的继承路径所访问到的公共基类子对象可能不一样,由此导致数据不一致问题。
A
/ \
B C
\ /
D
为了解决钻石继承问题,需要设法让公共基类子对象在最终子类对象中仅有唯一的实例,且为所有中间子类对象所共享——虚继承。
二、多态
形状:位置,绘制
矩形:宽度、高度,绘制
圆形:半径,绘制
Shape
/ \
Rect Circle
如果将基类中的某个成员函数声明为虚函数,那么其子类中与该函数具有相同原型的成员函数就也成为虚函数,并对基类中的版本构成覆盖(override)。通过一个指向子类对象的基类指针,或者引用子类对象的基类引用,调用这个虚函数时,实际被调用的将是子类中的覆盖版本。这种特性被称为多态。
三、关于覆盖
1.基类中成员函数必须是虚函数。
2.子类中成员函数必须与基类中的虚函数拥有完全相同的函数名、形参表和常属性。
3.如果基类中的虚函数返回基本类型,那么子类覆盖版本的返回类型必须与基类完全相同。如果基类中的虚函数返回类类型的指针或者引用,那么子类覆盖版本的返回类型可以是基类返回类型的子类。
class X { ... };
class Y : public X { ... };
class A {
virtual int foo (void) { ... }
virtual X* bar (void) { ... }
};
class B : public A {
int foo (void) { ... }
Y* bar (void) { ... }
};
4.子类中的覆盖版本不能比基类版本抛出更多的异常。
5.子类中覆盖版本与基类版本的访控属性无关。
class A {
public:
virtual int foo (void) { ... }
};
class B : public A {
private
int foo (void) { ... }
};
B b;
b.foo (); // ERROR !
A* p = &b;
p -> foo (); // OK !
四、多态=虚函数+指针/引用
B b;
A a = (A)b;
a.foo (); // A::foo()
class A {
public:
void foo (void) {
this -> bar ();
}
virtual void bar (void) {
cout << 'A' << endl;
}
};
class B : public A {
private:
void bar (void) {
cout << 'B' << endl;
}
};
int main (void) {
B b;
b.foo (); // B
}
class A {
public:
A (void) {
this -> bar ();
}
~A (void) {
this -> bar ();
}
virtual void bar (void) {
cout << 'A' << endl;
}
};
class B : public A {
private:
void bar (void) {
cout << 'B' << endl;
}
};
int main (void) {
B b; // 'A'
}
五、虚函数的实现原理——虚函数表
class A {
public:
virtual void foo (void) { ... }
virtual void bar (void) { ... }
};
class B : public A {
public:
void foo (void) { ... }
};
编译器对于虚函数调用,不会根据调用者的类型生成对特定成员函数调用指令,相反生成一组特殊的指令,该组指令在运行时被执行,完成以下工作:
1)确定调用对象的真实类型;
2)从实际调用对象中获取虚函数表指针;
3)找到与所调用函数相对应的函数地址;
4)调用该函数。
——动态绑定(后期绑定)。
虚函数与普通成员函数相比,执行开销会更大,虚函数不能做内联优化。
六、纯虚函数、抽象类和纯抽象类
形如:
virtual 返回类型 函数名 (形参表) = 0;
的虚函数称为纯虚函数。
至少含有一个纯虚函数的类,称为抽象类。抽象类不能实例化对象。
除了构造函数和析构函数以外全部由纯虚函数组成类,称为纯抽象类。
七、运行时类型信息——RTTI
在运行阶段获得对象的类型信息的方法。
运算符:typeid
对象:typeinfo - name()、==、!=
支持多态。
dynamic_cast - 在多态父子类指针或引用之间转换。对于指针转换失败返回NULL,对于引用转换失败抛出bad_cast异常。
八、虚析构函数
将基类的析构函数定义为虚函数,delete一个指向子类对象的基类指针,实际被执行的就是子类的析构函数,而子类的析构函数又会自动调用基类的析构函数,从而保证子类和基类中所有的资源都被析构干净。
九、不是所有的函数都能定义为虚函数
构造函数
静态函数
全局函数
十、模板方法模式
class PDFReader {
public:
void open (const char* file) {
解析文本;
this -> drawText ();
解析矩形;
drawRect ();
}
virtual void drawText (void) = 0;
virtual void drawRect (void) = 0;
};
class PDFRender : public PDFReader {
void drawText (void) { ... }
void drawRect (void) { ... }
};
PDFRender pr;
pr.open ("1.pdf");