c++ 函数指针_虚函数_虚函数表_纯虚函数

函数指针

1: 函数调用: 准备好参数后,把指令指针拨动到函数开始的位置;
2: 函数的开始的指令对应一个代码段上的内存地址,这个内存地址在C/C++里面是函数的名字
3: 除了普通数据类型的指针,还可以定义函数指针;
void (*ptr)(int value); 返回值为void,参数为int的类型的函数的指针,可以存放函数所在的地址;
ptr = 函数的名字;
4: 使用变量可以访问得到这个地址,从而可以使用函数指针来调用函数;
5: typedef 定义一个函数指针的类型, 定义一种类型,专门用来存放这种类型的函数地址;


  • 当函数调用的时候,要把指令指针拨动到函数开始的地方;
  • 每个函数都对应开始的内存地址;
  • c语言函数的名称就是这个函数代码指令的起始地址;
  • 函数编译–>指令在代码段上的起始地址;
  • 调用函数就是让我们的指令指针,跳转到起始地址;
  • 函数的名字->就是地址;可以用指针变量来存放它;
    • c++ 函数指针_虚函数_虚函数表_纯虚函数
  • 怎么样定义一个函数指针呢?变量用来存放这种类型的函数地址
    • c++ 函数指针_虚函数_虚函数表_纯虚函数
  • 每一次这样定义函数指针,有点不方便;
  • 使用typedef 创造了一种类型FUNC_PRT 是一个void (int)函数指针类型
    • c++ 函数指针_虚函数_虚函数表_纯虚函数

虚函数

1: 虚函数: virtual 修饰的成员函数;
2: 虚函数表:
1>每个类都有一个全局唯一的虚函数表,保存了这个类里面每个虚函数对应的函数地址;
2>每个类的实例都会有一个虚函数表的指针,指向这个虚函数表;
3>调用虚函数的时候,首先从实例对象里面的虚函数表的指针,获得虚函数表,然后找到对应函数的地址,然后调用;
4>虚函数的调用性能要比普通的成员函数要差;


  • virtual 定义函数我们把它叫做虚函数
    • c++ 函数指针_虚函数_虚函数表_纯虚函数
  • 虚函数表:
    • 每一个类在代码段上,都有全局唯一的虚函数表;
    • 这个表里面存放的就是每个虚函数所在地址;
    • 编译器在编译的时候就会在代码段上生成一个虚函数表;
      • c++ 函数指针_虚函数_虚函数表_纯虚函数
    • 每一个虚函数在这个表里面就会有一个偏移;
    • 虚函数表是的是它在虚函数表的偏移;
    • 全局唯一的,每一个类只要有虚函数都有一个这样的表;
  • 每个类的实例,如果类里面有虚函数,那么实例里面会有一个指针,指向它的虚函数表;
  • 调用虚函数:
    • 1> 获取实例的虚函数表指针,就获得类的虚函数表;
    • 2> 函数名字,就能够对应到虚函数表里面的偏移;
    • 3> 查虚函数表,找到这个代码地址,准备好参数后,跳转过去;
    • 4> 虚函数调用的性能开销视察表过程,没有普通函数调用那么快,那么高;
    • c++ 函数指针_虚函数_虚函数表_纯虚函数

虚函数表与继承

1: 当子类继承父类的时候,子类的虚函数表,编译器首先会复制父类的虚函数表;
2: 如果子类有自己的新的虚函数,就加到虚函数表的后面;
3: 如果子类重载了父亲的虚函数,那么就会把原来父亲虚函数位置的地址改成现在重载的地址;


  • 虚函数表继承:
    • 1> 拷贝过来父类的虚函数表;
    • 2> 如果有新的虚函数,那么就加到后面;
    • 3> 如果重载了父类的虚函数,那么在虚函数表里面把原来父类的函数更新成你重载的函数
    • c++ 函数指针_虚函数_虚函数表_纯虚函数
    • c++ 函数指针_虚函数_虚函数表_纯虚函数

基类指针指向子类实例

1: 定义一个基类指针 = new 子类实例
2: 基类指针调用普通的成员函数:
1>只会在基类里面搜索;
2> 基类里面有的函数,子类重载了,使用基类的指针来调用这个函数,还是基类的函数;
3: 如果基类里面有个虚函数:
1>会去到子类的虚函数表里面去找对应的偏移;
2>如果子类重载了这个虚函数,那么调用的就是子类的函数;


  • 我们知道子类is-a基类;
  • 基类指针指向子类实例:
    • 1> 使用这个指针来调用普通的的函数,只能访问到基类的方法和成员;
    • 2> 如果使用指针来访问普通的成员函数,访问的是基类的成员函数
      • c++ 函数指针_虚函数_虚函数表_纯虚函数
    • 3> 如果 子类重载父类虚方法,指向的是子类的成员函数
      • address会找到对应的,虚函数表的指针–>man;
      • test_funchoman类里面偏移是1;
      • man表,偏移为1的函数被子类重载了,所以更新成了子类重载后的函数 man::test_func(){}
      • c++ 函数指针_虚函数_虚函数表_纯虚函数

纯虚函数 (抽象接口)

1: 纯虚函数:定义virtual 返回值 函数名字(参数) = 0;
2: 特点:
1>有函数没有实现,故这个类不能实例化;
2>用途:强制子类要重载这个接口,否者不能使用;
3: 多用于抽象必须实现的接口;


  • 纯虚函数:
    • 1> 函数没有实现 = 0;
      • c++ 函数指针_虚函数_虚函数表_纯虚函数
    • 2> 有纯虚函数的类,是叫做抽象类,不能够直接实例化;
      • c++ 函数指针_虚函数_虚函数表_纯虚函数
    • 3> 因为还有一个函数没有实现,所以不能实例化!要实现实例化除非实现接口
      • c++ 函数指针_虚函数_虚函数表_纯虚函数
    • 4> 抽象接口,强制要实例化的子类必须要实现这个方法;

总结

1: 掌握函数指针;
2: 掌握虚函数与虚函数表,以及虚函数调用过程;
3: 掌握虚函数表的继承;
4: 掌握基类的指针指向子类的实例,调用普通函数与虚函数;
5: 掌握纯虚函数的用途;


–>源代码