C++中有两种多态性:编译时的多态性和运行时的多态性。
前者通过函数的重载和运算符的重载来实现。函数重载是根据函数调用时参加运算的对象的不同;运算符重载是根据参加运算的对象的不同。
后者是指在程序执行前,无法根据函数名和参数来确定该调用哪一个函数,必须在程序执行过程中动态的确定。这种多态性是通过继承关系和virtual 函数来实现的。
虚函数定义:
1 |
virtual 返回类型 函数名 参数表 {...} |
virtual 仅用于类定义中,在所有派生类中,参数表和返回类型要与基类中的虚函数一样。如果未加virtual,普通的派生类中的新成员函数覆盖基类的同名成员函数,可称为同名覆盖函数(override),不能实现运行时的多态性。在派生类中重新定义虚函数时,不仅要同名,而且它的参数表和返回类型全部要与基类中的虚函数一样。
含有一个或几个虚函数的类及其派生类时,为该类建立虚函数表(virtual function table)。
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include "stdafx.h" #include <stdio.h> #include <stdlib.h> class A{ public: void function(){printf("Hello World");} }; class B{ public: virtual void function(){printf("Hello World");} }; int _tmain(int argc, _TCHAR* argv[]){ A* p1 = NULL; p1->function(); B* p2 = NULL; //程序会crash //B* p2 = new B;改成这样就可以 p2->function(); system("pause"); return 0; }; |
原因是:不是虚函数的情况,this指针当成第一个参数传入函数(一般是通过ecx寄存器),主要里面没有使用这个this指针,函数运行是没有问题,如果用到了this指针,譬如访问了对象的成员变量,同样会crash。
1 2 3 4 5 |
A* p1 = NULL; 003913DE mov dword ptr [p1],0 p1->function(); 003913E5 mov ecx,dword ptr [p1] 003913E8 call A::function (3910E1h) |
而虚函数的情况,就不一样了,这种情况下,对象的第一个成员是虚函数表指针(指向虚函数表,虚表指针的位置可能不同的编译器有差异,但原理一样)。调用虚函数的时候,首先是通过对象地址(this指针)获得第一个成员(虚表指针)的值,然后再通过虚表指针+偏移获得相应虚函数的位置,再调用虚函数,其实这是C++多态的原理,因此这个地方因为对象指针为NULL,因此去空地址取虚表指针,自然crash了。
1 2 3 4 5 6 7 8 9 10 11 |
B* p2 = NULL; 013C13ED mov dword ptr [p2],0 p2->function(); 013C13F4 mov eax,dword ptr [p2] //在空地址取虚表指针 013C13F7 mov edx,dword ptr [eax]//此处crash 数据寄存器 013C13F9 mov esi,esp 013C13FB mov ecx,dword ptr [p2] 013C13FE mov eax,dword ptr [edx] 013C1400 call eax 013C1402 cmp esi,esp 013C1404 call @ILT+320(__RTC_CheckEsp) (13C1145h) |
绑定:计算机程序自身彼此关联的过程,如将一个标识符名和一个存储地址联系在一起,将一条消息和一个对象的操作相结合的过程以及将函数调用与函数体代码链接。
先前没有讲到多态时的函数调用都是采用静态绑定,包括编译时的多态—函数和运算符重载。
动态绑定
如果使用对象名和点成员选择运算符引用特定的一个对象来调用虚函数,则被调用的虚函数是在编译时确定的(静态绑定),调用的虚函数也就是为该特定对象的类重新定义的虚函数,并无动态或者运行时的多态性的体现。如果使用基类指针或引用指向该派生类对象,并使用该指针调用虚函数(用"->"),则程序运行时动态地选择该派生类的虚函数,实现动态绑定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
#include "stdafx.h" #include "iostream" #include <string> using namespace std; class Student{ string coursename; int classhour; int credit; public: Student(){coursename="#";classhour=0;credit=0;} virtual void Caculate(){credit=classhour/16;} void SetCourse(string str,int hour){ coursename=str; classhour=hour; } int GetHour(){return classhour;} void SetCredit(int cred){credit=cred;} void Print(){cout<<coursename<<'\t'<<classhour<<"学时"<<'\t'<<credit<<"学分"<<endl;} }; class GradeStudent:public Student{ public: GradeStudent(){}; void Caculate(){SetCredit(GetHour()/20);} }; int _tmain(int argc, _TCHAR* argv[]){ Student s,*ps; GradeStudent g; s.SetCourse("物理",80); s.Caculate(); s.Print(); g.SetCourse("物理",80); g.Caculate(); g.Print(); s.SetCourse("数学",160); ps=&s; ps->Caculate(); ps->Print(); g.SetCourse("数学",160); ps=&g; ps->Caculate(); ps->Print(); return 0; } |