虚函数简单介绍理解
C++提供的新机制,虚函数允许函数调用与函数体的匹配在运行时才确定,是一种动态绑定机制。
正常来说,通过基类引用或指针所能看到的是一个基类对象,派生类中的成员对于基类引用或指针来说是不可见的,无法通过基类的指针或引用指向派生类。
举例:
class Base_demo { public: void demo() { cout << "这是基类Base" << endl; } }; class Derived_demo :public Base_demo { public: void demo() { cout << "这是派生类Derived" << endl; } }; int main() { Base_demo b1, *b2; Derived_demo d1 ; b2 = &b1; b2->demo();//这是基类Base b2 = &d1; b2->demo();//这是基类Base return 0; }我们想通过基类引用或指针来访问派生类的成员,就得使用本章的主题虚函数——virtual,将基类的Print说明为虚函数形式。这样就可以通过基类引用或指针来访问派生类:
class Base_demo { public: virtual void demo() {//在此使用虚函数 cout << "这是基类Base" << endl; } }; class Derived_demo :public Base_demo { public: void demo() { cout << "这是派生类Derived" << endl; } }; int main() { Base_demo b1, *b2; Derived_demo d1 ; b2 = &b1; b2->demo();//这是基类Base b2 = &d1; b2->demo();//这是派生类Derived return 0; }为了更好体现虚函数在运行时才确定,下面我将使用if进行判断运行;
class Base_demo { public: virtual void Demo() { cout << "这是基类Base" << endl; } }; class Derived_demo :public Base_demo { public: void Demo() { cout << "这是派生类Derived" << endl; } }; class if_demo{ public: void Demo(Base_demo& b1, Derived_demo& d1, Base_demo*& b2) { int condition; cin >> condition; if (1 == condition) b2 = &b1; else b2 = &d1; } }; int main() { Base_demo b1, * b2; Derived_demo d1; if_demo i1; i1.Demo(b1, d1, b2); b2->Demo(); return 0; }如上,Demo的调用依赖于用户输入的condition值,而非在编译阶段就确定好谁调用Demo。
虚函数概念理解
在基类中,用virtual关键字声明的成员函数即是虚函数。并且,虚函数可以在一个或者多个公有派生类中被定义但是要求虚函数的原型必须完全相同。
否则原型不同,如只函数名相同,会被编译器定义为函数重载,从而丢失虚函数特性。仅返回类型不同,其他相同。C++编译器认为这种情况是不允许的。
意义:基类使用虚函数提供一个接口,但派生类可以定义自己的实现版本。调用的解释依赖于它的对象类型,这就实现了“一个接口,多种语义”的概念。
注意虚函数的限制条件:
1.不能将虚函数说明为全局函数。
2.不能将虚函数说明为静态成员函数。
3.不能将虚函数说明为友元函数。
4.虚函数必须是类的非静态成员函数。
class Base { public: // 错误:构造函数不能是virtual virtual Base() { } };原因:
虚函数的调用依赖对象的虚指针(VPTR)和虚函数表(VTABLE),但:
1.VPTR 是在构造函数中初始化的:对象的 VPTR 是由类的构造函数自动设置的(构造函数执行时,才会把 VPTR 指向当前类的 VTABLE);
2.构造函数执行时,对象还没完全创建:构造函数是用来 “创建对象” 的,执行构造函数的那一刻,对象的内存刚分配,VPTR 还未初始化 —— 此时虚函数的调用逻辑(通过 VPTR 找 VTABLE)根本无法工作。
5.构造函数不能定义为虚函数,而析构函数可以定义为虚函数。
class Base_demo { public: virtual void Demo() { cout << "这是基类Base" << endl; } virtual ~Base_demo() { cout << "析构Base" << endl; } }; class Derived_demo :public Base_demo { public: void Demo() { cout << "这是派生类Derived" << endl; } ~Derived_demo() { cout << "析构Derived" << endl; } }; int main() { Base_demo * b2, * d2; b2 = new Base_demo(); d2 = new Derived_demo(); delete b2; delete d2; return 0; }虚函数表和虚指针
在编译时,为每个有虚函数的类建立一张虚函数表VTABLE,表中存放的是每一个虚函数的指针;同时用一个虚指针VPTR指向这张表的入口。
访问某个虚函数时,不是直接找到那个函数的地址,而是通过VPTR间接查到它的地址。
int main(){ Base_demo b1; Base_demo* b2 = &b1; b2->Demo(); Derived_demo d1; Base_demo* b2 = &d1; b2->Demo(); }对于基类Base_demo的对象b1:
b1的内存空间:包含自己的data members(这里我没定义成员变量,所以只有VPTR)+VPTR(虚指针);
b1的 VPTR 指向 Base_demo的 VTABLE(虚函数表),这个表中存的是Base_demo::Demo()的函数地址。
对于子类Derived_demo的对象d1
d1的内存空间:包含Base_demo的data members(继承的) +自己的data members+VPTR;
d1的 VPTR 指向 Derived_demo的 VTABLE,这个表中存的是Derived_demo::Demo()的函数地址。
纯虚函数与抽象类
定义纯虚函数的语法形式: virtual type functionName(parameters)=0;
纯虚函数:基类中的公共接口只需要有说明而不需要有实现,即纯虚函数。纯虚函数刻画了派生类应该遵循的协议,这些协议的具体实现由派生类来决定。
抽象类:拥有纯虚函数的类被称为抽象类。抽象类不能被实例化,只能作为基类被使用。抽象类的派生类需要实现纯虚函数,否则该派生类也是一个抽象类。
接口类:当抽象类的所有函数成员都是纯虚函数时,这个类被称为接口类。
class Shape{ virtual float Perimeter()=0; virtual float Area()=0; };Shape类中的Perimeter与Area就是纯虚函数,而Shape则是抽象类。又可以看到类中成员全是虚函数,所以Shape也是一个接口类。
没什么用的小知识:
特殊规则:注意一点,纯虚函数的重写不受访问限制,即使这里没用写public默认的private子类也可以重写。但通过父类指针调用私有虚函数是不合法的。所以一般我们还是要遵守访问限定域。