1. C++中虚函数的作用和多态
虚函数: 实现类的多态性
关键字:虚函数;虚函数的作用;多态性;多态公有继承;动态联编
C++中的虚函数的作用主要是实现了多态的机制。基类定义虚函数,子类可以重写该函数;在派生类中对基类定义的虚函数进行重写时,需要再派生类中声明该方法为虚方法。
当子类重新定义了父类的虚函数后,当父类的指针指向子类对象的地址时,[即B b; A a = &b;] 父类指针根据赋给它的不同子类指针,动态的调用子类的该函数,而不是父类的函数(如果不使用virtual方法,请看后面★*),且这样的函数调用发生在运行阶段,而不是发生在编译阶段,称为动态联编。而函数的重载可以认为是多态,只不过是静态的。注意,非虚函数静态联编,效率要比虚函数高,但是不具备动态联编能力。
★如果使用了virtual关键字,程序将根据引用或指针指向的 对象类型来选择方法,否则使用引用类型或指针类型来选择方法。
下面的例子解释动态联编性:
class A{
private:
int i;
public:
A();
A(int num) :i(num) {};
virtual void fun1();
virtual void fun2();
};
class B : public A{
private:
int j;
public:
B(int num) :j(num){};
virtual void fun2();// 重写了基类的方法
};
// 为方便解释思想,省略很多代码
A a(1);
B b(2);
A *a1_ptr = &a;
A *a2_ptr = &b;
// 当派生类“重写”了基类的虚方法,调用该方法时
// 程序根据 指针或引用 指向的 “对象的类型”来选择使用哪个方法
a1_ptr->fun2();// call A::fun2();
a2_ptr->fun2();// call B::fun1();
// 否则
// 程序根据“指针或引用的类型”来选择使用哪个方法
a1_ptr->fun1();// call A::fun1();
a2_ptr->fun1();// call A::fun1();
2. 虚函数的底层实现机制
实现原理:虚函数表+虚表指针
关键字:虚函数底层实现机制;虚函数表;虚表指针
编译器处理虚函数的方法是:为每个类对象添加一个隐藏成员,隐藏成员中保存了一个指向函数地址数组的指针,称为虚表指针(vptr),这种数组成为虚函数表(virtual function table, vtbl),即,每个类使用一个虚函数表,每个类对象用一个虚表指针。
举个例子:基类对象包含一个虚表指针,指向基类中所有虚函数的地址表。派生类对象也将包含一个虚表指针,指向派生类虚函数表。看下面两种情况:
-
如果派生类重写了基类的虚方法,该派生类虚函数表将保存重写的虚函数的地址,而不是基类的虚函数地址。
-
如果基类中的虚方法没有在派生类中重写,那么派生类将继承基类中的虚方法,而且派生类中虚函数表将保存基类中未被重写的虚函数的地址。注意,如果派生类中定义了新的虚方法,则该虚函数的地址也将被添加到派生类虚函数表中。
下面的图片体现了上述的底层实现机制:
3. 多重继承中的虚函数
1)无虚函数覆盖的多重继承
代码:
#pragma once
//无覆盖,多重继承
class Base1
{
public: //三个虚函数
virtual void f() { cout << "Base1::f" << endl; }
virtual void g() { cout << "Base1::g" << endl; }
virtual void h() { cout << "Base1::h" << endl; }
};
class Base2
{
public: //三个虚函数
virtual void f() { cout << "Base2::f" << endl; }
virtual void g() { cout << "Base2::g" << endl; }
virtual void h() { cout << "Base2::h" << endl; }
};
class Base3
{
public: //三个虚函数
virtual void f() { cout << "Base3::f" << endl; }
virtual void g() { cout << "Base3::g" << endl; }
virtual void h() { cout << "Base3::h" << endl; }
};
//多重继承无覆盖
class Derive :public Base1 , public Base2 , public Base3
{
public:
virtual void f1() { cout << "Derive::f1" << endl; }
virtual void g1() { cout << "Derive::g1" << endl; }
virtual void h1() { cout << "Derive::h1" << endl; }
};
void Test()
{
Derive d;
}
调试结果:
可得:
1. 每个父类都有虚表;
2. 同样问题,虚表中没有体现出子类的虚函数;见真实内容:
可见子类的虚函数在按基类声明顺序的第一个基类的虚表中,且在此基类虚函数之后;
2)有虚函数覆盖的多重继承
代码:
#pragma once
//有虚函数覆盖的多重继承
#pragma once
//无覆盖,多重继承
class Base1
{
public: //三个虚函数
virtual void f() { cout << "Base1::f" << endl; }
virtual void g() { cout << "Base1::g" << endl; }
virtual void h() { cout << "Base1::h" << endl; }
};
class Base2
{
public: //三个虚函数
virtual void f() { cout << "Base2::f" << endl; }
virtual void g() { cout << "Base2::g" << endl; }
virtual void h() { cout << "Base2::h" << endl; }
};
class Base3
{
public: //三个虚函数
virtual void f() { cout << "Base3::f" << endl; }
virtual void g() { cout << "Base3::g" << endl; }
virtual void h() { cout << "Base3::h" << endl; }
};
//多重继承无覆盖
class Derive :public Base1, public Base2, public Base3
{
public:
virtual void f() { cout << "Derive::f" << endl; } //唯一一个覆盖的子类函数
virtual void g1() { cout << "Derive::g1" << endl; }
virtual void h1() { cout << "Derive::h1" << endl; }
};
void Test()
{
Derive d;
Base1 *b1 = &d;
Base2 *b2 = &d;
Base3 *b3 = &d;
b1->f(); //Derive::f()
b2->f(); //Derive::f()
b3->f(); //Derive::f()
b1->g(); //Base1::g()
b2->g(); //Base2::g()
b3->g(); //Base3::g()
}
运行结果:
其实底层是这样的:
分析:
1. 每个父类的虚表中原本存放f()函数的地方,被子类的f()函数覆盖;
2. 其余不变;(意思是,还有没被用来覆盖的子类虚函数,任然在首个父类虚函数表的后边位置)
见图
转载出处:
[1] https://blog.csdn.net/iFuMI/article/details/51088091
[2] https://blog.csdn.net/Li_Ning_/article/details/51893748
感谢原作者的分享!
另外发现另一作者讲得更加清晰全面,附上链接:https://blog.csdn.net/haoel/article/details/1948051