【C++】多态与虚函数

多态和虚函数

先说说重载,重载要求函数名相同,返回值(返回值不同不构成重载)和形参不用相同,我们常用的重载是一种静态的多态,为什么C语言不能够重载呢?这与c语言的编译器有关,体现在符号表中,c语言的函数在编译时不会像C++那样把参数也添加到对应的符号表里

而多态当中,派生类中对于基类虚函数的重写,要求是返回值和形参都是一致的,否则就是重载。多态要求基类中有虚函数,派生类中重写了基类的虚函数,此外在实际使用时,我们还需要用基类指针指向派生类对象,从而调用派生类实例中经过重写的函数,这个过程叫做动态绑定

纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”
 

virtual void funtion1()=0;

对于纯虚函数,

1、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。
2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。
  为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数,则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。
声明了纯虚函数的类是一个抽象类。所以,用户不能创建类的实例,只能创建它的派生类的实例。
纯虚函数最显著的特征是:它们必须在继承类中重新声明函数(不要后面的=0,否则该派生类也不能实例化),而且它们在抽象类中往往没有定义。
定义纯虚函数的目的在于,使派生类仅仅只是继承函数的接口。
纯虚函数的意义,让所有的类对象(主要是派生类对象)都可以执行纯虚函数的动作,但类无法为纯虚函数提供一个合理的缺省实现。所以类纯虚函数的声明就是在告诉子类的设计者,“你必须提供一个纯虚函数的实现,但我不知道你会怎样实现它”。

抽象类
抽象类是一种特殊的类,它是为了抽象和设计的目的为建立的,它处于继承层次结构的较上层。
(1)抽象类的定义:  称带有纯虚函数的类为抽象类。
(2)抽象类的作用:
抽象类的主要作用是将有关的操作作为结果接口组织在一个继承层次结构中,由它来为派生类提供一个公共的根,派生类将具体实现在其基类中作为接口的操作。所以派生类实际上刻画了一组子类的操作接口的通用语义,这些语义也传给子类,子类可以具体实现这些语义,也可以再将这些语义传给自己的子类。
(3)使用抽象类时注意:
•   抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类中没有重新定义纯虚函数,而只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体的类。
•   抽象类是不能定义对象的。

虚函数的执行过程

C++编译阶段,没办法知道一个基类的指针或引用所指对象的类型,所以没办法通过这个指针判断调用的虚函数到底是谁的,所以只能通过查找虚函数表来找到函数的入口地址。
一个类,如果有虚函数,那么编译器在编译这个类的时候就会为它添加一个虚函数表,以及指向这个虚函数表的指针pv

继承这个基类的派生类,也会新建一个虚函数表,如果没有重载,那么这个新的虚函数表中的函数指针就被拷贝为父类该函数的地址否则为新的函数地址。编译器会将这些函数指针在虚函数表中按照基类中该函数出现的次序排列,子类中的虚函数表也将以这种方式排列。
每个有虚函数的类都有一个虚函数表指针pv,当通过指针或引用调用一个虚函数时,先通过pv找到虚函数表,然后根据这个虚函数在虚函数表中的偏移量来找到正确的函数地址,然后再CALL之。

 

析构函数可以声明为虚函数吗?
1、当然是可以的,而且,为了防止内存泄漏,基类的析构函数必须声明为虚函数!
2、为什么将基类析构函数声明为虚函数,就可以防止内存泄漏?

如果没有将基类析构函数声明为虚函数,在释放指向派生类对象的基类指针的时候,只会调用基类的析构函数,而派生类的析构函数不会被调用,导致属于派生类的新添加的数据得不到释放,从而导致内存泄漏。
如果将析构函数声明为虚函数,在释放指向派生类对象的基类指针的时候,会调用派生类的析构函数,而派生类的析构函数会自动调用基类的析构函数,从而释放所有内存,避免了内存泄漏。

(如果派生类没有额外申请堆空间,也没有额外的类成员,那么直接delete可以删吗?有待考究)

 

为什么将基类析构函数声明为虚函数之后,在释放指向派生类对象的基类指针时,调用的是派生类的析构函数?基类和派生类析构函数名字不相同,理论上来说不构成重写

其实,析构函数是一个特殊的函数,编译器在编译时,析构函数的名字统一为destucter;
所以只要将基类的析构函数声明为虚函数,不管子类的析构函数前是否加virtual,都构成重写。这也就可以解释为什么将基类析构函数声明为虚函数,释放指向派生类对象的基类指针时,会调用派生类的析构函数,因为虚表中的函数指针指向的是派生类的析构函数。

虚函数表

首先要明确的是虚函数表相当于一个类的静态成员,所有的实例都共享一份虚函数表,所以虚函数表应该存在.data(linux平台)

虚函数表是在类之外的,一个类的size不包括虚函数表的大小。而虚函数指针则包含在类中,sizeof一个类则会包含一个虚函数表的指针。(如果该类没有虚函数,自然就没有虚函数指针)

派生类不一定继承了基类的全部虚函数,所以只属于派生类自己的虚函数,是由另外一张表来维护的,同时也有另外一个pv指针来指向

posted on 2022-08-30 09:34  甲鱼写代码  阅读(93)  评论(0)    收藏  举报

导航