C++面向对象的编程(二)
先看下面的一段代码:
1 #include "stdafx.h" 2 #include <iostream> 3 using namespace std; 4 struct A 5 { 6 void func1() 7 { 8 cout<<"A::func1()"<<endl; 9 } 10 }; 11 struct B: public A 12 { 13 void func1() 14 { 15 cout<<"B::func1()"<<endl; 16 } 17 }; 18 struct C: public A 19 { 20 void func1() 21 { 22 cout<<"C::func1()"<<endl; 23 } 24 }; 25 26 27 void main() 28 { 29 B obj_b; 30 obj_b.func1();//B::func1 31 A obj_a; 32 obj_a.func1();//A::func1 33 }
这是当基类和派生类中发生重名函数时候的调用结果。可以总结为一句话:1,直接用基类指针引用基类对象 2,直接用派生类指针引用派生类对象
修改代码如下:
(1)将基类中的func1修改为虚函数
(2)
1 void main() 2 { 3 4 B obj_b; 5 obj_b.func1();//B::func1 6 A obj_a; 7 obj_a.func1();//A::func1 8 9 B *pb = new B; 10 A *pa =pb; 11 pa->func1();//B::func1 这也是我们最常用的动态绑定,此时pa的值和pb的值是相等的,也就是说,两者指向同一内存区域 12 13 //将一个派生类对象赋给基类对象 14 obj_a = obj_b; 15 obj_a.func1();//A::func1 也就是说,只有通过指针或者引用的形式才会发生动态绑定 16 17 //将一个基类对象赋值给派生类对象,比较危险 18 C*obj_c = static_cast<C*>(&obj_a); 19 obj_c->func1();//A::func1 20 }
static_cast是用来进行类型转换的,是将基类类型的指针或引用转换为派生类对象的指针或引用。即保证由底层到高层转换的安全性。我们知道,发生动态绑定的关键是将派生类的指针或引用转化为基类的指针或者是引用。这样,C++就能安全的通过基类类型的指针或引用来调用派生类的虚函数。
而上面的代码恰恰相反,将基类的指针强制转化为派生类的指针,调用的结果为指针指向的对象中的函数。
我们知道,虚函数的定义为:
虚函数的作用是实现动态联编,也就是在程序的运行阶段动态地选择合适的成员函数,在定义了虚函数后,可以在基类的派生类中对虚函数重新定义,在派生类中重新定义的函数应与虚函数具有相同的形参个数和形参类型。以实现统一的接口,不同定义过程。如果在派生类中没有对虚函数重新定义,则它继承其基类的虚函数。
定义虚函数的限制:(1)非类的成员函数不能定义为虚函数,类的成员函数中静态成员函数和构造函数也不能定义为虚函数,但可以将析构函数定义为虚函数。实际上,优秀的程序员常常把基类的析构函数定义为虚函数。因为,将基类的析构函数定义为虚函数后,当利用delete删除一个指向派生类定义的对象指针时,系统会调用相应的类的析构函数。而不将析构函数定义为虚函数时,只调用基类的析构函数。
(2)只需要在声明函数的类体中使用关键字“virtual”将函数声明为虚函数,而定义函数时不需要使用关键字“virtual”。
(3)当将基类中的某一成员函数声明为虚函数后,派生类中的同名函数自动成为虚函数。
关于虚函数的实现机制,百度百科和维基百科里面解释的比较详细。