多态
多态
这是面向对象编程世界中的三大特性之一,各支持面向对象编程语言都对其有语法上的支持。
分别从多态的产生以及应用场景、多态效果的模拟、以及C++对多态机制的实现原理等进行阐述
多态的产生极其应用场景
在面向对象世界中,类,是基础单位,这本质上是在模拟现实世界中的构建规则。
在面向对象世界中有一个继承的概念,这本质上是对封装的进一步应用,也是代码复用的重要手段,但是从逻辑上讲却存在无法避免的缺陷。
比如正方形继承自矩形,但是矩形可以分别控制临边的不同长度,而正方形只能只能控制所有边的长度一致,因此,当实现『修改』变长方法时,子类与父类产生了不同。
可以认为这种机制既是面向对象的Bug,也是面向对象的feature。
从编程语言的角度上讲,等号(=)两侧的值与类型必须是一致的,当存在基类与派生类是,可以认为派生类是(is-a)基类,此时,等号左侧的基类指针便能指向派生类(类型是一致的),那么,当调用父类的接口方法时,到底应该执行的是子类的方法还是父类的方法呢? 从面相对象角度而言应该是父类的方法,而从模拟现实世界的角度而言应该是子类的方法。
而C++中的多态,便是为了处理这种问题而诞生。
当父类指针指向子类,调用父类接口方法时,实质执行的是子类方法,这种特性被称作多态
这种方式的好处在于,可以将父类认为是一个接口,这个接口可以表示各种不同的具体类,通过直接调用父类的方法,就能实现不同类的具体行为。
比如:基类是士兵
从士兵派生出步兵
骑兵
弓箭手
等具体类,士兵有接口方法攻击
可以使用士兵接口指向不同的兵种,直接调用士兵的攻击
方法,则实质就会调用具体不同兵种的攻击方法,这样,就带来了极佳的扩展性,能够更加灵活的构建面向对象的世界。
#include <iostream>
using namespace std;
class Soldier{
public:
//通过virtual关键字修饰函数,此函数就是虚函数,C++多态实现的支持
virtual void attack(){
cout<<"soldier attack"<<endl;
}
};
class Infantry:public Soldier{
public:
void attack(){
cout<<"Infantry attack"<<endl;
}
};
class Cavalry:public Soldier{
public:
void attack(){
cout<<"Cavalry attack"<<endl;
}
};
int main(){
Soldier *sol = new Infantry;
Soldier *sol2 = new Cavalry;
//同一个类名,调用同样的方法,实质是不同对象在执行各种真正的方法
sol->attack();//Infantry attack
sol2->attack();//Cavalry attack
return 0;
}
C++通过关键字virtual
来实现多态的特性。而我们也可以通过函数指针的方式来模拟多态效果
多态效果的模拟
函数指针,定义全局函数指针类型非常简单
#include<stdio.h>
void demoFunc(int a){
int b = a++;
printf("%d\r\n",b);
}
typedef void(*pfunc)(int);
int main(){
pfunc demoFuncPtr = demoFunc;
demoFuncPtr(11);
return 0;
}
那么在C++中,如何定义以及使用类成员函数的函数指针呢?
#include <iostream>
using namespace std;
class Node{
public:
int value;
void showValue(){
cout<<value<<endl;
}
};
//定义一个Node类成员函数的函数指针类型
typedef void(Node::*pvalueType)(void);
int main(){
Node node;
node.value = 12;
node.showValue();//12
//定义成员函数指针变量pvfunc,并指向成员函数showValue
pvalueType pvfunc = &Node::showValue;
//使用非常别扭的方式调用成员函数指针
(node.*pvfunc)();//12
Node *node2 = new Node;
node2->value = 22;
(node2->*fptr)();//22
return 0;
}
通过成员函数指针调用成员函数的方式非常奇怪难看,C++中基本很难见到这种实践方式
通过类成员函数指针的方式模拟实现多态
#include <iostream>
using namespace std;
class Node;
class TTNode;
typedef void(Node::*pvalueType)(void);
class Node{
public:
pvalueType showvaluePtr = &Node::showValue;
int value;
void showValue(){
cout<<"node value "<<value<<endl;
}
};
class TTNode:public Node{
public:
TTNode(){
showvaluePtr = (pvalueType)&TTNode::showValue;
}
void showValue(){
cout<<"ttnode value "<<value<<endl;
}
};
int main(){
Node *node = new TTNode;
node->value = 12;
//通过及其别扭的方式模拟多态
(node->*(node->showvaluePtr))();//ttnode value 12
return 0;
}
C++多态的实现机制
C++多态的实现是通过『建表』完成的。通过虚函数表(函数地址数组)间接调用函数实现多态。
#include <iostream>
using namespace std;
class Node{
public:
Node(int v = 0):value(v){ }
virtual void show(){
cout<<"node show"<<endl;
}
virtual void mixer(){
cout<<"node mixer"<<endl;
}
private:
int value;
};
class TTNode(){
public:
void show(){
cout<<"ttnode show"<<endl;
}
void mixer(){
cout<<"ttnode mixer"<<endl;
}
};
int main(){
return 0;
}
在编译时,C++便会创建一个虚函数地址的数组,根据虚函数的定义顺序依次将虚函数地址放入这个数组中,这个数组又被称为虚函数表,每个类对应一个虚函数表。
在运行时,C++会将对应的虚函数表的地址安置在具体对象的首地址内存处(第一个成员),实质就是一个数组指针类型,32bit系统下占4字节,64bit系统下占8字节。
所以也就是说,每个类都有一张属于该类的虚函数表,每个对象都有一个虚函数表的指针,执行属于该类的虚函数表地址。
当产生虚函数的多态调用时:
1、通过虚函数表地址找到虚函数表
2、通过虚函数名字对应的顺序,取出数组中对应下标的虚函数地址
3、跳转到该地址
4、调用真正的函数地址
在此过程中,虚函数表的建立处在编译期,而真正将虚函数表的内存地址放入对象中,处在运行期。
在32bit系统中,TTNode
类的内存占8字节
,其中,从父类Node
中继承过来的成员value
占4个字节,虚函数表地址占4个字节。
posted on 2021-12-28 00:23 shadow_fan 阅读(33) 评论(0) 编辑 收藏 举报