简单的说,“接口”就是该类的public函数。由于在类的外部一般只能通过这些公共函数才能访问一个类,所以这些函数就称为类的接口。虽然一个类也可以存在public成员数据,但是,一般不要编写这种能够直接在类的外部访问的public数据,因为这样就使信息隐藏性受到一定的“破坏”。
当使用一个基类的指针指向其一个派生类的实体时,利用该指针调用一个函数,如果这个函数在基类的定义中使用了virtual关键字,那么,这个函数在派生类中自动也是虚函数(前提:此函数没有被“重载”[注:重载的实质是函数名未发生改变,但是函数的参数个数、参数类型、或者类型的顺序发生改变,重载也叫做“静态多态性”],但是可以重新定义其可执行语句)。这个时候,就会发生函数的动态绑定--也就是执行的函数不再是指针所属类的成员函数,而是针所指向的对象对应类的同名函数。
例如,以下的代码片段:
class Base
{
public:
virtual get(){...}//基类里面的虚函数
......
};
class Derived :public Base
{
public:
get(){...}//该函数在派生类自动为虚拟函数
......
};
int main()
{
Base *pB;
pB=new Derived;
pB-> get();//运行时动态绑定,执行Derived::get()函数
......
}
那么,这是不是说,如果有以下的语句:
Base *pB;
pB=new Base;
pB-> get();
函数就不再是动态绑定呢?答案是:还是动态绑定。只要用 "pointer-> Virtual_Function(); ",就会发生函数调用的动态绑定。
这是为什么呢?
这是因为,编译器再编译上述代码的时候,做了三项工作:
1、为每一个含有虚函数的类制造一个唯一的虚函数表(VTable),虚函数表是一个虚函数入口地址数组,每一个成员就是一个虚函数的指针(入口地址);
2、每实例化一个含有虚函数的对象,就为该对象增加一个VPtr(虚函数表指针)做为该对象的首成员数据,它保存的值固定的,即其所属类的VTable地址;
3、编译 "pointer-> Virtual_Function(); "时,不是直接确定被调函数的地址(直接确定被调函数地址就是“静态绑定”了),而是插入以下的代码:
3.1 访问pointer所指向的真实对象的Vptr,从而得到该对象所属类的唯一VTable地址;
3.2 访问VTable,查阅被调函数的地址;
3.3 按照3.2的结果,执行相应的函数代码.
纯虚函数是在基类里面,没有给出实现,而是直接赋值为0的虚函数.例如:
class Instrument
{
public:
virtual void play()=0;//这就是纯虚函数
......
}
纯虚函数没有任何动作,也没有入口地址,因而不会填写Vtable里面的指针的值,仅仅用作占据虚函数表位置(因为无论你在派生类里用什么样的顺序实现多个虚拟函数,在派生类的虚函数表中,从基类继承来的虚函数在派生类的Vtable中的顺序,仍然保持与基类的相同).
此外,纯虚函数还有两个作用:
一是,纯虚函数表示其所属类是一个抽象类(至少包含一个纯虚函数的类叫作抽象类),抽象类不能被实例化;
二是,纯虚函数表示其自身仅仅是一个基类接口,其所属类的某个派生类必须实现这个接口函数.否则,派生类还是包含纯虚函数,还是抽象类,不能被实例化;
当使用一个基类的指针指向其一个派生类的实体时,利用该指针调用一个函数,如果这个函数在基类的定义中使用了virtual关键字,那么,这个函数在派生类中自动也是虚函数(前提:此函数没有被“重载”[注:重载的实质是函数名未发生改变,但是函数的参数个数、参数类型、或者类型的顺序发生改变,重载也叫做“静态多态性”],但是可以重新定义其可执行语句)。这个时候,就会发生函数的动态绑定--也就是执行的函数不再是指针所属类的成员函数,而是针所指向的对象对应类的同名函数。
例如,以下的代码片段:
class Base
{
public:
virtual get(){...}//基类里面的虚函数
......
};
class Derived :public Base
{
public:
get(){...}//该函数在派生类自动为虚拟函数
......
};
int main()
{
Base *pB;
pB=new Derived;
pB-> get();//运行时动态绑定,执行Derived::get()函数
......
}
那么,这是不是说,如果有以下的语句:
Base *pB;
pB=new Base;
pB-> get();
函数就不再是动态绑定呢?答案是:还是动态绑定。只要用 "pointer-> Virtual_Function(); ",就会发生函数调用的动态绑定。
这是为什么呢?
这是因为,编译器再编译上述代码的时候,做了三项工作:
1、为每一个含有虚函数的类制造一个唯一的虚函数表(VTable),虚函数表是一个虚函数入口地址数组,每一个成员就是一个虚函数的指针(入口地址);
2、每实例化一个含有虚函数的对象,就为该对象增加一个VPtr(虚函数表指针)做为该对象的首成员数据,它保存的值固定的,即其所属类的VTable地址;
3、编译 "pointer-> Virtual_Function(); "时,不是直接确定被调函数的地址(直接确定被调函数地址就是“静态绑定”了),而是插入以下的代码:
3.1 访问pointer所指向的真实对象的Vptr,从而得到该对象所属类的唯一VTable地址;
3.2 访问VTable,查阅被调函数的地址;
3.3 按照3.2的结果,执行相应的函数代码.
纯虚函数是在基类里面,没有给出实现,而是直接赋值为0的虚函数.例如:
class Instrument
{
public:
virtual void play()=0;//这就是纯虚函数
......
}
纯虚函数没有任何动作,也没有入口地址,因而不会填写Vtable里面的指针的值,仅仅用作占据虚函数表位置(因为无论你在派生类里用什么样的顺序实现多个虚拟函数,在派生类的虚函数表中,从基类继承来的虚函数在派生类的Vtable中的顺序,仍然保持与基类的相同).
此外,纯虚函数还有两个作用:
一是,纯虚函数表示其所属类是一个抽象类(至少包含一个纯虚函数的类叫作抽象类),抽象类不能被实例化;
二是,纯虚函数表示其自身仅仅是一个基类接口,其所属类的某个派生类必须实现这个接口函数.否则,派生类还是包含纯虚函数,还是抽象类,不能被实例化;