优设网站官网,外贸网站建设 杭州,徐州建设安全监督网站,wordpress中文分享插件下载多态性是将接口与实现进行分离#xff1b;用形象的语言来解释就是实现以共同的方法#xff0c;但因个体差异#xff0c;而采用不同的策略。 1、什么是多态 多态#xff08;Polymorphism#xff09;按字面的意思就是“多种状态”。在面向对象语言中#xff0c;接口的…多态性是将接口与实现进行分离用形象的语言来解释就是实现以共同的方法但因个体差异而采用不同的策略。 1、什么是多态 多态Polymorphism按字面的意思就是“多种状态”。在面向对象语言中接口的多种不同的实现方式即为多态。它是面向对象程序设计OOP的一个重要特征。如果一个语言只支持类而不支持多态只能说明它是基于对象的而不是面向对象的。C中的多态性具体体现在运行和编译两个方面。它可以简单地概括为“一个接口多种方法”。 那么多态的作用是什么呢封装可以使得代码模块化继承可以扩展已存在的代码他们的目的都是为了代码重用。而多态的目的则是为了接口重用。也就是说不论传递过来的究竟是那个类的对象函数都能够通过同一个接口调用到适应各自对象的实现方法。
#define _CRT_SECURE_NO_WARNINGS 1
#include iostream
using namespace std;
class Animal
{
public:virtual void Sleep() //这里的关键字virtual后面会讲到{cout Sleep() endl;}
public:char sex[5]; int age;
};class Person: public Animal
{
public:virtual void Sleep(){cout Person lying down to sleep! endl;}
public:char notionality[20]; //国籍char name[10];
};class Horse: public Animal
{
public:virtual void Sleep(){cout Horse sleep standing up! endl;}
public:char rase[20]; //种族
};int main()
{Person p;Horse h;Animal *pa1 p;Animal *pa2 h;pa1-Sleep();pa2-Sleep();return 0;
}
输出 可以看到虽然pa1、pa1都是基类的指针但是调用时却调用的是Person、Horse两个派生类的方法。这是因为pa1和pa2分别指向了Person、Horse两个派生类的对象。原来下来慢慢剖析。
2、多态的分类 ①编译时多态也叫静态多态在编译时就可以确定对象使用的形式。 实现机制使通过函数重载运算符重载实质也属于函数重载。 ②运行时多态也叫动态多态其具体引用的对象在运行时才能确定。 实现机制是通过虚函数。 静态多态性与动态多态性的实质区别就是函数地址是早绑定还是晚绑定。如果函数的调用在编译器编译期间就可以确定函数的调用地址并生产代码是静态的就是说地址是早绑定的。而如果函数调用的地址不能在编译器期间确定需要在运行时才确定这就属于晚绑定。
3、动态多态性存在的三个必要条件 ①要有类的继承关系 ②要有虚函数只有重写了虚函数的才能算作是体现了C多态性 注意函数重写、重载、重定义的区别可以参考 http://blog.csdn.net/ljx_5489464/article/details/51127081 ③要有父类指针/引用指向子类对象用于访问派生类中同名覆盖成员函数。
4、多态的实现 C多态性是通过虚函数来实现的通过指向派生类的基类指针或引用访问派生类中同名覆盖成员函数。虚函数允许子类重新定义成员函数而子类重新定义父类的做法称为覆盖(override)或者称为重写重写的话可以有两种直接重写成员函数和重写虚函数只有重写了虚函数的才能算作是体现了C多态性。 虚函数在某基类中声明为 virtual 并在一个或多个派生类中被重新定 义的成员函数用法格式为 virtual 函数返回类型 函数名参数表 { 函数体; } 关于虚函数这里不做过剖析可以参考 http://blog.csdn.net/ljx_5489464/article/details/51138393 前面说了多态的动态性实现是靠虚函数它的实质是在基类定义一个虚函数在派生类中对其进行重写然后定义基类的指针指向派生类对象然后用该指针调用虚函数此时调用的就是指针指向的对象对应的类里面的同名函数。那么这种机制在底层是怎样实现的呢 想要弄清楚这个就必须要清楚一个东西–虚表全称为虚拟函数表。什么是虚表虚表就是一张表它里面存放虚函数的入口地址在C语言中每个有虚函数的类或者基类有虚函数的派生类编译器都会为它生成一个虚拟函数表注意虚表是从属于类的。此外编译器会为包含虚函数的类加上一个成员变量是一个指向该虚函数表的指针常被称为vfptr注意虚表指针是从属于对象的。也就是说如果一个类含有虚表则该类的所有对象都会含有一个虚表指针并且该虚表指针指向同一个虚表。 虚表的内容是依据类中的虚函数声明次序–填入函数指针。派生类会继承基础类别的虚表以及所有其他可以继承的成员再在此虚表上添加上自己的虚函数指针当我们在派生类中改写虚函数时虚表就受了影响改写后的的虚函数的地址会替换原虚函数的地址存放于虚表中。
#define _CRT_SECURE_NO_WARNINGS 1
#include iostream
using namespace std;class no_Virtual
{
public:void Fun(){cout no_Virtual::Fun() endl;}
};class Virtual
{
public:virtual void Fun() //注意这儿加了关键字virtual{cout Virtual::Fun() endl;}
};int main()
{cout sizeof(no_Virtual) endl;cout sizeof(Virtual) endl;return 0;
} ①no_Virtual类没有任何数据成员也没有虚函数按说它的大小应该是0但这儿它的大小是1。我理解的原因有以下两点 这个是因为no_Virtual既然是一个类型那么就能用来定义变量而定义变量肯定要分配空间分配多大合适呢0个显然不可能2个、4个太浪费了它什么都不用存储分配那么大干什么所以编译器就为我们选了个折中的办法分配一个既不会浪费空间也能用来定义变量。 既然no_Virtual是类型用来定义变量的那么要是它一个空间都不占的话用它定义出来的变量就都从同一位置开始存储了那我们访问那块空间时怎么知道到底是访问哪一个变量呢 ②Virtual类没有任何数据成员但是有虚函数按我们上面的说法它的大小是4结果很给面子的正确了。 定义一个Virtual类的变量看看是不是像上面说的那样有一个从属于它的虚表指针从监视窗口看确实是的。
#define _CRT_SECURE_NO_WARNINGS 1
#include iostream
using namespace std;class Base
{
public:virtual void Fun1(){cout Base::Fun1() endl;}virtual void Fun2() //注意这儿加了关键字virtual{cout Base::Fun2() endl;}
};class Derived: public Base
{
public:virtual void Fun3() //注意这儿加了关键字virtual{cout Derived::Fun3() endl;}virtual void Fun4() //注意这儿加了关键字virtual{cout Derived::Fun4() endl;}
};void PrintVirTable()
{Base b;Derived d;cout Base endl;int *vfpTable (int *)(*(int*)b);while (*vfpTable ! NULL){int *pFun (int*)(*vfpTable);cout pFun endl;vfpTable;}cout Derived endl;vfpTable (int *)(*(int*)d);while (*vfpTable ! NULL){int *pFun (int*)(*vfpTable);cout pFun endl;vfpTable;}
}int main()
{PrintVirTable();return 0;
}
输出结果
因为VS的监视窗口好像有Bug 像这儿派生类的对象就只能看到继承与基类的虚表而不能看到派生类自己的所以上面我一个函数分别打印了基类与派生类的虚表可以很直观的看到首先派生类的虚表是继承于基类的然后在加上自己的虚函数的地址到虚表中去就构成了自己的虚表。 现在来剖析一下打印虚表的函数是怎么实现的 首先得清楚带有虚函数的类创建的对象前四个字节里面存储的就是虚表指针的地址如下图
再回过头来看看虚表的打印函数
void PrintVirTable()
{Base b;Derived d;cout Base endl;int *vfpTable (int *)(*(int*)b);while (*vfpTable ! NULL){int *pFun (int*)(*vfpTable);cout pFun endl;vfpTable;}cout Derived endl;vfpTable (int *)(*(int*)d);while (*vfpTable ! NULL){int *pFun (int*)(*vfpTable);cout pFun endl;vfpTable;}
}
它的原理其实是这样的
再来看一个例子
#define _CRT_SECURE_NO_WARNINGS 1
#include iostream
using namespace std;class Base
{
public:virtual void Fun1(){cout Base::Fun1() endl;}virtual void Fun2() //注意这儿加了关键字virtual{cout Base::Fun2() endl;}
};class Derived: public Base
{
public:virtual void Fun3() //注意这儿加了关键字virtual{cout Derived::Fun3() endl;}virtual void Fun4() //注意这儿加了关键字virtual{cout Derived::Fun4() endl;}
public:int data;
};void PrintVirTable()
{Derived d1;Derived d2;cout d1:virTable endl;int *vfpTable (int *)(*(int*)d1);while (*vfpTable ! NULL){int *pFun (int*)(*vfpTable);cout pFun endl;vfpTable;}cout d2:virTable endl;vfpTable (int *)(*(int*)d2);while (*vfpTable ! NULL){int *pFun (int*)(*vfpTable);cout pFun endl;vfpTable;}
}int main()
{PrintVirTable();return 0;
}
输出结果
这儿就证明了如果一个类有虚函数那么该不同对象公用同一张虚表。因为函数的地址是在编译期间的确定的一个函数的地址只会被存储一次虽然累的对象不同但它们调用该类的同名函数其实是调用的同一函数因此它们的虚表会相同。