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

C++的多态(重载,隐藏和覆盖)

2017-12-27 12:24 459 查看
C++的多态特性主要体现在三个方面: 重载,隐藏和覆盖.前两个是编译时的多态,最后一个是运行期的多态.

 重载:同一个类中方法名相同,方法签名(参数类型和个数)不同的方法构成这个类的方法重载.编译器根据方法调用时的参数类型和个数匹配相应的类方法,由于可以在编译期决定调用的方法,因此这种多态是静态的多态.

 隐藏:具有父子关系的类中,子类方法同父类方法相同(方法名,方法签名和返回类型)的方法构成类的方法隐藏.方法隐藏也是发生在编译期,编译器根据方法调用时的对象类型决定调用的是父类方法还是子类方法.如果对象是父类的指针或引用,则调用的是父类方法;如果对象是子类的指针或引用,则调用的是子类方法.同样,由于可以在编译期决定调用的方法,因此这种多态也是静态的多态.

覆盖(重写):具有父子关系的类中,子类方法同父类方法相同(方法名,方法签名和返回类型),且该方法在父类中使用virtual关键字声明,或在父子类中都用virtual关键字声明的方法构成累的方法覆盖(重写).对象指针或引用调用的方法是父类方法还是子类方法取决于指针或引用实际指向的对象的类型,而非指针或引用的类型.由于这种多态在编译期是无法感知的(想象一下,如果一个含有虚函数的父类的指针通过函数返回值被赋值,编译器是无法知道该指针指向的对象的真实类型的),只能在运行时动态的获取,因此也被称为动态多态.

重载和隐藏都是比较简单的,接下来重点分析覆盖的实现原理.在深入覆盖原理之前,先来看一下隐藏和覆盖的举例:

#include <iostream>

using namespace std;

class Base {
public:
void func1() {cout << "Base::func1()" << endl;}
virtual void func2() {cout << "Base::func2()" << endl;}
};

class Derived: public Base {
public:
void func1() {cout << "Derived::func1()" << endl;} // hiden
virtual void func2() {cout << "Derived::func2()" << endl;} // override
};

int main(void) {
Base b, *bptr;
Derived d;

b.func1(); // call Base::func1()
b.func2(); // call Base::func1()

d.func1(); // hiden Base::func1(), call Derived::func1()
d.func2(); // override, call Derived::func2()

bptr = &d;
bptr->func1(); // call Base::func1()
bptr->func2(); // override, call Derived::func2()

return 0;
}

运行结果:

Base::func1()
Base::func2()
Derived::func1()
Derived::func2()
Base::func1()
Derived::func2()

C++是如何实现运行时多态(覆盖或重写)的呢?这就是C++中大名鼎鼎的虚函数表.如果一个类使用virtual关键字声明了虚函数,在编译的时候,编译器会产生一个对应的虚函数表.子类也会有一个独立的虚函数表,如果子类没有覆盖了父类的虚函数,则子类虚函数表的函数指针指向父类定义的虚函数;如果子类覆盖了父类的虚函数,则子类虚函数表中相应的函数指针指向子类自身定义的虚函数.在生成包含虚函数的类的对象时,编译器隐式的在对象中增加一个指针(g++通常把这个指针放在对象起始位置,当然不同编译器也有可能放在对象结尾),指向这个实际对象类的虚函数表.一个包含虚函数的类的多个对象共享这个类的虚函数表.如此,在运行时对虚函数的调用实际上是通过查找实际对象指向的虚函数表,找到相应的方法,再调用该方法.

下面给出虚函数表的存储结构,分两种不同的情况.

(1)单继承,无覆盖:

#include <iostream>

using namespace std;

class Base {
public:
virtual void bFunc1() {cout << "Base::bFunc1()" << endl;}
virtual void bFunc2() {cout << "Base::bFunc2()" << endl;}
};

class Derived: public Base {
public:
virtual void dFunc1() {cout << "Derived::dFunc1()" << endl;}
virtual void dFunc2() {cout << "Derived::dFunc2()" << endl;}
};

int main() {
Base b;
Derived d1;
Derived d2;

typedef void (*pFunc)(void);
pFunc *bf = (pFunc *)(*(long *)&b);
(*bf++)();
(*bf)();

pFunc *df1 = (pFunc *)(*(long *)&d1);
pFunc *df2 = (pFunc *)(*(long *)&d2);

if (df1 == df2) {
cout << "Shared virtual function table" << endl;
} else {
cout << "Individual virtual function table" << endl;
}

(*df1++)();
(*df1++)();
(*df1++)();
(*df1)();

(*df2++)();
(*df2++)();
(*df2++)();
(*df2)();

return 0;
}

运行结果:

Base::bFunc1()
Base::bFunc2()
Shared virtual function table
Base::bFunc1()
Base::bFunc2()
Derived::dFunc1()
Derived::dFunc2()
Base::bFunc1()
Base::bFunc2()
Derived::dFunc1()
Derived::dFunc2()

父类虚表示意图:



子类虚表示意图:



(2)单继承,有覆盖:

#include <iostream>

using namespace std;

class Base {
public:
virtual void bFunc1() {cout << "Base::bFunc1()" << endl;}
virtual void bFunc2() {cout << "Base::bFunc2()" << endl;}
};

class Derived: public Base {
public:
virtual void bFunc1() {cout << "Derived::bFunc1()" << endl;}
virtual void dFunc2() {cout << "Derived::dFunc2()" << endl;}
};

int main() {
Base b;
Derived d;

typedef void (*pFunc)(void);
pFunc *bf = (pFunc *)(*(long *)&b);
(*bf++)();
(*bf)();

pFunc *df = (pFunc *)(*(long *)&d);

(*df++)();
(*df++)();
(*df)();

return 0;
}

运行结果:

Base::bFunc1()
Base::bFunc2()
Derived::bFunc1()
Base::bFunc2()
Derived::dFunc2()

父类虚表同上,子类虚表第一个虚函数被重新,指向子类定义的虚函数:



总结:当然,以上分析并不完整,对多继承的情况没有分析,但是基本思想已经表达了.本文通过指针获取虚表方法的方式只是为了分析虚表实现方式,这是一种能够破坏c++控制权限和封装机制的方式,在实际应用中最好不要使用.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: