c++语言的一个重要特性就是支持多态,多态性可以简单的概括为"一种接口,不同实现",是面向对象的核心,可分为类的多态和函数的多态,其中函数的多态可以理解为非虚函数的重载,类的多态可以通过虚函数实现。前者相对简单,通过参数的类型和参数的个数来实现静态绑定(从最佳匹配到次佳匹配)。后者的具体实现可以参照博文http://blog.csdn.net/caoyan_12727/article/details/51160690,里面有详细的讲解,下面我们来明确下面的四个概念:
(1).对象的静态类型:指针或是引用在声明是所采用的类型,在编译期就能确定;
(2).对象的动态类型:因为可以用基类指针或是引用指向派生类对象,所以指针或引用的动态类型由它所指的实际类型确定,在运行期决定;对象的静态类型不可更改,而动态类型却可以改变;假设我们有如下类型定义:
class B{
};
class C : public B{
};
class D : public B{
};
D* pD = new D(); // pD的静态类型是它声明的类型D*,动态类型也是D*
B* pB = pD;// pB的静态类型是它声明的类型B*,动态类型是pB所指向的对象pD的类型D*
C* pC = new C();
pB = pC;//pB的动态类型是可以更改的,现在它的动态类型是C*
(3).静态绑定:
静态绑定是指在程序编译过程中,把函数(方法或者过程)调用与响应调用所需的代码结合的过程称之为静态绑定。
(4)动态绑定:
动态绑定是指在执行期间(非编译期间)判断所引用(或指向)对象的实际类型,根据实际类型调用其相应的方法,程序运行过程中,把函数(或过程)调用与响应调用所需要的代码相结合的过程称之为动态绑定。
举个简单的例子:
输出结果为:
class B{
public:
void f(){
cout << "non-virtual function of class B"<< endl;
};
virtual void vf(){
cout << "virtual function of class B" << endl;
};
};
class C : public B{
public:
void f(){
cout << "non-virtual function of class C" << endl;
};
virtual void vf(){
cout << "virtual function of class C" << endl;
};
};
class D : public C{
public:
void f(){
cout << "non-virtual function of class D" << endl;
};
virtual void vf(){
cout << "virtual function of class D" << endl;
};
};
int main(){
D* pD = new D();
B* pB = pD;
//非虚函数
pD->f();
pB->f();
//虚函数
pD->vf();
pB->vf();
return 0;
}
输出结果为:
如果我们将类D的函数f注释掉时:
class D : public C{
public:
//void f(){
// cout << "non-virtual function of class D" << endl;
//};
virtual void vf(){
cout << "virtual function of class D" << endl;
};
};
int main(){
D* pD = new D();
B* pB = pD;
//非虚函数
pD->f();
return 0;
}
运行结果:
从上面的结果来看,非虚函数的执行大概遵循下面的原则:
(1)发生的是静态绑定,即根据指针的静态类型就可以确定哪个函数在编译器被绑定,而虚函数执行时,发生动态绑定时,一定要根据指针(引用)的动态类型来确定;
(2)在静态绑定时首先查看当前类(比如class D)有没有f()函数,如果有那么执行类D自己的函数f(),如果没有就会从继承层次上从下至上寻找,直到找到匹配为止(比如在类D中没有定义f(),编译器将类C中对应的方法和f()绑定,所以最终执行类C中的f()函数)。这一切都在编译器时决定;
对于虚函数而言,
pD->vf();
pB->vf();
函数vf()的执行,仅仅从pD和pB静态类型是不能确定哪个vf()对应的方法被绑定执行,用引用(或指针)调用的虚函数在运行时确定,被调用的函数是引用(或指针)所指对象的实际类型所定义的。C++中为了实现虚函数的动态绑定,一般是通过一张虚函数表(virtual table)实现的。这个表中记录了虚函数的地址,解决继承、覆盖的问题,保证动态绑定时能够根据对象的实际类型调用正确的函数。
缺省参数的虚函数绑定: