C++第六天

十二、多重继承与钻石继承
1.名字冲突问题
用过类名+作用域限定
A
  foo
B
  foo
C : A, B
C c;
c.A::foo
c.B::foo
汇聚替代:在子类中提供对有冲突标识符的隐藏版本,在该隐藏版本中通过重载等机制,指明调用的是哪个基类中的。
2.钻石继承问题
钻石继承:子类继承自多个基类,而这些基类又源自一个共同的基类。
       A
      / \
     B   C
      \ /
       D
问题:公共基类子对象在每个中间子类中各有一个实例,因此通过不同继承路径访问公共基类子对象中的数据会有不一致的问题。
解决:通过虚继承,使公共基类子对象在最终子类对象中只有一0份实例,而所有中间子类对象共享该实例。
虚继承:virtual、最终子类的构造函数可能需要显式指明公共基类子对象的构造方式。
十三、虚函数与多态
1.虚函数与多态的基本概念
       图形-位置、绘制
      /    \
  矩形      圆形
  长,宽    半径
  绘制      绘制
在基类中将一个成员函数声明为虚函数(在返回类型前面加上virtual关键字),其子类中的同原型成员函数就也成为虚函数,并且对基类中的版本形成覆盖。此时,通过一个指向子类对象的基类指针,或者引用子类对象的基类引用,调用该虚函数,实际被调用的是子类中的覆盖版本。这种语法现象被称为多态。
虚函数——覆盖——多态。
2.虚函数覆盖的限制
1)基类中的成员函数必须被声明为虚函数,子类中的成员函数与基类中虚函数的函数名、形参表、常属性完全相同。
2)如果基类中虚函数的返回类型是基本类型,那么子类中覆盖版本的返回类型必须与基类版本完全相同。
3)如果基类中虚函数的返回类型是类类型的指针或引用,那么子类中覆盖版本的返回类型也可以是基类版本返回类型的子类。
4)子类中的覆盖版本不能比基类中的虚函数抛出更多的异常。
5)子类中覆盖版本的访控属性与基类无关。
3.注意严格区分重载、隐藏和覆盖。
class Visitor {
public:
  virtual bool visit (int credit); // A
  virtual bool visit (double cash); // B
};
class ValidVisitor : public Visitor {
public:
  bool visit (int credit); // C
};
class MyValidVisitor : public ValidVisitor {
public:
  bool visit (double cash); // D
};
class Dumy : public Visitor {
public:
  void visit (int credit); // E
};
B和A:重载
C和A:覆盖
C和B:隐藏
D和C:隐藏
D和B:覆盖
E和A:错误的覆盖
4.纯虚函数、抽象类与纯抽象类
纯虚函数:在基类中被定义为如下形式的成员函数:
virtual 返回类型 成员函数名 (形参表)=0;
被称为纯虚函数。
抽象类:至少包含一个纯虚函数的类。抽象类不能被实例化为对象。抽象类的子类如果没有对基类中所有的纯虚函数提供覆盖定义,那么该子类也是抽象类。
纯抽象类:完全由纯虚函数(除了构造和析构函数)组成的抽象类。<接口>
虚基类是从虚继承来的,为了解决钻石继承问题。
5.虚函数表与动态绑定
虚函数表是一个存放虚函数地址的函数指针数组。每个由包含虚函数的类所创建的对象中都有一个指向该虚函数表的指针。当通过指向子类对象的基类指针,或者引用子类对象的基类引用调用基类中的虚函数时,实际上是根据该指针或引用实际指向的对象中的虚函数表获取函数地址并调用的。
//
vftbl.cpp
/
对于A的子类B:
A* pa = new B (...);
当前编译器看到
pa -> foo ();
并不直接生成函数调用代码,相反它会自动生成一些列操作指令,完成以下动作:
a)明确pa的类型及其所指向的对象;
b)从pa所指向的对象中提取虚函数表,并找到与foo()函数相对应的函数地址。
c)根据所找到的虚函数地址,调用之。
以上三步操作均在运行阶段完成,故谓之动态绑定。
6.多态的前提和限制
多态=虚函数+指针或引用
在基类的构造函数和析构函数中调用虚函数,没有多态性,实际调用的永远是基类版本。
十四、运行时类型信息——RTTI
1.动态类型转换:dynamic_cast
在运行期对指针实际指向的类型(应具有多态性)做检查,以返回NULL表示转换失败。
2.静态类型转换:static_cast
在转换源类型和目标类型之间只要有一个方向上可以做隐式转换,那么在另一个方向上就可以做静态转换。以编译错误的形式表示转换失败。
3.重解释类型转换:reinterpret_cast
无论在编译期还是运行期均不做任何检查,使用时要十分慎重。
4.获取类型信息
typeid ()运算符返回值是一个typeinfo类型(声明在typeinfo的头文件中)的对象。其中包括一个name的成员函数,通过该函数可以获得类型的字面名称。同时typeinfo对象还可以用于比较两个类型是否相同或不同。
typeid可以获取指针所指向对象的实际类型,前提是该类型应具有多态性。
十五、虚析构函数
1.将基类的析构函数定义为虚函数。delete一个指向子类对象的基类指针将导致子类类型的析构函数被调用,该函数在释放完子类对象特有的资源之后,自动调用基类类型的析构函数,完整其基类部分的释放。
2.如果一个类中存在虚函数,往往意味着该类可以多态方式被使用,因此该类的析构函数就应该被定义为虚函数。
思考:
              定义为虚函数
全局函数      No
普通成员函数  Yes
静态成员函数  No
构造函数      No
析构函数      Yes
操作符函数
  成员        Yes
  友元        No
-----------------
class DocParser {
public:
  void parse (...) {
    ...
    onRect (...);
    ...
    onText (...);
    ...
    onImage (...);
  }
  virtual void onRect(...)=0;
  virtual void onText(...)=0;
  virtual void onImage(..)=0;
};
class DocRender : public DocParser {
  void onRect(...) {...};
  void onText(...) {...};
  void onImage(..) {...};
};
DocRender dr (...);
dr.parse ();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值