C++:多态
一、基本概念
1、静态多态(编译多态):函数重载和运算符重载
2、动态多态(运行多态):派生类和虚函数
3、静态多态和动态多态的区别函数地址早绑定(静态联编)还是晚绑定(动态联编)。
二、静态联编
#include <iostream>
using namespace std;
class Animal
{
public:
void speak()
{
cout << "动物在说话" << endl;
}
};
class Cat: public Animal
{
public:
void speak()
{
cout << "小猫在说话" << endl;
}
};
// 存在父子关系的类,其指针或引用可直接相互转换
void doSpeak(Animal& animal) // Animal& animal = cat
{
// 地址早就绑定好了,属于早绑定,是静态联编。
animal.speak();
}
int main()
{
Cat cat;
// 存在父子关系的类,其指针或引用可直接相互转换
doSpeak(cat); // 输出结果为 动物在说话
}
三、动态联编
动态多态产生条件:
a、存在父子继承关系;
b、父类函数为虚函数,子类对父类对应函数进行重写;
c、父类的指针或引用指向子类对象。
// ***注意与上面静态联编代码一起看,比较两者间的区别***
#include <iostream>
using namespace std;
class Animal
{
public:
// 虚函数
virtual void speak()
{
cout << "动物在说话" << endl;
}
};
class Cat: public Animal
{
public:
// 子类对父类虚函数进行重写,该重写函数可不写为虚函数,即可不写关键字virtual.
void speak()
{
cout << "小猫在说话" << endl;
}
};
// 存在父子关系的类,其指针或引用可直接相互转换
void doSpeak(Animal& animal) // Animal& animal = cat
{
// 父类中的speak函数为虚函数,在运行时再绑定,为动态联编。
animal.speak();
}
int main()
{
Cat cat;
// 存在父子关系的类,其指针或引用可直接相互转换
doSpeak(cat); // 输出结果为 小猫在说话
}
四、重写及多态原理
1、重写:子类重写父类中的虚函数,须返回值、函数名、形参列表相同。
2、多态原理:
a、当父类写了虚函数,其内部结构发生变化,多了一个虚函数表指针(vfptr);
b、虚函数指针(vfptr) -> 虚函数表(vftable);
c、虚函数表中记录着虚函数的入口地址;
d、当父类指针或引用指向子类对象,发生多态,调用时从虚函数表中寻找函数入口地址。
五、利用指针实现对象函数调用
// 该实例须在vs编译器下编译运行。笔者最开始是在DEV_C上运行的,结果直接报错,在vs2013上就不存在该问题了。
#include <iostream>
using namespace std;
class Animal
{
public:
// 虚函数
virtual void speak()
{
cout << "动物在说话" << endl;
}
virtual void eat(int var, float flag)
{
cout << "动物在吃饭" << endl;
}
};
class Cat : public Animal
{
public:
// 子类对父类虚函数进行重写,该重写函数可不写为虚函数,即可不见关键字virtual.
void speak()
{
cout << "小猫在说话" << endl;
}
// 子类对父类中的eat函数进行了重写(返回值、函数名及形参列表相同)
void eat(int var, float flag)
{
cout << "小猫在吃饭" << endl;
}
};
int main()
{
// 父类仅存在两个虚函数,那么就创建了一个虚函数指针用于指向虚函数表。虚函数表中存有两个虚函数的入口地址。
cout << "sizeof Animal: " << sizeof(Animal) << endl; // 4
Animal* animal = new Cat;
// 调用speak
// *(int*)animal转到虚函数表中;*(int*)*(int*)animal转到虚函数speak入口地址
((void(*)())(*(int*)*(int*)animal))();
// 调用eat
// vs2013中,C/C++函数默认调用惯例:__cdecl
// 利用下面指针形式调用其调用惯例应为 __stdcall,因此应通过如下方式进行修改。
typedef void(__stdcall *pfun)(int, float); // eat对应函数指针
// (int*)*(int*)animal + 1 eat对应虚函数入口指针
(pfun(*((int*)*(int*)animal + 1)))(10, 0.0);
system("pause");
return 0;
}