1 虚函数
类中的成员函数原型前面加上virtual 表面这个函数是个虚函数。虚函数的目的是为了在继承它的派生类中重新定义这个函数,以便于通过基类的指针或引用在运行时对派生类的函数进行调用。
2 派生类和虚函数
派生类一般情况下要重定义所继承的虚函数,有几个注意事项。
<1>虚函数的声明必须和基类中的函数声明原型完全一致,例外的是当基类返回基类型的指针或者引用的时候,派生类可以派生类类型的指针或者引用
<2>virtual 关键词加不加无所谓,但是一般要加上,能看的清楚这是个虚函数
<3>virtual的标签一旦在基类中的函数前面加上,就永远也去不掉
3 动态绑定的触发
C++的函数调用默认不使用动态绑定,使用动态绑定需要两个必须的条件
<1>通过基类类型的指针或者引用进行函数的调用
<2>函数是虚函数
通过这里也可以说明,当通过指针或者引用调用函数的时候,即便派生类重定义了基类的版本,也不会调用派生类的版本,因为它不会触发多态。
4 虚函数的覆盖机制
有时候通过指针或者引用不想触发多态,此时可以通过加限定符来显式的调用基类的函数
Derived d; Base *p = &d; p->Base::func();如果在派生类中的函数定义的时候想要调用基类的同名函数那么也要这样显式的调用,否则会引发无穷递归。
5 虚函数跟复制控制
<1>通常将析构函数设置为虚函数,这是因为析构函数在执行的时候只会执行自身的部分。例如通过基类的指针或者引用来调用析构函数的话,那么只会调用基类的构造函数,派生类的构造函数将不会被调用。
<2>构造函数不能定义为虚函数,因为在构造函数是在对象完全构造之前运行的,在构造函数执行的时候,对象的动态类型还不完整。
<3>赋值操作符原理上可以设置为虚函数,但是这样做是没意义的。原因如下:
虚函数要求函数原型完全一致,这其中包括了函数参数类型的一致,如果我们把operator = 设置为虚函数,那么在基类中函数的参数是基类类型,在派生类虚函数的参数类型依然是基类类型。但是赋值操作符是要求函数参数类型和类类型一致,这样就会产生非常容易混淆的东西。
class Base{ public: virtual Base& operator =(Base& xx); } class Derived : public Base { public: virtual Derived& operator =(Base& xx); //virtual function Derived& operator =(Derived& xx); //real operator = }
6 构造函数和析构函数中的虚函数
在运行构造函数和析构函数的时候,此时的对象都不是一个完整的类,这个时候编译器将对象的对象视作在构造期间发生了变化,如果在其中调用虚函数的话,那么调用的将会是类自身类型
定义的版本。
原因:基类在构造的时候,派生类部分的成员还没有初始化,如果此时调用的是派生类的虚函数,那么派生类的虚函数访问类成员就会出问题。同理基类的析构函数也是这样子,因为析构函
数直管自身成员的释放,派生类在调用析构函数的时候先调用自身的析构函数,然后再去调用基类的析构函数,所以基类的析构函数不可能调用派生类的虚函数,因为派生类的成员已经被释
放。实例如下:
//Base.h #pragma once #include <iostream> using namespace std; class Base { public: Base(void){ func();}; ~Base(void){ funp();}; virtual void func(){cout<<"this is in Base constructor"<<endl;} virtual void funp(){cout<<"this is in Base destructor"<<endl;}; };
//Derived.h #pragma once #include "base.h" class Derived : public Base { public: Derived(void){func();} ~Derived(void){func();} virtual void func(){cout<<"this is in Derived constructor"<<endl;} virtual void funp(){cout<<"this is in Derived destructor"<<endl;}; };
//main.cpp #include "Derived.h" void main() { Base X; Derived Y; }
7 纯虚函数
虚函数的形参后面跟上“=0”则表示纯虚函数。纯虚函数需要注意的有以下几点:
<1>纯虚函数没有函数体;
<2>最后面的“=0”并不表示函数返回值为0,它只起形式上的作用,告诉编译系统“这是纯虚函数”;
<3>这是一个声明语句,最后应有分号。
<4>函数不能被调用
<5>不能创建有纯虚函数的类对象
<6>如果在一个类中声明了纯虚函数,而在其派生类中没有对该函数定义,则该虚函数在派生类中仍然为纯虚函数。
含有一个或者多个纯虚函数的类被称为抽象类,抽象类不能创建对象。抽象类的意义在于提供功能基础接口,然后由派生类来实现,在此基础上实现多态。但是抽象类可以作为引用或者指针。实例程序如下:
//Base.h #pragma once #include <iostream> using namespace std; class Base { public: Base(void){ func();}; ~Base(void){ funp();}; virtual void func(){cout<<"this is in Base constructor"<<endl;} virtual void funp(){cout<<"this is in Base destructor"<<endl;}; virtual void fuck()=0; };
//Derived.h #pragma once #include "base.h" class Derived : public Base { public: Derived(void){func();} ~Derived(void){func();} virtual void func(){cout<<"this is in Derived constructor"<<endl;} virtual void funp(){cout<<"this is in Derived destructor"<<endl;}; virtual void fuck(){cout<<"what a fuck day it is!"<<endl;} };
//main.cpp #include "Derived.h" void main() { Derived Y; Base& X = Y; }