C/C++_虚函数
发布日期:2021-06-30 15:33:44 浏览次数:5 分类:技术文章

本文共 2857 字,大约阅读时间需要 9 分钟。

虚函数推出的目的是为了解决派生类调用基类的问题,在最早派生类继承基类之后,当派生类想要调用基类的方法时必须使用“derived_classes->base_classes::func”

很显然这样比较龊,其次是不太符合泛型编程的思想,因为基类里应描述派生类的基本行为,但如果派生类的种类不同很显然某些基本行为并不能满足派生类。

如:

狼,蜘蛛,老虎,蛇,都属于动物

那么动物就是基类,狼,蜘蛛,老虎,蛇都属于派生类

但是它们攻击的姿势不同,狼和老虎是咬,蜘蛛是缠网,蛇是缠绕,那么这种情况下如果基类已经实现了基本的攻击方法,那么这些攻击姿势都是一致的,在开发游戏过程中很明显这样不符合游戏规则,因为基类的实现函数只有一个,都使用基类的攻击函数那么岂不是都是一个攻击姿势了!

所以诞生了虚函数,基类实现虚函数,派生类可以继承基类的虚函数重新实现虚函数功能。

如动物基类:

class Animal{    public        void having_dinner(){}        void attack(){}        void sleep(){}};

有三种方法,分别对应:、

having_dinner 吃饭

attack 攻击

sleep 睡觉

这些是动物基本技能

如果当产生一个子类,如羚羊

class Antelope : public Animal{    void jump(){}};

羚羊继承父类,拥有动物的基本行为,并且羚羊有自己的特殊技能,如跳跃

同时羚羊不需要去编写吃饭,攻击,以及睡觉这些行为,因为它已经从父类动物里继承了动物的基本行为规范

它拥有动物的基本行为,同时也拥有动物种类最擅长的技能。

这样的好处是不需要在去重复编写额外的代码,这就是继承的好处

当然这种情况下只是不考虑GUI的情况下,倘若你在编写游戏的时候,不可能让你的模型动作都一致吧,羚羊的攻击是用角,狼的攻击是咬,父类里只能实现一个方式,这个时候虚函数的好处就来了

当父类实现的攻击动画是咬,当哺乳类动物继承父类时非常好,因为狼和虎还有豹子,都是撕咬,就不需要在自己重写虚函数了,但是如果是羚羊之类的动物,我们只需要重写这个攻击虚函数即可,其它的吃饭以及睡觉可以不变,大大提升开发周期以及效率

定义虚函数的方法就是在函数定义前面加上virtual

修改后的父类与子类:

父类:

class Animal{    public        virtual void having_dinner(){}        virtual void attack(){}        virtual void sleep(){}};

子类:

子类重写虚函数不需要加上virtual

class Antelope : public Animal{    void having_dinner(){        //实现你自己的攻击方式    }    void jump(){}};

同时你需要知道,虚函数表

虚函数表存在于基类或者每个拥有虚函数的类中

虚函数表是c++实现多态的重要特点

1.虚函数表放在虚类的首地址中,一般占用4字节(32位)

2.子类没有虚函数的情况下继承带有虚函数的父类,子类头是没有虚函数表的,但是存在于父类中,当子类继承父类,子类被申请出来,父类会被一并申请出来

3.子类在有虚函数的情况下那么头部也会存有虚函数表

4.虚函数表的作用是存储虚函数的地址,当子类继承父类时,调用虚函数时,编译器会先在当前类下寻找同名函数若没有则到父类的虚函数表里寻找同名函数,找到则调用,如果子类有同名函数则调用子类

如下代码很容易理解第4条的含义

#include 
using namespace std;class d{ public: virtual void dd(){ cout << "2" << endl; }};class f : public d{ public: void dd(){ cout << "1" << endl; }};int main(){ f sd; sd.dd();}

输出:1

如果把子类实现去掉:

#include 
using namespace std;class d{ public: virtual void dd(){ cout << "2" << endl; }};class f : public d{};int main(){ f sd; sd.dd();}

输出:2

5.虚类头的虚函数表其实是虚函数指针,它是一个指向列表的指针,虚函数表存在于内存的某一块,由编译器编译时生成好指令代码分配,虚函数指针指向这个列表,所以无论虚函数表多大,虚类的头总是4字节,当你的编译位数发生改变时这个大小也会发生改变,因为指针随着位数大小而变

 

同时虚函数还有别的用法,向上类型转换

如:

A *a = new B();

这种情况下A是父类,B是子类,这是教科书上最常用的一种方法,目的是为了实现向上类型转换

父类指向子类,意思是父类具有子类所有功能,同时父类可以访问派生类里的成员变量,并且具备父类自己的功能,避免扩展类发生不可预知的问题,这是教科书上的前辈给出的思想,因为扩展类没有人知道会发生什么问题,但是这种类型转换可以控制派生类的行为规范

如:

#include 
using namespace std;class d{ public: virtual void dd(){ cout << "2" << endl; } private: int d;};class f : public d{ public: void fs(){ }};int main(){ d *sd = new f(); sd->dd(); sd->fs();}

会报错:

但是父类里调用虚类会去调用子类的,这很好的规范了行为,防止调用一些扩展类不属于派生类规范的函数。

向上类型转换:

即派生类赋值给父类引用或者指针

因为派生类继承父类,同时具有父类的实例,但是因为是父类指针,它只能调用父类里的方法,同时它指向派生类,不用担心内存偏移的问题c++编译器会帮我们完成。

转载地址:https://jrhar.blog.csdn.net/article/details/110067040 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:C/C++ 异常( std::exception)
下一篇:Windows核心编程_修改U盘图标

发表评论

最新留言

关注你微信了!
[***.104.42.241]2024年04月18日 23时39分49秒