c++ 07

时间:2024-11-28 11:36:38

一、多重继承
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");