静态联编和动态联编
多态是面向对象程序设计语言中数据抽象和继承之外的第三个基本特征。
多态性(polymorphism)提供接口与具体实现之间的另一层隔离,从而将”what”和”how”分离开来。多态性改善了代码的可读性和组织性,同时也使创建的程序具有可扩展性,项目不仅在最初创建时期可以扩展,而且当项目在需要有新的功能时也能扩展。
c++支持编译时多态(静态多态)和运行时多态(动态多态),运算符重载和函数重载就是编译时多态,而派生类和虚函数实现运行时多态。
静态多态和动态多态的区别就是函数地址是早绑定(静态联编)还是晚绑定(动态联编)。如果函数的调用,在编译阶段就可以确定函数的调用地址,并产生代码,就是静态多态(编译时多态),就是说地址是早绑定的。而如果函数的调用地址不能编译不能在编译期间确定,而需要在运行时才能决定,这这就属于晚绑定(动态多态,运行时多态)。
静态联编地址早绑定 编译阶段绑定好地址
动态联编地址晚绑定 运行阶段绑定好地址
多态的成立条件
1.有继承
2.子类重写父类虚函数函数(1)返回值,函数名字,函数参数,必须和父类完全一致(析构函数除外)
(2)子类中virtual关键字可写可不写,建议写3.类型兼容:父类指针,父类引用 指向子类对象
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Animal
{
public:
virtual void speak()
{
cout << "动物在说话" << endl;
}
virtual void eat()
{
cout << "动物在吃饭" << endl;
}
};
class Cat :public Animal
{
public:
void speak()
{
cout << "小猫在说话" << endl;
}
virtual void eat()
{
cout << "小猫在吃鱼" << endl;
}
};
//调用doSpeak ,speak函数的地址早就绑定好了,早绑定,静态联编,编译阶段就确定好了地址
//如果想调用猫的speak,不能提前绑定好函数的地址了,所以需要在运行时候再去确定函数地址
//动态联编,写法 speak方法改为虚函数,在父类上声明虚函数,发生了多态
// 父类的引用或者指针 指向 子类对象
void doSpeak(Animal & animal) //Animal & animal = cat
{
animal.speak();
}
//如果发生了继承的关系,编译器允许进行类型转换
void test01()
{
Cat cat;
doSpeak(cat);
}
void test02()
{
//cout << sizeof(Animal) << endl;//在写关键字virtual之前,输出结果为1.之后,输出结果为4
//父类指针指向子类对象 多态
Animal * animal = new Cat;
//animal->speak(); 打印出 小猫在说话。
//内部如何调用speak的?
// *(int*)*(int*)animal 函数地址
//*(int*)animal此时已经找到虚函数表首地址
((void(*)()) (*(int*)*(int*)animal))();
// *((int*)*(int*)animal+1)猫吃鱼的地址
((void(*)()) (*((int*)*(int*)animal + 1)))();
}
int main(){
//test01();
test02();
system("pause");
return EXIT_SUCCESS;
}
多态原理解析
当父类中有了虚函数后,内部结构就发生了改变
内部多了一个 vfptr
virtual function pointer 虚函数表指针
指向 vftable 虚函数表
父类中结构 vfptr &Animal::speak
子类中 进行继承 会继承 vfptr vftable
构造函数中 会将虚函数表指针 指向自己的虚函数表
如果发生了重写,会替换掉虚函数表中的原有的speak,改为 &Cat::speak
深入剖析,内部到底如何调用
((void()()) ((int*)(int)animal))();
虚函数必须是基类的非静态成员函数,其访问权限可以是protected或public.
多态原理解析
可以将下边的图与开发人员命令行工具结合分析。
当父类中有了虚函数后,内部结构就发生了改变。内部多了一个vfptr,virtual function pointer 虚函数表指针,指向vftable,虚函数表。父类中结构,vfptr &Animal::speak 子类中继承 会继承 vfptr vftable 构造函数中,会将虚函数表指针,指向自己的虚函数表。如果发生了重写,会替换掉虚函数表中原有的speak,改为&Cat::speak
计算器案例
#include <iostream>
using namespace std;
class Calculator
{
public:
void stev1(int v1)
{
this->value1 = v1;
}
void stev2(int v2)
{
this->value2 = v2;
}//为私有属性赋值的接口;
int GetResult(string oper)
{
if (oper == "+") {
return value1 + value2;
}
else if(oper == "-"){
return value1 - value2;
}
}//操作;
private:
int value1;
int value2;
};
void test01()
{
Calculator c1;
c1.stev1(10);
c1.stev2(5);
cout << c1.GetResult("+") << endl;
}
int main()
{
test01();
return 0;
}
//计算器版本V1.0
#########################################################################
#include <iostream>
using namespace std;
//在真正的开发中,有一个开闭原则;
//对扩展开放,对修改关闭;
//用多态的方法实现计算器:
class Calculator
{
public:
int setv1(int v1)
{
return value1 = v1;
}
int setv2(int v2)
{
return value2 = v2;
}
虚函数
//virtual int getResult()
//{
// return 0;
//}
纯虚函数
virtual int getResult()=0
public://节约时间,写成public权限;
//若写成private,则应该用get接口获得;
int value1;
int value2;
};
//如果父类中有了 纯虚函数,子类继承父类,就必须要实现 纯虚函数
class A :public Calculator
{
public:
virtual int getResult()
{
return 0;
}
};
class PlusCal :public Calculator
{
public:
int getResult()
{
return value1 + value2;
}
};
class SubCal :public Calculator
{
public:
int getResult()
{
return value1 - value2;
}
};
//直接在原有基础上扩展出新的功能;
class ChengCal :public Calculator
{
public: