C++中静态绑定与动态绑定
多态:多态是指同一实体同时具有多种形式,即一个名字可以具有多种语义,函数重载、类模板、函数模板等都属于多态性。通俗来说就是借口的多种不同实现方式。
1.对象的静态类型:对象在声明时采用的类型,是在编译期确定的。
2.对象的动态类型:目前所指对象的类型,是在运行期决定的。对象的动态类型可以更改,但是静态类型无法更改。
对象静态类型和动态类型:
class A{
};
class B:public A{
};
class C:public A{
};
C* pc=new C(); //pc的静态类型是它声明类型C*,动态类型也是C*
A* pa=pc; //pa的静态类型是它声明的类型A*,动态类型是pa所指向的对象pc的类型C*
B* pb=new B();
pa=pb; //pb的动态类型是可以更改的,现在它的动态类型是B*
函数绑定:就是函数的入口地址同函数调用相联系的过程。分为静态绑定和动态绑定。
区别:静态绑定在程序执行前完成,由编译系统或操作系统装入程序计算函数的入口地址;而动态绑定则是执行过程中完成,由程序自身计算函数的入口地址。
C++中,非虚函数都是静态绑定,而虚函数却是动态绑定。
重点:构造函数和析构函数中调用虚函数,虚函数是静态绑定的,本类只会调用类本身的虚函数。
静态绑定和动态绑定例:
静态绑定是编译期间就可以确定的类型
Base *B=new Base(); //B的类型只能是Base
动态绑定则只能运行时确定
Derived是Base的派生类
Base *B=new Derived(); //B的静态类型是Base 动态类型却是Derived
#include<iostream>
using namespace std;
class Base{
public:
void fun(){
cout<<"Base"<<endl;
}
};
class Derived:public Base{
public:
};
int main(){
Derived x;
Base *pb=&x;
pb->fun();
Derived *pd=&x;
pd->fun();
return 0;
}
如果Derived中没有定义fun()函数,则两次调用的行为肯定会是一样的,为:
Base
Base
但是调用过程却不相同,
pb->fun(); //pb为指向B类型的指针,直接调用B中的fun()函数
pd->fun(); //pd为指向D类型的指针,首先在Derived中查找fun()函数声明,没有找到,然后到Base中去找,找到fun(),则停止查找。
但如果Derived中有自己定义的fun()函数,将会执行Derived的fun()函数。
pb->fun(); //调用Base::fun();
pd->fun();//调用Derived::fun();
原因为:非虚函数都是静态绑定,也就是说,由于pb被声明为指向B类型的指针,那么通过pb调用的非虚函数用于是Base所定义的版本,即使pb指向一个类型为"Base的派生类Derived"的对象。
但是,虚函数却是动态绑定,不管是通过什么类型的指针调用的这个虚函数,都会根据指针实际指向的对象类型来决定虚函数的调用,而与指针类型无关,如果fun()函数为虚函数,那么不管是通过pd还是pb调用fun()函数,都会调用Derived::fun(),因为pd与pb真正指向的都是同一个类型Derived的对象:
#include <iostream>
using namespace std;
class Base{
public:
virtual void fun(){
cout<<"Base"<<endl;
}
};
class Derived:public Base{
public:
void fun(){
cout<<"Derived"<<endl;
}
};
int main(){
Derived x;
Base *pb=&x;
pb->fun();
Derived *pd=&x;
pd->fun();
return 0;
}
将fun函数声明为虚函数,不管是通过pb还是pd调用fun()函数,都会调用Derived::fun(),因为pb和pd真正指向的都是同一类型Derived的对象,程序输出为:
Derived
Derived
因此C++中,绝对不要重新定义继承而来非虚函数,如果这样,函数的调用决定因素不在对象本事,而与调用函数的指针类型有关。
如果一个对象Derived可能表现出Base或Derived的行为,决定因素不在x对象本身,而是在于指向x的指针类型,同时,不要重新定义一个继承而来的virtual函数的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数却是动态绑定。
虚函数:
虚函数:也是类的一种成员函数,并且不能是静态成员函数。
虚函数的作用是实现动态绑定,也就是在程序的运行阶段动态的选择合适的成员函数。从而实现多态性
设置虚函数的一写注意事项:
1、只有类的成员函数才能声明为虚函数。因为虚函数只适用于有继承关系的类对象,所以普通函数不能声明为虚函数。
2、静态成员不能是虚函数,因为静态成员不受限于某个对象。
3、内联函数不能是虚函数,因为内联函数不能在运行中欧诺个动态的确定其位置。即使虚函数在类的内部定义,编译时仍将其看做是非内联的。
4、构造函数不能是虚函数,因为构造时对象还是一片未定型的空间。只有在构造完成后,对象才能成为一个类的名副其实的实例。
5、析构函数可以是虚函数,而且通常声明为虚函数。目的在于:使用delete删除一个对象时,确保析构函数能够被正确运行。因为设置虚析构函数后,可以利用动态绑定方式选择析构函数。
class A
{
public:
A()
{
cout<<"A()"<<endl;
}
virtual ~A()
{
cout<<"~A()"<<endl;
}
};
class B:public A
{
private :
char *_buf;
public:
B(int i)
{
_buf = new char[i];
cout<<"B()"<<endl;
}
virtual ~B()
{
delete[] _buf;
cout<<"~B()"<<endl;
}
};
void fun(A* pa)
{
delete pa;
}
int main()
{
A *pa = new B(10);
fun(pa);
system("pause");
return 0;
}
结果为:
A();
B();
~B();
~A();
如果~A没有定义为虚函数,则结果为:
A();
B();
~A();
纯虚函数:无函数体的一种特殊的虚函数。
其声明的格式为:virtual 类型 函数名(参数表)= 0;
抽象类:至少有一个纯虚函数的类。
作用:将有关类组织在一个继承层次结构中,由它提供一个公共的根,相关的子类都是从这个根派生出来的。
抽象类的使用规定:
1、抽象类只能用作其他类的基类,不能建立抽象类对象。
2、抽象类不能用作参数类型、函数返回类型或显示转换类型
3、可以定义抽象类的指针和引用,此指针可以指向它的派生类,从而实现多态。
#include<iostream>
using namespace std;
class A //抽象类
{
public:
A()
{
cout<<"A()"<<endl;
}
virtual ~A()
{
cout<<"~A()"<<endl;
}
virtual void fun()=0;
};
class B: public A
{
public:
B()
{
cout<<"B()"<<endl;
}
virtual ~B()
{
cout<<"~B()"<<endl;
}
void fun()
{
cout<<"B::fun()"<<endl;
}
};
class C: public A
{
public:
C()
{
cout<<"C()"<<endl;
}
virtual ~C()
{
cout<<"~C()"<<endl;
}
void fun()
{
cout<<"C::fun()"<<endl;
}
};
int main()
{
A *arr[2];
B b;
C c;
arr[0] = &b;
arr[0]->fun();
arr[1] = &c;
arr[1]->fun();
system("pause");
return 0;
}
特别注意:
当缺省参数和虚函数一起出现的时候情况有点复杂,极容易出错。虚函数是动态绑定,但是为了执行的效率,缺省参数是静态绑定的。
class A{
virtual void fun(int i=1){};
};
class B:public A{
virtual void fun(int i=2){};
};
B* pb=new B();
A* pa=pb;
pb->fun();
pa->fun();
pb->fun()和pa->fun()调用都是函数B::fun(),但是缺省参数为多少?
缺省参数是静态绑定的,pb->fun(),pb的静态类型是B*,所以它的缺省参数应该是2;pa->fun()的缺省参数应该是1.
所以要记住:绝对不要重新定义一个继承而来的virtual函数的缺省参数值,因为缺省参数值都是静态绑定(为了执行效率),而virtual函数却是动态绑定。
二、RTTI
RTTI机制的来源:在C++中存在虚函数,也就存在多态性,对于多态的对象,在程序编译时可能会出现无法确认对象的类型的情况,当类中有虚函数时,基类的指针可以指向任何派生类的对象,这时可能不知道基类指针到底指向那个派生类的对象,而类型的确定要子啊运行时类型标志做出,为了获得一个对象的类型可以使用typeid函数;
1.两个操作符
(1)typeid操作符,返回指针和引用所指的实际类型
(2)dynamic_cast操作符,用来执行运行是类型的识别和装换,dynamic_cast用于将一个父类对象的指针转换为子类对象的指针或者引用(动态转换);
2.typeid函数
该函数的作用主要是让用户知道当前的变量时什么类型
#include<iostream>
#include<typeinfo.h>
using namespace std;
class A{
public:
void fun(){ cout<<"A::fun()"<<endl;}
};
class B:public A{
public:
void fun(){cout<<"B::fun()"<<endl;}
};
int main(){
A *pa=new A();
A a;
cout<<typeid(pa).name()<<endl;
cout<<typeid(a).name()<<endl;
B *pb=new B();
cout<<typeid(pb).name()<<endl;
}
在调用完typeid()函数之后,接着调用name()成员函数,因为typeid()函数是一个返回类型为const typeid_info&类型的函数,接下来简单了解type_info类;
3.type_info类
class type_info
{
public:
virtual ~type_info();
//在type_info类中重载了==运算符,该运算符用于比较两个对象的类型是否相等
bool operator ==(const type_info&)const;
//在type_info类中重载了!=运算符,该运算符用于比较两个对象的类型是否不相等
bool operator !=(const type_info&)const;
//使用成员函数name,该函数返回对象的类型的名字
const char*name()const;
bool before(const type_info&);
private:
type_info(const type_info&);
type_info&operator =(const type_info&);
//type_info类的构造函数和赋值运算符都是私有的
};
( 1 )因为typr_info类的构造和赋值运算符重载都是私有,所以不允许用户自己创建,所以要使用type_info类的方法使用typeid函数。
(2)使用type_info类中重载的==与!=用于比较两个类型是否相等,如果两个对象的类型相等,则返回1,不相等返回0
这种方法也可以用于比较两个带有虚函数的类的对象是否相等如例。
4.dynamic_cast<>运算符
dynamic_cast<>运算符用来执行运行时类型识别和转换
dynamic_cast<>可以实现两个方向的转换,upcast和downcast
upcast:把派生类的指针,引用转换成基类的指针和引用
downcast::父类对象指针->子类指针/引用(用dynamic_cast转型是安全的)
#include<iostream>
using namespace std;
class Base
{
public:
virtual void fun()
{
cout << "Base::fun()" << endl;
}
};
class Driver :public Base
{
public:
virtual void fun()
{
cout << "Driver::fun()" << endl;
}
};
int main()
{
Base b;
Driver d;
Driver *pd = dynamic_cast<Driver*>(&b);//不需要转换返回0
cout << pd << endl;
Base *pb = dynamic_cast<Driver*>(&b);//不需要转换 返回0
cout << pb<< endl;
pb = &d;
pd = dynamic_cast<Driver*>(pb);//转换成功非0
cout << pd << endl;
}