首先我们来简单介绍下多态,多态顾名思义是多种形态的意思。在C++中多态又分为静态多态和动态多态。静态多态中包括重载和泛型编程,是在编译期间完成,编译器根据实参类型(可能会隐式类型转化)确定调那个函数。应注意:宏替换是在预处理期间完成所以不是静态多态。而动态多态是虚函数的调用,编译器在执行期间判断所引用对象的实际类型。
动态绑定的条件:
1.基类必须是虚函数并且在派生类中重写
2.通过基类的指针或引用调用虚函数。
继承体系同名成员函数的关系:
1.重载:在同一作用域中,函数名相同,参数列表不同,返回值可以不同。
2.重写(覆盖):
(1)不在同一作用域(基类和派生类)
(2)函数原型一模一样(函数名,参数,返回值(协变除外))
(3)基类必须是虚函数
(4)访问修饰符可以不同
几点注意:
(1)构造函数不能作为虚函数。因为虚函数是通过对象调用,而对象还没有创建完整虚表地址就无法填入因而就不能调虚函数。
(2)静态成员函数不能作为虚函数,因为虚函数通过对象拿到虚表地址,而静态成员不属于任何对象。
(3)赋值运算写成非虚函数比较好。因为赋值运算本来就是同类型赋值,如果写成虚函数对象多开辟四个字节浪费空间和时间。
(4)友元函数不是类的成员函数因此无法拿到虚表指针。也不能作为虚函数。
(5)析构函数最好给成虚函数,可以先看下面的代码
class B
{
public:
~B()
{
cout<<"~B()"<<endl;
}
};
class D:public B
{
public:
~D()
{
cout<<"~D()"<<endl;
}
};
int main()
{
B* pb = new D;//创建一个基类的指针指向派生类
delete pb;
system("pause");
return 0;
}
这段代码运行结果是调用了基类析构函数~B(),这样做会使D类空间没有释放,会导致内存泄漏,所以析构函数一般给成虚函数。
(6)如果在类外定义虚函数,只能在声明函数时加virtual关键字,定义时不用加。
(7)不要在构造函数和析构函数中调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会出现未定义的行为。
纯虚函数:在成员函数的形参列表后面写上=0,则成员函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。纯虚函数在派生类中重新定义以后,派生类才能实例化出对象。下面给出一个例子
class Base
{
public:
virtual void FunTest()=0;//纯虚函数
};
class Derived:public Base
{
public:
virtual void FunTest()
{
}//重定义之后派生类就成为普通的类而不是抽象类
};
int main()
{
//Base b;
Derived d;
return 0;
}
虚表剖析:
class Base
{
public:
virtual void FunTest1()
{
cout<<"B::FunTest1()"<<endl;
}
virtual void FunTest2()
{
cout<<"B::FunTest2()"<<endl;
}
virtual void FunTest3()
{
cout<<"B::FunTest3()"<<endl;
}
};
class Derived:public Base
{
public:
virtual void FunTest1()
{
cout<<"D::FunTest1()"<<endl;
}
virtual void FunTest2()
{
cout<<"D::FunTest2()"<<endl;
}
virtual void FunTest3()
{
cout<<"D::FunTest3()"<<endl;
}
int _d;
};
typedef void(*Fun)();//定义一个函数指针类型
void FunTest()
{
Base b;
Fun* pFun = (Fun*)*(int*)&b;
while(*pFun)
{
(*pFun)();
pFun = (Fun*)((int*)pFun+1);
}
}
int main()
{
//Derived d;
//d.FunTest1();
FunTest();
system("pause");
return 0;
}
上面这个图是Base类的对象模型,在Base类中多了四个字节,里面存放了一个虚表指针,指向一个虚函数表,表中存放了虚函数的地址。
下面是一个我们容易出错的小问题:
Base* pb = new Base;
pb->FunTest1();
pb = (Base*)new Derived;//与强转无关只和当前指向对象有关
pb->FunTest1();
下面再来看一段程序:
class Base
{
public:
virtual void FunTest1()
{
cout<<"B::FunTest1()"<<endl;
}
virtual void FunTest2()
{
cout<<"B::FunTest2()"<<endl;
}
/*virtual */void FunTest3()
{
cout<<"B::FunTest3()"<<endl;
}
virtual void FunTest4()
{
cout<<"B::FunTest4()"<<endl;
}
};
class Derived:public Base
{
public:
virtual void FunTest1()
{
cout<<"D::FunTest1()"<<endl;
}
virtual void FunTest2()
{
cout<<"D::FunTest2()"<<endl;
}
virtual void FunTest3()
{
cout<<"D::FunTest3()"<<endl;
}
virtual void FunTest4()
{
cout<<"D::FunTest4()"<<endl;
}
int _d;
};
typedef void(*Fun)();
void FunTest(Base& b)
{
Fun* pFun = (Fun*)*(int*)&b;
while(*pFun)
{
(*pFun)();
pFun = (Fun*)((int*)pFun+1);
}
}
int main()
{
Base b;
Derived d;
FunTest(b);
system("pause");
return 0;
}
根据上面的一些验证我们可以知道:
虚表形成:
基类:和虚函数在基类中出现次序一致。
派生类:先拿一份基类虚函数(派生类重写了就替换基类的)新加的出现在基类虚函数后边。
总结:
多态调用(必须是虚函数)
1.取虚表指针
2.取该虚函数在虚表中的偏移量
3.调用虚函数