一、什么是多态?
1.多态的概念
在面向对象方法:向不同的对象发送同一个消息,不同的对象在接收的时会产生不同的行为(即方法)
在C++中,多态性的表现形式之一:具有不同功能的函数可用同一个函数名,这样就可以实现用一个函数名调不同内容的函数。
用现实生活中的例子来说一下动态,比如说:学校开学,不同身份的人会有不同的反应。比如老师要备课上课,学生要报名上课,保洁阿姨要打扫教室学校卫生……
2.分类
从系统实现的角度来看,多态可以分为两类:静态多态和动态多态(如下图所示)
【静态多态】
静态多态(早绑定):编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推断出要调用那个函数,如果有对应的函数就调用该函数,否则出现编译错误
下面用代码来说明一下:
#include<iostream>
using namespace std;
int Add(int left, int right)
{
return left+right;
}
double Add(double left, double right)
{
return left+right;
}
float Add(float left, float right)
{
return left+right;
}
int main()
{
cout<<Add(1, 2)<<endl;
cout<<Add(1, 'd')<<endl;
cout<<Add(12.34f, 34.12f)<<endl;
cout<<Add(1, "Hello")<<endl;//会报错
system("pause");
return 0;
}
【动态多态】
动态多态(晚绑定):在程序执行期间(非编译期)判断所引用对象的实际类型,根据其实际类型调用相应方法
使用virtual关键字修饰类的成员函数时,指明该函数为虚函数,派生类需要重新实现,编译器将实现动态绑定
动态绑定的条件:
1.必须是虚函数(一定在派生类中对基类的虚函数进行重写)
2.通过基类类型的引用或者指针调用虚函数(调用虚函数的方法)
下面来看一个实例:
二、【虚函数】
虚函数就是在基类声明函数是虚拟的,并不是实际存在的函数,然后在派生类中才正式定义此函数
作用:允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或者引用来访问基类和派生类中的同名函数
- **重载&重写(覆盖)&重定义(隐藏)的区别
重载:在同一作用域;函数名相同/参数不同;返回值可以不同
重写(覆盖):不在同一作用域(分别在基类和派生类);函数名相同/参数相同/返回值相同(协变例外);基类函数必须有virtual关键字;访问修饰符可以不同
重定义(隐藏):在不同作用域中(分别在基类和派生类);函数名相同;在基类和派生类中只要不构成重写就是重定义
2.协变:基类虚函数返回类型是基类的引用或指针,派生类的虚函数返回类型是派生类的引用或指针,但还要构成重写
代码:
class Base
{
public:
virtual void Test1()
{
cout<<"Base::Test1()"<<endl;
}
virtual void Test2()
{
cout<<"Base::Test2()"<<endl;
}
void Test3()
{
cout<<"Base::Test3()"<<endl;
}
int _b;
};
class Derived : public Base
{
public:
virtual void Test1()
{
cout<<"Derived::Test1()"<<endl;
}
virtual void Test2()
{
cout<<"Derived::Test2()"<<endl;
}
virtual void Test3()
{
cout<<"Derived::Test()3"<<endl;
}
int _d;
};
void Test(Base& b)
{ // ecx<----b
b.Test2(); // call Base::FunTest2
b.Test1();
}
int main()
{
cout<<sizeof(Base)<<endl;
Base b;
Derived d;
Test(b);
Test(d);
return 0;
}
3.纯虚函数:在成员函数的形参列表后面写上=0,则成员函数就是纯虚函数。
包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化处对象(一定要被继承)。纯虚函数在派生类中重新定义以后,派生类才能实例化出对象。
class Base
{
virtual void Base() = 0;
public:
int _b;
};
class Derived : public Base
{};
总结:
1.派生类重写基类的虚函数实现多态,要求函数名/参数列表/返回值完全相同(协变除外)
2.基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性
3.如果在类外定义虚函数,只能在声明函数时加virtual关键字,定义时不用加
4.虚表是所有类对象实例共用的
三、虚表指针
什么是虚表,虚表是如何构造的???
3.1 普通单继承
基类虚表:虚函数在类中的声明次序
派生类中的虚函数:1.拷贝基类的虚表 2.检测派生类中是否对基类中虚函数进行重写—–(如若有重写的)用派生类中重写的虚函数来替代相同偏移量位置的基类虚函数 3.在虚表之后添加派生类自己的虚函数
下面用代码来实现以下:
class Base
{
public:
virtual void Test1()
{
cout<<"Base::Test1()"<<endl;
}
virtual void Test2()
{
cout<<"Base::Test2()"<<endl;
}
void Test3()
{
cout<<"Base::Test3()"<<endl;
}
int _b;
};
class Derived : public Base
{
public:
virtual void Test1()
{
cout<<"Derived::Test1()"<<endl;
}
virtual void Test2()
{
cout<<"Derived::Test2()"<<endl;
}
virtual void Test3()
{
cout<<"Derived::Test3()"<<endl;
}
virtual void Test4()
{
cout<<"Derived::Test4()"<<endl;
}
int _d;
};
typedef void (*FuncPtr)();
void Print(Base& b)
{
FuncPtr* pFunc = (FuncPtr*)(*(int*)&b);
while(*pFunc)
{
(*pFunc)();
pFunc++;
}
}
void Test(Base& b)
{
b.Test1();
b.Test2();
b.Test3();
}
int main()
{
Base b;
Derived d;
Test(b);
Test(d);
return 0;
}
*注:通过基类的引用或指针调用虚函数时,调用基类还是派生类的虚函数,要根据运行时引用(指针)实际引用(指向)的类型确定,调用非虚函数时,则无论基类指向的是何种类型,都调用的是基类的函数
3.2多继承
基类虚表:虚函数在类中的声明次序
派生类中的虚函数:1.拷贝基类的虚表 2.检测派生类中是否对基类中虚函数进行重写—–(如若有重写的)用派生类中重写的虚函数来替代相同偏移量位置的基类虚函数 3.添加派生类自己的虚函数到第一张虚表后面(why??—–这样会使效率提高)
下面用代码来实现以下:
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 D:public B2, public B1
{
public:
virtual void FunTest1()
{
cout<<"D::FunTest1()"<<endl;
}
virtual void FunTest3()
{
cout<<"D::FunTest3()"<<endl;
}
virtual void FunTest6()
{
cout<<"D::FunTest6()"<<endl;
}
int _d;
};
typedef void (*FunPtr)();
void PrintVFT(B1& b)
{
FunPtr* pfun = (FunPtr*)(*(int*)&b);
while(*pfun)
{
(*pfun)();
pfun++;
}
cout<<endl;
}
void PrintVFT(B2& b)
{
FunPtr* pfun = (FunPtr*)(*(int*)&b);
while(*pfun)
{
(*pfun)();
pfun++;
}
cout<<endl;
}
int main()
{
cout<<sizeof(D)<<endl;
D d;
B1& b1 = d;
PrintVFT(b1);
B2& b2 = d;
PrintVFT(b2);
return 0;
}
3.3菱形继承
class B
{
public:
virtual void FunTest1()
{
cout<<"B::FunTest1()"<<endl;
}
virtual void FunTest2()
{
cout<<"B::FunTest2()"<<endl;
}
int _b;
};
class C1:public B
{
public:
virtual void FunTest1()
{
cout<<"C1::FunTest1()"<<endl;
}
virtual void FunTest3()
{
cout<<"C1::FunTest3()"<<endl;
}
int _c1;
};
class C2:public B
{
public:
virtual void FunTest1()
{
cout<<"C2::FunTest1()"<<endl;
}
virtual void FunTest4()
{
cout<<"C2::FunTest4()"<<endl;
}
int _c2;
};
class D:public C1, public C2
{
public:
virtual void FunTest1()
{
cout<<"D::FunTest1()"<<endl;
}
virtual void FunTest3()
{
cout<<"D::FunTest3()"<<endl;
}
virtual void FunTest4()
{
cout<<"D::FunTest4()"<<endl;
}
virtual void FunTest5()
{
cout<<"D::FunTest5()"<<endl;
}
int _d;
};
typedef void (*FunPtr)();
void PrintVFT(C1& c)
{
FunPtr* pfun = (FunPtr*)(*(int*)&c);
while(*pfun)
{
(*pfun)();
pfun++;
}
cout<<endl;
}
void PrintVFT(C2& c)
{
FunPtr* pfun = (FunPtr*)(*(int*)&c);
while(*pfun)
{
(*pfun)();
pfun++;
}
cout<<endl;
}
int main()
{
cout<<sizeof(D)<<endl;
D d;
d.C1::_b = 0;
d._c1 = 1;
d.C2::_b = 2;
d._c2 = 3;
d._d = 4;
C1& c1 = d;
PrintVFT(c1);
C2& c2 = d;
PrintVFT(c2);
return 0;
}