静态多态:编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推 断出要调用那个函数,如果有对应的函数就调用该函数,否则出现编译错误。
动态绑定:在程序执行期间(非编译期)判断所引用对象的实际类型,根据其实际类型调用相应的方 法。 使用virtual关键字修饰类的成员函数时,指明该函数为虚函数,派生类需要重新实现,编译器将实 现动态绑定。
也就是说: 通过基类的指针或引用调用虚函数时,调用基类还是派生类的虚函数,要在运行时根据指针(引用)指向(引用)的类型确定,当调用非虚函数时,无论指向的是哪种类型,一定调用的是基类的函数。
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; class Base { public: /*virtual*/ void Test() { cout << "Base.Test()" << endl; } private: int _b; }; class Derived :public Base { public: /*virtual*/ void Test() { cout << "Derived.Test()" << endl; } private: int _d; }; void test(Base& b) { b.Test(); } int main() { Base b; Derived d; test(b); test(d); return 0; }可以看出,由于Test不是虚函数,所以两次输出的结果都是Base.Test()。 当把Test改为虚函数后:
在进行重写时,在派生类中重写的函数必须满足函数原形相同(包括函数返回值、函数名、函数参数列表),协变除外。 【协变: 基类中的虚函数返回值类型为基类类型的指针或引用,并且派生类中重写的虚函数返回值类型为派生类类型的指针或引用。】 接下来举例验证协变:
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; class Base { public: virtual void Test() { cout << "Base.Test()" << endl; } virtual Base& Test1() { cout << "Base.Test1()" << endl; return *this; } private: int _b; }; class Derived :public Base { public: virtual void Test() { cout << "Derived.Test()" << endl; } virtual Derived& Test1() { cout << "Derived.Test1()" << endl; return *this; } private: int _d; }; void test(Base& b) { b.Test(); b.Test1(); } int main() { Base b; Derived d; test(b); test(d); return 0; }那么,有虚表函数的类的大小是多少呢?
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; class Base { public: virtual void Test() { cout << "Base.Test()" << endl; } virtual Base& Test1() { cout << "Base.Test1()" << endl; return *this; } private: int _b; }; class Derived :public Base { public: virtual void Test() { cout << "Derived.Test()" << endl; } virtual Derived& Test1() { cout << "Derived.Test1()" << endl; return *this; } private: int _d; }; void test(Base& b) { b.Test(); b.Test1(); } int main() { Base b; Derived d; test(b); test(d); cout << sizeof(Base) << endl; return 0; }我们知道,类的大小是类中成员变量的大小总和,Base类中,成员变量只有_b一个,若忽视虚函数,则该类的大小应为4个字节,可是这里求出来的是8个字节,那另外4个字节是什么?
只要类中有虚函数,就会有虚表指针的存在,虚表指针指向虚表。 也因此,上例中类的大小之所有为8,除了类中本身的成员变量所占的4个字节外,另外的四个字节为虚表指针。 对于有虚函数的类,编译器都会维护一张虚表,对象的前四个字节就是指向虚表的指针。 由上例也可看出,虚函数的调用顺序即声明顺序。在派生类中,前面是基类的虚函数,后面是派生类的虚函数。
虚表地址是在构造对象的时候填充进去的。具体看下例:
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; class Base { public: Base() { cout << "Base.Base()"<<endl; } virtual void Test() { cout << "Base.Test()" << endl; } virtual Base& Test1() { cout << "Base.Test1()" << endl; return *this; } public: int _b; }; class Derived :public Base { public: Derived() { cout << "Derived.Derived()" << endl; } virtual void Test() { cout << "Derived.Test()" << endl; } virtual Derived& Test1() { cout << "Derived.Test1()" << endl; return *this; } private: int _d; }; int main() { Derived d; return 0; }在创建派生类类对象时,首先调用基类的构造函数,再调用派生类的调用函数,由上图可见,在调用基类构造函数后填充一个虚表指针,调用派生类构造函数后,填充的虚表指针发生变化。
typedef void(*pfun)(); class Base { public: virtual void Test() { cout << "Base.Test()" << endl; } virtual void Test1() { cout << "Base.Test1()" << endl; } public: int _b; }; class Derived :public Base { public: virtual void Test1() { cout << "Derived.Test1()" << endl; } virtual void Test2() { cout << "Derived.Test2()" << endl; } private: int _d; }; void Print()//打印 { Derived d; Base& b = d; pfun* fun = (pfun*)(*(int *)&b); while (*fun) { (*fun)(); ++fun; } } int main() { Print(); return 0; }Base.Test() //基类中本就存Test,派生类中未重写,因此调用基类中Test函数 Derived.Test1() //基类中存在Test1,但在派生类中进行了重写,因此调用派生类中的Test() Derived.Test2() //Test2是派生类中新增加的,因此直接调用派生类中的Test2()
即:基类中的虚函数,若未在派生类中重写,则调用基类中的,重写了则调用派生类中的该函数(替换相同偏移量处的函数)。派生类中新增加的虚函数则放在后面(声明次序)。
上例所说为单继承的情况,接下来我们讨论多继承:
typedef void(*pfun)(); class Base1 { public: virtual void Test1() { cout << "Base1.Test1()" << endl; } virtual void Test2() { cout << "Base1.Test2()" << endl; } public: int _b1; }; class Base2 { public: virtual void Test3() { cout << "Base2.Test3()" << endl; } virtual void Test4() { cout << "Base2.Test4()" << endl; } private: int _b2; }; class Derived :public Base1, public Base2 { public: virtual void Test1() { cout << "Derived.Test1()" << endl; } virtual void Test3() { cout << "Derived.Test3()" << endl; } virtual void Test5() { cout << "Derived.Test5()" << endl; } public: int _d; }; void Print()//打印 { Derived d; Base1& b = d; pfun* fun = (pfun*)(*(int *)&b); while (*fun) { (*fun)(); ++fun; } cout << endl; cout << endl; Base2& b1 = d; fun = (pfun*)(*(int *)&b1); while (*fun) { (*fun)(); ++fun; } } int main() { Print(); return 0; }Derived.Test1() //基类中的Test1在派生类中被重写, 因此调用派生类中的Test2函数 Base1.Test2() //基类1中本就存Test2,派生类中未重写,因此调用基类中Test2函数 Derived.Test5() //派生类中所特有的Test5函数
Derived.Test3() //基类中的Test3在派生类中被重写, 因此调用派生类中的Test3函数 Base2.Test4() //基类2中本就存Test4,派生类中未重写,因此调用基类中Test4函数
Derived.Test1() //Base类中有Test1函数,先在C1类中重写,后又在派生类中重写,调用派生类中的Test1函数 Derived.Test2() //C1类中的函数,在派生类中重写 Derived.Test4() //派生类中所特有的Test4函数
Derived.Test1() //Base类中有Test1函数,先在C2类中重写,后又在派生类中重写,调用派生类中的Test1函数 C2.Test3() //C2类中的函数,未在派生类中重写
但是在这种继承方式下,在给_b赋值时,若是没有进行作用域限定,会产生二义性,因此,引入菱形虚拟继承