您的位置:首页 > 编程语言 > C语言/C++

Cpp_多态

2016-02-29 21:51 423 查看

一、虚函数、覆盖和多态

如果将基类中的某个成员函数声明为虚函数,那么其子类中与该函数具有相同原型的成员函数就也是虚函数,并且对基类中的版本形成覆盖。这时,通过指向子类对象的基类指针,或者引用子类对象的基类引用,调用虚函数,实际被执行的将是子类中的覆盖版本,而非基类中的原始版本,这种语法现象被称为多态

二、虚函数覆盖的条件

1.只有类的成员函数才能被声明为虚函数,全局函数和类的静态成员函数都不能被声明为虚函数。

2.只有在基类中被冠以virtual关键字的成员函数才能作为虚函数被子类覆盖,而与子类中virtual关键字无关。

3.虚函数在子类中的覆盖版本必须和该函数在基类中原始版本拥有相同的函数签名,即函数名、形参表和常属性必须严格一致。

4.如果基类中的虚函数返回除类类型指针和引用以外的数据,那么该函数在子类中的覆盖版本必须返回相同类型的数据。

5.如果基类中的虚函数返回类类型的指针(X*)或引用(X&),那么允许子类中的覆盖版本返回基类版本返回类型(X*/X&)的目标类型(X)的公有子类(Y)指针(Y*)或引用(Y&)——类型协变。

6.虚函数在子类中的覆盖版本和基类中的原始版本的访问控制属性没有必然联系。

三、多态的条件

1.多态特性除了需要在基类中声明虚函数,并在子类中提供有效的覆盖以外还必须通过指针或者引用来调用虚函数,才能表现出来。

2.调用虚函数的指针也可能是成员函数中的this指针,只要它是一个指向子类对象的基类指针,同样可以表现出多态性。

3.当基类的构造函数被子类的构造函数调用时,子类对象尚不能说是子类类型的,它只表现出基类类型的外观和行为。这时调用虚函数,它只能被绑定到基类版本,没有多态性。

4.当基类的析构函数被子类的析构函数调用时,子类对象已不再是子类类型的了,它只表现出基类类型的外观和行为。这时调用虚函数,它只能被绑定到基类版本,没有多态性。

5.在构造或析构函数中通过已构造完毕或尚未被析构的对象调用虚函数,其多态性不受任何影响。

四、纯虚函数、抽象类和纯抽象类

1.形如virtual 返回类型 函数名 (形参表) [const] = 0;的虚函数被称为纯虚函数。纯虚函数不需要定义,表示抽象的行为。

2.如果一个类包含了至少一个纯虚函数,那么这个类就是抽象类。抽象类不能被实例化为对象。

3.如果子类没有覆盖其抽象基类中的全部纯虚函数,那么该子类就也是一个抽象类。类的抽象属性可以被继承。

4.如果一个抽象类中除了构造和析构函数以外所有的成员函数都是纯虚函数,那么该抽象类就是一个纯抽象类,有名接口类。

class Browser {
public:
void show (void) {
// 提取一段文本
onText ();
// 提取一张图片
onImage ();
...
}
virtual void onText (void) { ... }
virtual void onImage (void) { ... }
...
};
class BrowserApple : public Browser {
public:
virtual void onText (void) { ... }
virtual void onImage (void) { ... }
...
};
BrowserApple ba;
ba.show ();


class Account {
public:
Account (Database* db) : m_db (db) {}
void save (double money) {
...
m_db->select ();
...
m_db->update ();
...
}
private:
Database* m_db;
};
class Database {
public:
virtual void select (void) = 0;
virtual void update (void) = 0;
};
class Oracle : public Database {
public:
void select (void) { ...Pro*C... }
void update (void) { ... }
};
class SQLServer : public Database {
public:
void select (void) { ...ADO... }
void update (void) { ... }
};
Account acc (new Oracle);
Accoutn acc (new SQLServer);


五、虚函数表与动态绑定

1.编译器会为每个包含虚函数的类生成一张虚函数表,即存放每个虚函数地址的函数指针数组,简称虚表(vtbl),每个虚函数对应一个虚函数表中的索引号。

2.除了为包含虚函数的类生成虚函数表以外,编译器还会为该类增加一个隐式的成员变量,通常在该类实例化对象的起始位置,用于存放虚函数表的首地址,该变量被称为虚函数表指针,简称虚指针(vptr)。

B b;
A& a = b;
a.foo (); // 被编译为:a.vptr[0] (&a)
// vptr[0]中存放的就是B::foo地址


3.虚表是一个类一张,而不是一个对象一张,同一个类的所有对象,通过各自的虚指针,共享同一张虚表。

4.所谓虚函数覆盖,其本质就是用子类中的虚函数地址覆盖基类虚表中对应的基类虚函数的地址。

5.当编译器看到通过指针或引用调用虚函数的语句时,并不急于生成有关函数调用的指令,相反它会用一段代码替代该语句,这段代码在运行时被执行,完成如下操作:

1)从调用指针或引用的目标对象中找到虚指针;
2)根据虚指针找到相应的虚表,并从中获取所调用虚函数的入口地址;
3)根据虚函数的入口地址执行虚函数代码。
这个函数绑定过程在运行阶段完成,因此被称为动态绑定。


6.动态绑定对性能的影响

1)动态绑定会增加内存开销;
2)虚函数的调用会增加时间开销;
3)虚函数不能被内联优化。
如果没有多态性的要求,最好不要使用虚函数。


六、运行时类型信息

1.typeid运算符可以作用于类型或者对象(基本类型或类类型),返回typeinfo类型对象的常引用。typeinfo中包含名为name的成员函数,调用该函数返回字符指针,该指针指向一个以空字符结尾的字符串,其中包含类型的标记。此外,typeinfo类提供了对“==”和“!=”运算符的支持,通过它们可以直接进行类型之间的比较。如果在类型之间存在多态的继承关系,typeid还可以利用多态性确定实际对象的类型。

#include <typeinfo>


2.dynamic_cast动态类型转换用于在具有多态继承关系的父子类指针和引用之间进行显式类型转换。在转换过程中,会检查指针或引用目标对象的类型是否与期望转换的类型一致,如果一致则转换成功,否则失败。如果所转换的是指针,则以返回空指针表示失败;如果所转换的是引用,则通过抛出bad_cast异常表示失败。

七、虚析构函数

1.delete一个指向子类对象的基类指针,实际被调用的仅仅是基类的析构函数。基类的析构函数只负责析构子类对象中的基类子对象,而不会调用子类的析构函数。这样在子类中动态分配的资源就会形成内存泄漏。

2.如果将基类的析构函数声明为虚函数,那么子类的析构函数就也是一个虚函数,并且对基类的虚函数构成有效的覆盖,可以表现出多态特性。这时delete一个指向子类对象的基类指针,实际被调用的将是子类的析构函数。子类的析构函数在释放子类对象特有的动态资源之后会自动调用基类的析构函数,释放基类子对象中的动态资源。最终实现完美的资源释放,避免了内存泄漏。

3.一般而言,如果一个类没有分配任何动态资源,可以不为其定义析构函数,但是编译器会为其提供缺省析构函数,而缺省析构函数不是虚函数。为了防止delete指向子类对象的基类指针时产生内存泄漏的风险,即使是空的析构函数也有为基类定义的必要,仅仅是为了通过虚析构在对象销毁过程中体现出多态特性。

八、类中哪些函数可以虚?

构造函数 不可以虚

析构函数 可以虚

普通成员函数 可以虚

静态成员函数 不可以虚

操作符函数 可以虚
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: