虚函数多态:
- 至少有两个有继承关系的类.
- 基类与派生类中有同名同参的函数,且基类函数被virtual所修饰(覆盖).
- 必须通过基类的引用或指针调用该函数.
.声明语法:
virtual 虚函数类型函数名(形参表)
多态性:
在C++中,多态类型是指声明或者继承了至少一个虚函数的类型,反之则为非多态类型。
对于非多态类型:
struct A
{
void foo() {}
};
A a;
std::cout << typeid(a).name(); // 可以在编译时确定a的类型为A
a.foo(); // 可以在编译时确定A::foo在内存中的地址
sizeof(a); // 尽管A为空,但由于需要在内存中确定a的地址,因此A类型对象所占空间为1个字节
它的类型可以确定。
而对于的多态类型:
struct A
{
virtual void foo() {} // 声明虚函数
};
struct B : public A
{
// 隐式继承了虚函数
};
...
B b{};
A& a_rb = b; // 将b绑定到A的左值引用a_rb上
typeid(decltype(a_rb)).name(); // decltype产生的是编译时即可确定的声明类型,因此为A
typeid(a_rb).name(); // 由于a_rb是多态类型的glvalue,typeid在运行时计算,因此为B
a_rb.foo(); // 这里调用的是B中的foo,其函数地址是运行时确定的
sizeof(b); // 这里的sizeof是编译器决定的,通常为8 (64位)
多态类型,一些信息必须延迟到运行时才可以确定,例如它的实际类型、所调用的虚函数的地址等。下面的这个例子中,类型B继承了声明有虚函数的类型A,因此A和B都是多态类型。
实现原理:
虚表:是一个存放本类所有虚函数入口地址的静态指针数组;
重载、隐藏、覆盖:
重载:在同一作用域中,同名不同参.
覆盖:分别位于基类和派生类中,同名同参,且基类函数被virtual所修饰.
隐藏: 分别位于基类和派生类中
a.同名同参,基类函数没有被virtual所修饰
b.同名不同参.
如果基类函数为虚函数,则派生类中同名同参函数既是没有virtual修饰,也是虚函数。
规定:
-
构造函数不能为虚函数.
-
内联函数不能为虚函数.
-
静态成员函数不能为虚函数.
-
析构函数可以为虚函数,而却通常是虚函数.
单继承
对于每一个多态类型,其所有的虚函数的地址都以一个表格的方式存放在一起,每个函数的偏移量在基类型和导出类型中均相同,这使得虚函数相对于表格首地址的偏移量在可以在编译时确定。虚函数表格的首地址储存在每一个对象之中,称为虚(表)指针(vptr)或者虚函数指针(vfptr),这个虚指针始终位于对象的起始地址。使用多态类型的引用或指针调用虚函数时,首先通过虚指针和偏移量计算出虚函数的地址,然后进行调用。
例如:
struct A
{
public:
virtual void f0() {
cout << "f0()" << endl;
}
virtual void f1() {
cout << "f1()" << endl;
}
};
struct B : public A
{
public:
void f0(){
cout << "B::f0()" << endl;
};
};
int main()
{
B a;
a.f0();
a.f1();
return 0;
}
多继承:
class Base
{
public://基类中有虚继承,则派生类中会自动加一个virtual
virtual void f1() { cout << "Base::f1()" << endl;}
virtual void f2() { cout << "Base::f2()" << endl; }
void f3() { cout << "Base::f3()" << endl; }
void f4() { cout << "Base::f4()" << endl; }
virtual void f5() { cout << "Base::f5()" << endl; }
void f6() { cout << "Base::f6()" << endl; }
virtual ~Base() { cout << "~Base()" << endl; }
int i;
};
class D :public Base
{
public:
virtual void f1() { cout << "D::f1()" << endl; }
void f2() { cout << "D::f2()" << endl; }//base中有virtual,则void f2()默认为virtual void f2()
virtual void f3() { cout << "D::f3()" << endl; }
void f4() { cout << "D::f4()" << endl; }
virtual void f7() { cout << "B::f7()" << endl; }//如果派生类中有virtual void f7() ,并且基类没有编译会出错
void f8() { cout << "B::f8()" << endl; }
~D() { cout << "~D()" << endl; }
int j;
};
与单链继承不同,由于A和B完全独立,它们的虚函数没有顺序关系,即f0和f1有着相同对虚表起始位置的偏移量,不可以顺序排布。 并且A和B中的成员变量也是无关的,因此基类间也不具有包含关系。这使得A和B在C中必须要处于两个不相交的区域中,同时需要有两个虚指针分别对它们虚函数进行索引。
虚表: