## 虚函数 ​ C++中可以通过基类指针或引用操纵子类。也可以对一个子类取地址,并将其当做父类对象使用。但是当使用父类指针操纵子类对象时,函数调用会发生错误: ```c++ #include using namespace std; class base{//基类 public: void show(void){ cout<<"base调用"< 所谓`晚捆绑`,其目的实际上是为了实现`运行时类型识别`,确保根据左值的实际类型调用到正确的函数。 ### 2. 声明虚函数 ​ C++使用`虚函数`实现`晚捆绑`,要使用`虚函数`,只需要在`成员函数中使用virtual关键字`: ```c++ #include using namespace std; class base{//基类 public: virtual void show(void){ cout<<"base调用"< 当基类中的某个函数是虚函数时,其在派生类中被重写的函数依然是虚函数,无论是否添加了virtual关键字。 ### 3. 虚函数的实现机理 ​ 当类中存在虚函数时,编译器将在编译时在这个类的`构造函数`的起始处添加一些代码,用于实现`晚捆绑`的机制。 ​ 当一个类对象创建时,这段代码将会为该对象创建一个表(`VTABLE`),包含这个类中的所有虚函数地址。并且将会自动为类添加一个不可见的成员指针:*vpointer*(称为**VPTR**),指向这个对象的`VTABLE`。 ​ 一旦对象的**VPTR**指向正确的相应的**VTABLE**,对象的类型就会被建立,当虚函数调用时用于帮助完成`晚捆绑`。 > 实际上,VPTR位于对象的起始处,所以,this指针的内容对应于VPTR。 ​ 当通过基类指针调用派生类对象时,程序将会实现通过对象的`VPTR`指向正确的`VTABLE`,然后在`VTABLE`中进行寻址,找到正确的函数。 ### 4. 继承和VTABLE ​ 当继承一个含有虚函数的类时,派生类的虚有函数和基类中的虚有函数在各自的VTABLE拥有相同的位置。而派生类中新增加的虚有函数将会添加到上述虚有函数位置之后。 ​ 当基类中的虚有函数没有被重写时,VTABLE中的地址将会指向基类相应函数的地址。若基类中的虚有函数被重写,则指向重写后的函数。 ### 5.虚函数的重写 ​ 与普通的函数重写不同,虚函数在进行重写时不允许改变函数的返回类型。 ​ 但是和普通的函数重写相同的是:虚函数在进行重写后,基类的其他重载版本全部被隐藏。 > 在对虚函数进行重写时,可以使用`override`关键字表名正在重写一个基类函数。若重写时书写发生错误,将会发生编译器错误: > > void f() override{}; ### 6. 虚函数的返回值 ​ 虽然虚函数重写时的返回类型不允许发生变化,但是却可以改变返回值的类型。 ## 抽象基类和纯虚函数 ​ 有时希望基类的使用仅仅是为了提供接口,而没有实际作用。这时候可能只想在基类中声明函数而不进行实现,而且不希望生成基类对象。要做到这一点,可以使用`纯虚函数`。 ​ 纯虚函数用于抑制类对象的生成,当类中存在任一个纯虚函数时,这个类就称为了一个`抽象类`。 ### 纯虚函数的声明 ​ 要声明纯虚函数,只需要使用`=0`修饰即可: ```c++ class base{ public: void f()=0; }; ``` > 抽象函数只声明,不实现。 ### 纯虚函数函数体 ​ 纯虚函数虽然可以只声明,不实现,但是有时又需要为纯虚函数提供函数体,为其子类提供通用函数。 ​ 纯虚函数的函数体与其声明必须分离: ```c++ class base{ public: virtual void f()=0; }; inline void base::f() { cout<<"base纯虚函数体"; } class derived:public base{ public: void f()override { base::f(); } }; ``` ​ 要调用纯虚函数体,则必须使用`::`限定符进行限定。 ## 构造函数和析构函数中的晚捆绑 ​ 类的构造函数**不允许**声明为一个虚有函数,而析构函数可以。但是相同的是:在构造函数或析构函数体中,晚捆绑机制不起任何作用。主要原因是: ​ 构造函数执行时,类型信息还未完全建立,虚机制不可用。 ​ 如果析构函数中晚捆绑机制可用,则基类对象的析构可能会依赖于子类对象,但是子类对象先于基类析构, 也就是类型信息虽然存在,但是不可信。 > 当类对象生成时,构造函数将沿继承树向下进行构造,即先调用基类构造函数,再调用子类构造函数。确保对象初始化成功。 > > 而当类对象进行析构时,则沿继承树的反方向进行,先调用子类的析构函数,再调用基类的析构函数。 ### 虚析构函数 ​ 当通过父类指针操纵子类对象时,为了确保析构函数正确调用,需要将父类的析构函数声明为虚有,确保调用到正确的析构函数。 ​ 虚析构函数的行为与其他虚函数的行为完全一致。 > ​ 当类中存在虚函数时,应当总是将析构函数声明为虚函数,即使其什么也不做。 ### 纯虚析构函数 ​ 纯虚析构函数的作用和其他纯虚函数的用途相同,用于抑制函数对象的生成。 > 派生类可以不对纯虚析构函数进行重写,这一点和其他纯虚函数不同。 ## 例:基于对象的继承 ​ 当不使用模板建立一个容器类时,要想使容器类容纳多种类型,意味着容器容纳的指针应为void,则意味着容器对所容纳的对象没有所有权。为了正确析构函数,必须将析构函数声明为虚有。 > 当delete一个void*时,不会调用析构函数。也就是说容器对所容纳的对象生命周期没有控制权,即:容器对所容纳的对象没有所有权。