拓冰建站拓冰建站
首页 / 资讯中心 / 正文

C++中的虚函数机制

文章目录虚函数机制多态虚函数虚函数表指针虚函数表通过虚函数表实现多态类模板的成员函数可以是虚函数模板在类上成员函数模板不能是虚函数模板在函数上虚函数机制多态要了解虚函数机制首先要了解多态。多态是指同一方法调用在不同对象上表现出不同的行为。它是面向对象编程的核心特性之一。c中的多态性包含静态多态、动态多态。静态多态在编译阶段进行决议常见的实现方式有重载、模板动态多态在运行阶段进行决议常见的实现方式是重写。虚函数虚函数是被virtual关键字所修饰的类的成员函数通过虚函数可以实现重写以此来完成动态多态。通过函数重写来实现多态// 基类 class Base { public: Base() {} virtual void test(int x) {} // 基类虚函数 }; // 派生类 class Derive : public Base { public: Derive() {} void f1(int x) {} // 子类中重写基类的虚函数子类函数自动继承虚函数特性 }; // 调用 Base * base_class new Derive(); // 基类指针持有子类对象 base_class-test(10); // 调用 Derive::test(int)会根据实际类型调用对应函数实现多态当某一个类的成员函数被定义为虚函数则由该类派生出来的所有子类中与该函数同名同参同返回值类型或返回类型协变的函数也会保持虚函数的特征。静态成员函数不能是虚函数因为静态成员函数是所有该类类型对象所共有的不会有多态特性。内联函数不能是虚函数因为内联函数在每个实例中都有一个拷贝无映射关系不会有多态特性。构造函数不能是虚函数因为在调用构造函数时对象还没有完成实例化。析构函数一般会定义为虚函数因为其子类析构时需要通过多态调用自身的析构函数。虚函数表指针虚函数表指针vfptrvirtual fun point table的生成条件——只要类中有虚函数那么该类的实例化对象的内存布局中就会创建一个隐含的成员即虚函数表指针且该类的派生类对象也会创建自身的虚函数表指针。无论虚函数有多少个虚函数表指针只有一个。虚函数表虚函数表指针指向的就是虚函数表virtual table又叫虚表虚表存放在内存中的只读数据段。虚函数表的构造发生在代码的编译阶段。多态发生时即调用时选择哪个表由对象的运行时类型决定。虚函数表中包含以下内容不同虚函数表的偏移量只在多重继承时使用RTTI运行时类型信息用来确定对象的运行时类型类中所有虚函数的函数地址。用来确定需要调用的函数入口子类在生成自身的虚函数表的时候会用自身重写的函数替换掉基类中的虚函数。所有同类对象共享一张虚表。通过虚函数表实现多态示例// 基类 class Base { public: Base() {} virtual void f1(int x) {} // 基类虚函数 virtual void f2(int x) {} // 基类虚函数 }; // 派生类 class Derive : public Base { public: Derive() {} void f1(int x) {} // 子类中重写基类的虚函数子类函数自动继承虚函数特性 }; // 调用 Base * base_class new Derive(); // 基类指针持有子类对象 base_class-f1(10); // 调用 Derive::test(int)会根据实际类型调用对应函数实现多态 base_class-f2(10); // 调用 Base::test(int)在编译阶段编译器看到virtual关键字给类生成一张虚函数表基类 Base 生成虚函数表在表中填入基类的虚函数地址Base::f1、Base::f2子类 Derive 生成自己的虚函数表包含自身的虚函数以及基类的虚函数使用自身重写的函数Derive::f1替换Base::f1而f2未被重写沿用父类Base::f2给类增加隐藏成员vptr虚函数表指针对象构造的时候构造函数把vptr虚函数表指针赋值为本类的虚函数表地址。在运行时阶段实例对象调用函数f1从对象Base * base_class的内存起始位置取出vptr虚函数表指针通过vptr虚函数表指针找到虚函数表根据函数f1查找虚函数表中获取需要调用的函数指针跳转调用这个函数指针指向的函数。通过上述的流程完成运行时多态。类模板的成员函数可以是虚函数模板在类上模板是在编译期阶段生成类所以实例化出来的具体类就和普通类一样可以有虚函数、参与多态、产生虚函数表template classT class Base { public: virtual void f1() {} //虚函数 virtual void f2() 0 {} //纯虚函数 };BaseT本身不是一个类是类模板实例化后的Baseint才是真正的类才有虚函数表。Baseint和Basestring是两个互不相关的类型两张独立虚函数表。成员函数模板不能是虚函数模板在函数上虚函数表需要在编译期就确定而函数模板的具体类型是在运行时产生了实例之后才确定的函数模板可以被实例化成无数种每实例化一次就生成一份全新函数所以编译器无法预知会实例化多少版本虚函数表无法预先确定。虚表是固定大小数组模板实例化数量是无限可能二者矛盾。虚表编译期就要定好全部函数指针函数模板实例化可以发生在任意地方编译器预判不了全部实例。templatetypename T // 无论类是不是模板类 class Base { public: templatetypename U virtual void test(U u) {} // 编译报错函数模板不能使用 virtual };正确的类模板带成员函数模板的实现templatetypename T // 无论类是不是模板类 class Base { public: templatetypename U void test(U u) {} // 正确函数模板未使用 virtual }; // 调用 Baseint b_class; // 类定义时已经可以确定类模板的具体类型了虚表是属于类的 b_class.teststring(haha); // 实例对象无法通过实例来确定函数模板类型虚表不是属于对象的
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门