# 虚函数


## ***虚函数***

1. *虚函数的种类*
   
   - *non-virtual： 你不希望派生类重新定义*
   
   - *virtual：你希望派生类重新定义，且他有默认定义*
   
   - *pure virtual：你希望派生类一定要重新定义，你对他没有默认定义*
     
     ```cpp
     class Shape {
     public:
       virtual void draw() const = 0; // pure virtual
       virtual void error(const std::string &msg); // impure virtual
       int objectID() const; // non-virtual
     };
     ```

## ***虚函数的实现原理***

1. *虚表与虚指针*
   
   - *我们先把下述代码扫一遍，并且来看看图，我们可以看到最左边的表 上面记录着 继承的父类和 自己的数据，但是除了这些之外，是不是还有一个记录着内存地址的，这个就是**虚指针（vtpr）**这个虚指针指向着一张**虚表（vtbl）***
     
     ```cpp
     class A {
     pubilc:
         virtual void vfunc1();
       virtual void vfunc2();
       void func1();
       void func2();
     private:
       int m_data1, m_data2;
     };
     
     class B : public A {
     pubilc:
         virtual void vfunc1();
       void func2();
     private:
       int m_data3;
     };
     
     class C : public B {
     pubilc:
         virtual void vfunc1();
       void func2();
     private:
       int m_data1, m_data4;
     };
     ```
     
     ![虚表和虚指针](https://raw.githubusercontent.com/vlicecream/cloudImage/main/data/imagesimage-20220521213437372.png)

2. *c++对一个虚函数进行调用的过程*
   
   - *他首先会考虑是**静态绑定**还是**动态绑定***
   - *静态绑定 就是 call xxx  call(汇编语言的一个动作) xxx(addr)*
   - *如果是满足三个条件就会动态绑定*
     - *通过指针来调用*
     - *指针满足向上转型*
     - *调用的是虚函数*
     - *只要满足这三个条件 就会变成 `(*(p->vptr)[n])(p);` 或者 `(*p->vptr[n] )(p);`*
     - *为什么叫动态绑定，因为要看"p"是什么*

## ***虚函数的妙用***

1. *通过虚指针我们可以实现多态*
   
   - *比如 一个list 需要放多个不同大小的 比如 矩形 圆形 长方形 各种的 就可以直接放父类A 的指针 指向不同大小的 具体图形 因为对于指针来说 都是4个byte*
     
     ```cpp
     list<A*> myList;
     ```

## ***虚函数的注意点***

1. ***析构函数要写成虚函数***
   
   - *是为了降低内存泄漏的可能性*
   - *一个基类的指针指向一个派生类的对象，在使用完毕准备销毁时，如果基类的析构函数没有定义成虚函数，那 么编译器根据指 针类型就会认为当前对象的类型是基类，调用基类的析构函数，仅执行基类的析构，所以造成内存泄漏*

2. ***构造函数不写成虚函数***
   
   - *我们创建一个对象，是需要知道对象的完整信息的。特别是要知道对象的具体类型，而虚函数只需要知道函数接口，而不需要知道具体类型，所以构造函数不应该被定义为虚函数*
   - *从目前编译器实现虚函数进行多态的方式来看，虚函数的调用是通过实例化之后对象 的虚函数表指针来找到虚函数的地址进行调用的，如果说构造函数是虚的，那么虚函数表 指针则是不存在的，无法找到对应的虚函数表来调用虚函数，那么这个调用实际上也是违 反了先实例化后调用的准则*

3. ***构造函数和析构函数中不要调用虚函数***
   
   - *举例来说，有一个动物的基类，基类中定义了一个动物本身行为的虚函数 action_type()， 在基类的构造函数中调用了这个虚函数*
     
     *派生类中重写了这个虚函数，我们期望着根据对象的真实类型不同，而调用各自实现的虚函 数，但实际上当我们创建一个派生类对象时，首先会创建派生类的基类部分，执行基类的构造 函数，此时，派生类的自身部分还没有被初始化，对于这种还没有初始化的东⻄，C++选择当 它们还不存在作为一种安全的方法*
     
     *派生类中重写了这个虚函数，我们期望着根据对象的真实类型不同，而调用各自实现的虚函 数，但实际上当我们创建一个派生类对象时，首先会创建派生类的基类部分，执行基类的构造 函数，此时，派生类的自身部分还没有被初始化，对于这种还没有初始化的东⻄，C++选择当 它们还不存在作为一种安全的方法*
   
   - *在析构函数中也是同理，派生类执行了析构函数后，派生类的自身成员呈现未定义的状态，那
     么在执行基类的析构函数中是不可能调用到派生类重写的方法的*

4. ***哪些函数不能写成虚函数***
   
   - ***构造函数：*** *上述已说明*
   - ***内联函数：*** *内联函数表示在编译阶段进行函数体的替换操作，而虚函数意味着在运行期间进行 类型确定，所以内联函数不能是虚函数*
   - ***静态函数：*** *静态函数不属于对象属于类，静态成员函数没有this指针，因此静态函数设置为虚 函数没有任何意义*
   - ***友元函数：*** *友元函数不属于类的成员函数，不能被继承。对于没有继承特性的函数没有虚函数 的说法*
   - ***普通函数：*** *普通函数不属于类的成员函数，不具有继承特性，因此普通函数没有虚函数*

