【转】c++重载、覆盖、隐藏——理不清的区别
原文网址:http://blog.sina.com.cn/s/blog_492d601f0100jqqm.html
再次把林锐博士的《高质量c++编程指南》翻出来看的时候,再一次的觉得这是一本难得的好书。实践派写的东西跟理论派和翻译派写的书有着本质的 区别,每次读这本书都觉得为什么自己读了这么多遍,还是会犯一些上面讲的小错误,编代码有时候莫名其妙又会把自己转糊涂了。这本书浅显易懂,而且提到了编 程过程中应该注意的很多细节,里面展开来讲的细节又偏偏是我觉得最为薄弱的环节,如果大家想学或者正在学习c++。建议大家用心的把这本并不长也不高深的 书好好的读几遍。
关于c++语言中重载、覆盖、隐藏这三个容易混淆的概念,我依然以林博士书中的例子发散开来回忆一下。先列举这三个概念必须满足的条件:
函数重载的特征:
1)处在相同的空间中(即相同的作用范围内,比如一个类中)。
2)函数名相同。
3)参数不同(相同位置参数的类型不同,或者参数的个数不同)。
4)virtual关键字可有可无。
覆盖的特征:
1)不同的范围(例如分别位于基类与派生类)。
2)函数名相同。
3)参数相同(参数个数与类型均相同)。
4)基类函数必须有virtual关键字(派生类可有可无,因为基类函数被声明为虚函数,派生类同名函数一定也是虚函数)。
隐藏的特征:
1)不同的范围(例如分别位于基类与派生类)。
2)函数名相同。
3)参数可相同也可不同(注意此处还有两种情况)。
4)virtual关键字可有可无。
注意:隐藏与覆盖的区别就在于如下两条:
1)如果派生类的函数与基类的函数同名,但是参数不同(不可能构成覆盖)。此时无论有无virual关键字,基类的函数将被隐藏。(不可能构成重载,因为重载必须在同一个类中)
2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virual关键字(如果有virual关键字,则满足覆盖的条件)。此时基类的函数被隐藏。
隐藏:
下面是林博士原书中的例子:
#include <iostream.h>
class Base
{
public:
void g(float x){cout << "Base::g(float)" << x <<endl;} //注意c++中将在类声明中定义了实现的函数自动默认为内联函数
void h(float x){cout << "Base::h(float)" << x <<endl;}
}
class Derived : public Base
{
public:
void g(int x){cout << "Derived::g(int)" << x <<endl;}
void h(float x){cout << "Derived::h(float)" << x <<endl;}
}
void main(void)
{
Derived d
Base* pb = &d;
Derived* pd = &d;
//behavior depends on type of pointer
pb->g(3.14f); //Base::g(float) 3.14;
pd->g(3.14f); //Derived::g(int) 3;
pb->h(3.14f); //Base::h(float) 3.14;
pd->h(3.14f); //Derived::h(float) 3.14;
}
参照隐藏规则,派生类的成员函数隐藏了基类的同名函数。所谓隐藏就是指派生类类型的对象、引用、指针访问基类和派生类都有的同名函数的时候,访问的是派生 类的函数,隐藏了基类同名函数。派生类既然自动继承了基类的成员,那么基类成员就可以被派生类直接访问,那么为什么访问的是派生类的成员函数呢?所以隐藏 规则实际上就是默认的c++名字解析过程。
在继承机制下,派生类的类域被嵌套在基类的类域中,派生类的名字解析过程如下:
1)首先在派生类中查找改名字。
2)如果第一步未查找到,及派生类的类域对改名字无法进行解析,则编译器在外围基类类域查找改名字的定义。
所以准确来说,当派生类和基类有同一名字的成员时,派生类成员是隐藏了对基类成员的直接访问。那么如果访问到基类同名成员呢?加上类作用域限定例如:Base::g(float)就可以访问了。
覆盖:
覆盖规则造成的调用现象,其实就是类的虚函数实现原理生成的。为了达到动态绑定(后期绑定)的目的,C++编译器通过某个表格(一般称为vtable), 在执行期"间接"调用实际上欲绑定的函数。每一个内含虚函数的类,C++编译器都会为它做出一个虚函数表,表中的每一个元素都指向一个虚函数的地址。
举个例子:
class base{
public:
func();
virtual vfunc1();
virtual vfunc2();
virtual vfunc3();
private:
int _data1;
int _data2;
};
base对象实例在内存中占据的空间是这样的:
base对象实例 vtable
--------------------------------------------------------------------------
vptr ---------> (*vfunc1)() -----------> base::vfunc1();
_data1 (*vfunc2)() -----------> base::vfunc2();
_data2 (*vfunc3)() -----------> base::vfunc3();
--------------------------------------------------------------------------
当派生类改写了虚函数时,虚函数表相应的被修改了:
class derived: public base{
public:
vfunc2();
};
derived对象实例 vtable
--------------------------------------------------------------------------
vptr ---------> (*vfunc1)() -----------> base::vfunc1()
_data1; (*vfunc2)() -----------> derived::vfunc2() ****注意,这里变了!!!***
_data2; (*vfunc3)() -----------> base::vfunc3()
--------------------------------------------------------------------------
所以当你写下如下程序的时候:
void main(void)
{
Derived d;
Base *pb = &d;
pb->vfunc2(); // Derived::vfunc2(void)
}
就不难理解为何pb->vfunc2()调用的是derived::vfunc2()了,因为pb实际上指向派生类derived的实例,而派生类中的虚函数表已经被修改了。
总结:简单来说,隐藏规则就是C++的名字解析过程,自里向外解析,这个好理解;而覆盖规则其实就是C++虚函数表的实现原理。
举个例子:
class base{
public:
func();
virtual vfunc1();
virtual vfunc2();
virtual vfunc3();
private:
int _data1;
int _data2;
};
base对象实例在内存中占据的空间是这样的:
base对象实例 vtable
--------------------------------------------------------------------------
vptr ---------> (*vfunc1)() -----------> base::vfunc1();
_data1 (*vfunc2)() -----------> base::vfunc2();
_data2 (*vfunc3)() -----------> base::vfunc3();
--------------------------------------------------------------------------
当派生类改写了虚函数时,虚函数表相应的被修改了:
class derived: public base{
public:
vfunc2();
};
derived对象实例 vtable
--------------------------------------------------------------------------
vptr ---------> (*vfunc1)() -----------> base::vfunc1()
_data1; (*vfunc2)() -----------> derived::vfunc2() ****注意,这里变了!!!***
_data2; (*vfunc3)() -----------> base::vfunc3()
--------------------------------------------------------------------------
所以当你写下如下程序的时候:
void main(void)
{
Derived d;
Base *pb = &d;
pb->vfunc2(); // Derived::vfunc2(void)
}
就不难理解为何pb->vfunc2()调用的是derived::vfunc2()了,因为pb实际上指向派生类derived的实例,而派生类中的虚函数表已经被修改了。
总结:简单来说,隐藏规则就是C++的名字解析过程,自里向外解析,这个好理解;而覆盖规则其实就是C++虚函数表的实现原理。
Stay hungry, stay foolish!