前言:
要想真的理解C++中的虚函数调用过程,首先自然而然的要了解虚函数调用过程的表面现象,那么要了解它的表面现象就自然而然的不能不理解静态类型和动态类型,静态绑定和动态绑定的概念!
其次才能更加深层次的探讨虚函数的调用原理,虚表!!!
自出现继承后,就可以有基类指针或者引用指向派生类对象了,此时指针或者引用的类型就不在是简简单单的了!!
因为这些概念设计到对函数的调用,到底是如何调用的基类的还是派生类的,如何调用函数也牵扯到虚函数如何调用的过程!
理解清楚就很方便理解多态了,多态的最主要的原理还是虚函数表的概念!但是这个概念并不打算在此文章讲,不想篇幅太长,所以留到下一篇章再和大家分享我对虚函数表的理解!
文章目录
- 静态类型,动态类型
- 静态绑定,动态绑定
静态类型,动态类型
学习C++多态,肯定要理解这几个概念,如果不理解的话,很难理解C++多态的发生过程!我之前也被这几个概念搞得头昏昏的;
- 静态类型:变量声明时候的类型,在编译阶段就可以确定了;
- 动态类型:指针或者引用所代表的内存中的对象的类型,在运行阶段才可以确定;
动态类型之所以叫动态类型,因为它是动态确定的,也就是说它的类型是不确定的,只有在运行时候(运行时候就是动态的过程)才会直到类型是什么,简单的说:动态类型是可以发生变化的;并且动态类型的概念是针对指针和引用这两个东西而引出来的。
举个例子:该例子没有实际含义,只是为了理解动态类型和静态类型
class Base
{
};
class Derived:public Base
{
};
class SubDerived:public Derived
{
};
int main()
{
Base* pb = new Derived; //pb的静态类型为Base*,我们不用运行就知道了,编译时候就能够确定下来;
//pb的动态类型为 Derived*,这个只有在运行阶段才可以确定下来,
//即程序运行到这句代码,才可以直到pb的动态类型为 Derived*
Derived der; //派生类对象,派生类的对象der的静态类型为derived,动态类型:无;
Base& pc = der; //pc的静态类型为 base,动态类型为 derived;
pb = new SubDerived; //pb的静态类型为 Base,动态类型为:SubDerived
//重要的理解:pb动态类型从原来的Derived,变到现在的SubDerived
//也说明了程序运行到这里,才可以确定pb的动态类型是什么,并且它是可以变化的
Base* pd = & der; //pd静态类型为 Base* ,动态类型为 Derived;
SubDerived subDer;
pd = &subDer; //pd静态类型为:Base*,动态类型从Derived变成了SubDerived
Base& pe = der; //pd静态类型为Base动态类型为 Derived;
//___________________________________________________________________________
//上面都是动态类型和静态类型不一致的情况,那么什么时候动态类型和静态类型会是一致的呢?
//举几个例子如下情况:
Base* p1 = new Base; //p1静态类型为Base*,动态类型为:Base*,它们是一致的;
Base base1; //base1的静态类型为:Base,base1对象没有动态类型的概念,因为他不是指针或者引用类型
p1 = &base;//p1静态类型为Base*,动态类型为:Base*,它们是一致的;
Base base2; //base2的静态类型为:Base;没有动态类型的概念
p1 = &base2; //p1的静态类型为Base*,动态类型为:Base*,没有发生改变,虽然它们指向的是不同的对象
return 0;
}
静态绑定,动态绑定
理解了什么是静态类型和动态类型之后,我们需要理解什么是静态绑定和动态绑定的概念;
注意哦,它们不是同一个概念哦,一个是类型级别的,一个是绑定级别的;
- 静态绑定:有人也叫为早绑定,早绑定什么呢?对象所调用的函数的地址,这个阶段是程序编译阶段就确定的了;
- 动态绑定:有人也叫晚绑定,也就是编译阶段时候不能确定对象调用函数的地址,需要程序运行到调用函数的阶段才可以确定;
其中我们要理解:绑定这个词,它是相对于函数来说的,把对象(包括指针或者引用)和函数绑定在一起;
举个例子理解静态绑定和动态绑定:
#include<iostream>
using namespace std;
class Base
{
public:
void fun()
{
cout <<" Base::fun() "<< endl;
}
};
class Derived :public Base
{
public:
void fun()
{
cout <<" Derived::fun() "<< endl;
}
};
class SubDerived :public Derived
{
public:
void fun()
{
cout << "SubDerived::fun()" << endl;
}
};
int main()
{
Base* b1 = new Base;
b1->fun();
Derived* d1 = new Derived;
d1->fun();
SubDerived* s1 = new SubDerived;
s1->fun();
return 0;
}
观察结果发现:这是毋庸置疑的答案:
b1 的静态类型为:Base*,动态类型为:Base*; b1->fun( )这个就是静态绑定,虽然我们的b1的动态类型和静态类型一致,可是这个动态类型却不影响它的静态绑定的事实;
d1 的静态类型为:Derived*,动态类型为:Derived* ;d1->fun( )这个就是静态绑定,虽然我们的b1的动态类型和静态类型一致,可是这个动态类型却不影响它的静态绑定的事实;
s1的静态类型为:SubDerived*,动态类型为:SubDerived*; s1->fun( )这个就是静态绑定,虽然我们的b1的动态类型和静态类型一致,可是这个动态类型却不影响它的静态绑定的事实;
我们继续测试一下:
修改主函数的代码为下面的:
父类指针指向子类对象
int main()
{
Base* b1 = new Derived;
b1->fun();
Derived* d1 = new SubDerived;
d1->fun();
return 0;
}
测试结果如下:
结果也是可以理解的:虽然说是父类指针指向了子类对象,可是也是调用子类对象中父类的成员;
b1 的静态类型为:Base*,动态类型为:Derived*; b1->fun( )这个就是静态绑定,虽然我们的b1的动态类型和静态类型不一致,可是这个动态类型却不影响它的静态绑定的事实;
d1 的静态类型为:Derived*,动态类型为:SubDerived*; d1->fun( )这个就是静态绑定,虽然我们的d1的动态类型和静态类型不一致,可是这个动态类型却不影响它的静态绑定的事实;
从汇编的角度我们也可以看出来,虽然是父类指针指向子类对象,可以调用的还是父类的成员函数!
我们继续测试:此时我们要注意:我们给类加了虚函数
#include<iostream>
using namespace std;
class Base
{
public:
void fun()
{
cout <<"Base::fun() "<< endl;
}
virtual void virFun()
{
cout << "Base::virfun() " << endl;
}
};
class Derived :public Base
{
public:
void fun()
{
cout <<"Derived::fun() "<< endl;
}
virtual void virFun()
{
cout << "Derived::virfun() " << endl;
}
};
class SubDerived :public Derived
{
public:
void fun()
{
cout << "SubDerived::fun()" << endl;
}
virtual void virFun()
{
cout << "SubDerived::virfun() " << endl;
}
};
void Test3()
{
Base* b1 = new Derived;
b1->virFun();
Base* b2 = new SubDerived;
b2->virFun();
Base* b3 = b2;
b3->virFun();
Derived* d1 = new SubDerived;
d1->virFun();
}
int main()
{
Test3();
return 0;
}
测试结果如下:
此时:有了虚函数,并且子类重写父类的虚函数,且父类的指针指向子类对象,那么就会发生变化,此时就发生了多态的行为:用父类的指针可以调用子类中的成员了
b1 的静态类型为:Base*,动态类型为:Derived*; b1->fun( )这个就是动态绑定,由于父类Base中有虚函数fun(),子类:Derived重写了父类的虚函数呋喃(),执行b1->fun( )是根据动态类型来确定执行哪个函数的,由于b1动态类型为Derived所以执行的版本就是Derirved;
b1的静态类型为:Base* ,动态类型为:SubDerived*; b1->fun( )这个就是动态绑定,由于基类Base中有虚函数fun( ),派生类:SubDerived重写了基类的虚函数fun( ),执行b1->fun( )是根据动态类型来确定执行哪个函数的,由于b2动态类型为SubDerived* 所以执行的版本就是SubDerirved;
b3的分析也是和上面一样;
d1的分析也是和上面一样;
从汇编也看出来,有了虚函数,调用的方式也不一样了!!!!!!
***
❓动态类型静态类型和静态绑定动态绑定之间有什么必然的联系吗?
其实没有必然的联系,但是静态类型的对象一定是调用静态绑定的函数的,对于对象来说,他是没有动态类型的概念之说,我们平时所看到的,比如一个普通类,直接创建对象调用的函数方式就是静态绑定,此时对象的类型为静态类型,绑定方式也为静态绑定;
但是一旦联系到了继承:我们就复杂了一点,因为继承是可以用父类的指针指向子类的对象,父类的指针就会多出一个动态类型的概念,也就是说程序运行时候才直到父类指针的具体类型是什么,在没有开始运行时候,它的类型就是静态类型可以确定,如果父类指针指向的也是父类对象,那么动态类型也是父类对象,如果父类指针指向子类对象,那么动态类型就是子类对象类型;尽管父类的指针指向子类对象时候,我们确切的直到它的动态类型为子类的对象指针,但是这个父类的指针,也只能访问子类对象中的父类的成员,并不能够因为指向的是子类对象,就能够访问子类的成员。
此时,就算是指针是动态类型说,发生的绑定也是静态绑定,即调用函数时候,也是在编译期间就确定了;假如在继承的基础上联系上了虚函数,也就是说,父类有虚函数,子类重写虚函数,父类指针指向子类对象时候,此时我们知道父类指针静态类型为父类类型,动态类型为子类类型,由于调用虚函数,此时就会发生动态绑定,也就是动态类型就会发挥作用了,用动态得到的类型去绑定虚函数!!!!
总的一句话说:只有虚函数使用的绑定才是动态绑定。
但不代表说虚函数的绑定都是动态绑定,前提还得是指针或引用类型绑定虚函数才可以;
其他的函数绑定方式都是静态绑定!不管你的指针类型引用类型或对象来说是静态类型还是动态类型,只要它们不联系上虚函数都是静态绑定!
比如下面的b2是对象,虽然b2绑定的virFun()是虚函数,但是这确实静态绑定,因为对于对象来说,它是没有动态类型说法;自然而然的不会有运行时刻才会确定绑定的类型,自然而然的也是说,编译阶段就可以知道了,所以说就是静态绑定;
而我们的b1的静态类型为Base*,动态类型也是Base*,此时由于b1是指针类型,并且调用的是虚函数,那么一定是动态绑定,也就是程序只有运行到这句函数调用才知道绑定的对象是动态类型 Base*,而不是静态类型Base*(虽然它们静态和动态类型一致)