深入理解虚函数

深入理解虚函数

请表达出你的想法,简单直接。 --- KB

什么是虚函数

我们知道面向对象有三大特性,封装、继承、多态,封装和继承就不多说了,大家比较常用,即使你是写Python的也会用到,但是多态就比较复杂了,在C++里,虚函数是实现多态的一个强大的武器。

多态在一些设计模式里比较重要,如模板模式,可以多个不同的模板来呈现同一个业务,或者策略模式里,可以实现不同的策略。也包括我们常说的里氏替换原则。

要理解虚函数,我们来看一个例子,现代人喜欢养宠物,有的喜欢猫,有的喜欢狗,我开发一个小游戏,你可以养一只猫,也可以养一只狗,也可以让他叫

不管你是猫还是狗,反正就是叫,猫就会叫“喵喵喵”,狗就会叫“汪汪汪”。

为了能让大家有个直观的认识,我先画一个小小的demo给大家感受一下。

要怎么去实现这个代码呢?

比较笨的就是:

enum AnimalType = {
    CAT,
    DOG
}

AnimalType animal_type;

Cat *cat = new Cat();

Dog *dog = new Dog();

if (animal_type == CAT)
{
    cat->sound()
} else if (animal_type == DOG)
{
    dog->sound()
}

这样写,肯定不行吧,也太初级了,一定会被领导喷死吧。

那么,要怎么改呢?

就要引入一个叫虚函数的东西。

class Animal
{

    public:
        virtual void jiao(Animal* animal);

}

/* 给我叫 */
Animal::speak(Animal *animal)
{
    animal->jiao();
}

class Cat: public Animal
{

}

Cat::jiao()
{
    // this->label->setText("哈基米,喵喵喵");
    cout<<"哈基米,喵喵喵"<<endl;
}

class Dog: public Animal
{
    
}

Dog::jiao()
{
    cout<<"翻滚吧,汪汪汪">>endl;
}

int main() {
    Animal *myPet;

    Cat cat;

    myPet = &cat;

    myPet->jiao();

    Dog dog;

    myPet = &dog;

    myPet->jiao();

    return 0;
}

bind事件到click信号时,我们调用的都是jiao()这个函数,但是在创建完对象后,我们可以让这个myPet指针指向这个新创建的对象,那么这个时候调用的就是子类的方法。

如果你不使用虚函数,会怎么样呢?那你就不能根据传入的参数或者指向不同的子类对象来调用不同的方法了。

这样就可以根据传入的对象不同,就去调用不同的方法,从而实现多态。

虚函数实现的原理

那么虚函数机制到底是怎么实现的呢?

我们可以看一下声明了虚函数生成的汇编代码:

	.file	"TclObjC.cpp"
	.text
	.align 2
.globl __ZN7TclObjC3addEv
	.def	__ZN7TclObjC3addEv;	.scl	2;	.type	32;	.endef
__ZN7TclObjC3addEv:
	pushl	%ebp
	movl	%esp, %ebp
	subl	$16, %esp
	movl	$3, -4(%ebp)
	leave
	ret
.globl __ZTV7TclObjC
	.section	.rdata$_ZTV7TclObjC,"dr"
	.linkonce same_size
	.align 8
__ZTV7TclObjC:
	.long	0
	.long	__ZTI7TclObjC
	.long	__ZN7TclObjC3addEv
.globl __ZTS7TclObjC
	.section	.rdata$_ZTS7TclObjC,"dr"
	.linkonce same_size
__ZTS7TclObjC:
	.ascii "7TclObjC\0"
.globl __ZTI7TclObjC
	.section	.rdata$_ZTI7TclObjC,"dr"
	.linkonce same_size
	.align 4
__ZTI7TclObjC:
	.long	__ZTVN10__cxxabiv117__class_type_infoE+8
	.long	__ZTS7TclObjC

从.globl __ZTV7TclObjC这里开始,会多分配很多空间给到虚函数,如果不使用vitual关键字的话,这段代码空间是没有的,所以我们猜测这里面应该就是为了给后续动态绑定运行时对象而预留的,因为在编译时,是不知道谁会触发这个虚函数,需要等到运行时以后,才能知道要调用的地址,所以使用了虚函数后,会多生成一些代码,来为运行时的调用改变作一些准备。

虚函数与延迟绑定

在GUI编程中,延迟绑定是特定有用的,因为很多对象,都是跟事件有关,不同的事件就会触发不同的逻辑,这里可能会动态创建一些对象,并且这些对象可能是不同的类型的,他们有各自的方法,这些方法需要根据不同的绑定作用域来被触发。

比如一个图形渲染框架,是需要根据不同的应用进行画面的实时渲染的,那么就需要根据不同的应用,定义一个函数,这个函数需要在派生类中去调用。

纯虚函数

纯虚函数主要用于抽象类,通过纯虚函数,可以定义抽象接口,然后在实例化时,你需要去实现对应的定义。

比如我们的Gui工具框架,你需要在继承后去实现对应的控件,然后再由框架层去调用,所以我们可以定义一个抽象基类BaseApplication.cpp,然后再去实现一个MyApplication.cpp文件。

虚基类

主要用于解决多继承中,基类被实现多次的场景。例如,你写一个多边形的类,这个类会继承Shape和Text,Shape和Text又同时继承UiObject类,那么就有可能在创建多边形类时,UiObject被构造了两次,通过定义虚基类,那么就可以避免这个问题。

结语

感觉把虚函数这个概念讲完,需要花不少时间。

posted @ 2023-12-02 13:41  cleardo  阅读(23)  评论(0编辑  收藏  举报