一.多态概念
- 多态:一词最初来源于希腊语,意思是:多种形态。
- 多态分为静态多态和动态多态。静态多态分为函数重载和泛型编程。动态多态是通过虚函数来实现的。
- 静态多态(叫静态绑定或早绑定):编译器在编译期间完成的,,编译器可以根据函数实参的类型(可能会进行隐式的类型转换)注意:宏不是静态多态,宏是在预处理阶段完成的
- 动态多态(又叫动态绑定或者晚绑定):在程序执行期间判断所引用对象的实际类型根据其实际类型调用相应的方法。
动态绑定的条件
- 必须是虚函数。
- 必须通过基类类型的指针或者引用调用虚函数。
纯虚函数:在成员函数的形参后面写上=0,则成员函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类)。抽象类不能实例化出对象。
纯虚函数在派生类中重新定义以后,派生类才能实例化出对象
重写的概念:
怎么才能构成重写?
- 一个在基类一个在派生类。
- 在基类的必须为虚函数,在派生类中重写时函数的原型必须一样(包括返回值,函数的名字以及形参列表),
协变除外
协变:
- 若为基类 -> 返回基类的指针或者引用。
- 若为派生类 -> 返回派生类的指针或者引用。
哪些函数不可以写成虚函数?
1.构造函数 2.拷贝构造函数 3.静态成员函数 4.友元函数 5.赋值运算符重载函数可以写成虚函数,但是最好不要这么做,使用的时候容易混淆- 如果类中有虚函数。那么最好将析构函数写成虚函数。
二.用代码验证有关虚函数的一些理论
1.只要类中有虚函数,编译器就会自动合成一个构造函数,并且改写构造函数,将对象的前四个字节放入虚表的地址。虚表中放着每一个虚函数的地址。
若下代码:
#include<iostream>
using namespace std;
class Base
{
public:
virtual void Funtest1()
{
cout<<"Base::Funtest1()"<<endl;
}
virtual void Funtest2()
{
cout<<"Base::Funtest2()"<<endl;
}
virtual void Funtest3()
{
cout<<"Base::Funtest3()"<<endl;
}
void Funtest4()
{
cout<<"Base::Funtest4()"<<endl;
}
int _b;
};
typedef void (*pFun)();//定义一个函数指针
void Print( Base b)
{
pFun * pfun =(pFun*)(*(int*)&b);
while(*pfun)
{
(*pfun)();
++pfun;
}
}
int main()
{
Base b;
Print(b);
system("pause");
return 0;
}
调试结果如下图:
2.虚函数是怎么完成调用的?
- 找到虚函数表。
- 从虚函数表中找到要调用的虚函数。
3.函数在虚标中的次序。
- 基类:虚表中的声明次序就是在虚表中的次序。
派生类:若派生类重写了虚函数,并且有属于自己的虚函数,则会以以下步骤生成虚表:
- 先拷贝基类虚表。
- 如果对基类虚函数重写了,则会替换(覆盖)相同位置的虚函数。
- 在后面加上派生类自己特有的虚函数。
上述可以通过如下代码进行验证
#include<iostream>
using namespace std;
class Base
{
public:
virtual void Funtest1()
{
cout<<"Base::Funtest1()"<<endl;
}
virtual void Funtest2()
{
cout<<"Base::Funtest2()"<<endl;
}
virtual void Funtest3()
{
cout<<"Base::Funtest3()"<<endl;
}
void Funtest4()
{
cout<<"Base::Funtest4()"<<endl;
}
int _b;
};
class Deriver:public Base
{
virtual void Funtest2()
{
cout<<"Deriver::Funtest2()"<<endl;
}
virtual void Funtest5()
{
cout<<"Deriver::Funtest5()"<<endl;
}
};
typedef void (*pFun)();//定义一个函数指针
void Print( Deriver d)
{
pFun * pfun =(pFun*)(*(int*)&d);
while(*pfun)
{
(*pfun)();
++pfun;
}
}
int main()
{
Base b;
Deriver d;
Print(d);
system("pause");
return 0;
}
类比以上的两个图片 可以论证以上结论。
4.多继承
class B1
{
public:
virtual void Funtest1()
{
cout<<"B1::Funtest1()"<<endl;
}
virtual void Funtest2()
{
cout<<"B1::Funtest2()"<<endl;
}
int _b1;
};
class B2
{
public:
virtual void Funtest3()
{
cout<<"B2::Funtest3()"<<endl;
}
virtual void Funtest4()
{
cout<<"B2::Funtest4()"<<endl;
}
int _b2;
};
class C:public B1,public B2
{
public:
virtual void Funtest2()
{
cout<<"C::Funtest2()"<<endl;
}
virtual void Funtest3()
{
cout<<"C::Funtest3()"<<endl;
}
virtual void Funtest5()
{
cout<<"C::Funtest5()"<<endl;
}
int _c;
};
typedef void (*pFun)();//定义一个函数指针
void Print()
{
C c;//打印B1的虚表
pFun * pfun =(pFun*)(*(int*)&c);
while(*pfun)
{
(*pfun)();
++pfun;
}
cout<<endl;
B2 &b=c;//打印B2虚表
pfun =(pFun*)(*(int*)&b);
while(*pfun)
{
(*pfun)();
++pfun;
}
}
int main()
{
C c;
cout<<sizeof(C)<<endl;
c._b1 =0;
c._b2=1;
c._c =2;
Print();
system("pause");
return 0;
}
以上结果证明:一个类继承了几个带有虚函数的类,则会有几个虚表,而将自己特有的虚函数地址,放在第一张虚表上。
4.虚拟继承
- 若派生类新增加一个虚函数,则创建派生类对象时会多出一张虚表来保存派生类自己的虚函数。
- 派生类的构造函数所做的事情:
- 调基函数
- 填虚表地址
- 填偏移量表格的地址
class B
{
virtual void Funtest1()
{
cout<<"B::Funtest1()"<< endl;
}
virtual void Funtest2()
{
cout<<"B::Funtest2()"<< endl;
}
};
class C:virtual B
{
virtual void Funtest2()
{
cout<<"C::Funtest2()"<< endl;
}
virtual void Funtest3()
{
cout<<"C::Funtest3()"<< endl;
}
};
int main()
{
C c;
cout<< sizeof(c) << endl;
system("pause");
return 0;
}
如上述程序,若注释了Funtest3()则打印结果是16,反之则打印结果是20.故说明上述第一个理论