覆盖与隐藏!

对覆盖和隐藏较好的解释

这几个概念都有一个共同点:函数名称相同,所以不免让人混淆,大致的区别如下:

重载(overload):
必须在一个域中,函数名称相同但是函数参数不同,重载的作用就是同一个函数有不同的行为,因此不是在一个域中的函数是无法构成重载的,这个是重载的重要特征

覆盖(override):
覆盖指的是派生类的虚拟函数覆盖了基类的同名且参数相同的函数,既然是和虚拟函数挂钩,说明了这个是一个多态支持的特性,所谓的覆盖指的是用基类对象的指针或者引用时访问虚拟函数的时候会根据实际的类型决定所调用的函数,因此此时派生类的成员函数可以"覆盖"掉基类的成员函数.
注意唯有同名且参数相同还有带有virtual关键字并且分别在派生类和基类的函数才能构成虚拟函数,这个也是派生类的重要特征.
而且,由于是和多态挂钩的,所以只有在使用类对象指针或者引用的时候才能使用上.
总之一句话:覆盖函数都是虚函数,反之不然~~

隐藏(hide):
指的是派生类的成员函数隐藏了基类函数的成员函数.隐藏一词可以这么理解:在调用一个类的成员函数的时候,编译器会沿着类的继承链逐级的向上查找函数的定义,如果找到了那么就停止查找了,所以如果一个派生类和一个基类都有同一个同名(暂且不论参数是否相同)的函数,而编译器最终选择了在派生类中的函数,那么我们就说这个派生类的成员函数"隐藏"了基类的成员函数,也就是说它阻止了编译器继续向上查找函数的定义....
回到隐藏的定义中,前面已经说了有virtual关键字并且分别位于派生类和基类的同名,同参数函数构成覆盖的关系,因此隐藏的关系只有如下的可能:
1)必须分别位于派生类和基类中
2)必须同名
3)参数不同的时候本身已经不构成覆盖关系了,所以此时是否是virtual函数已经不重要了
当参数相同的时候就要看时候有virtual关键字了,有的话就是覆盖关系,没有的时候就是隐藏关系了

上面的解说大体把三者的区别给说清楚了,但是还有一些疑惑的地方,以下以代码例子说明.

很多人分辨不清隐藏和覆盖的区别,因为他们都是发生在基类和派生类之中的.但是它们之间最为重要的区别就是:
覆盖的函数是多态的,是存在于vtbl之中的函数才能构成"覆盖"的关系,而隐藏的函数都是一般的函数,不支持多态,在编译阶段就已经确定下来了.

 1   class Base
 2   {
 3   public :
 4   virtual void f( float x) {cout << " Base::f(folat) " << x << endl;}
 5   void g( float x) {cout << " Base::g(float) " << x << endl;}
 6   } ;
 7
 8   class Derived: public Base
 9   {
10  public :
11  virtual void f( float x) {cout << " Derived::f(float) " << x << endl;}
12  void g( int x) {cout << " Deriver::g(int) " << x << endl;}
13  } ;
14
15  int main()
16  {
17  Derived d;
18  Base * pb =& d;
19  Derived * pd =& d;
20  pb -> f( 3.14f );
21  pd -> f( 3.14f );
22  pb -> g( 3.14f ); // 输出结果:Base::g(float)3.14
23  pd -> g( 3.14f ); // 输出结果:Dervied::g(int)3
24  return 0 ;
25  } 

在调用f函数的时候,派生类Derived的f函数覆盖了基类Base的f函数,而派生类Derived的g函数隐藏了基类Base的g函数.
为什么?理由很简单,f函数是virtual函数,但是g函数不是.我们可以把Base类和Derived类看成这样的一个struct:

 1 struct Base
 2 {
 3 void (*g)(float); // Base类型的函数指针,不可变
 4 struct VTABLE *__vptr; // 虚拟函数指针数组,可变
 5 };
 6
 7 void __Baseg(float)
 8 {
 9 cout<<"Base::g(folat)"<<
10 }
11
12 struct Derived
13 {
14 void (*g)(float); // Derived类型的函数指针,不可变
15 struct VTABLE *__vptr; // 虚拟函数指针数组,可变
16 };
17
18 void __Derivedg(float)
19 {
20 cout<<"Deriver::g(int)"<<
21 }
22
23 struct VTABLE
24 {
25 void (*f)(float); // 函数指针
26 };
27
28 void __Basef(float)
29 {
30 cout<<"Base::f(folat)"<<
31 }
32
33 void __Derivedf(float)
34 {
35 cout<<"Deriver::f(int)"<<
36 }
37 

在程序编译的时候,函数指针f就已经是确定的了,但是__vptr根据不同的而有分别,而这个变化是运行期动态决定的.
也就是说:f的地址不可变,__vptr可变.
回到上面的例子中,Base *pb=&d;的时候只是用Derived类对象d的__vptr修改了Base类pb的__vptr指针,但是当Base类成员建立的
时候f函数指针就是不能改变的.

当函数被声明为virtual的时候,就激活了多态机制,程序在运行的时候会根据类型的实际类型到VTABLE中查找函数指针,因此对函数g的调用就是这样子的:
pb->__vptr->g();
而对f的调用就是一般的类成员函数指针的调用了:pb->f(),因为这个类型在程序编译的时候已经确认了,所以在程序运行的时候是不能发生改变的.

综上,可以把
Derived d;
Base *pb=&d;
的过程分解为:
d.g = __Derivedg;
d.__vptr->f = __Derivedf;
pb->g = __Baseg; // 这里根据指针的真正类型确定函数指针
pb->__vptr = d.__vptr; // 这里只是简单的指针赋值,因此访问到的就是Derived的函数了
最后在调用:
pb->f(3.14f);
pb->g(3.14f);
实际上是:
pb->__vptr->__Derivedf(3.14f);
__Baseg(3.14f);
这么写就明白最后在调用的时候为什么会用那样的结果了,可以看出多了一个__vptr这个间接层实现了所谓的"动态绑定".

最后,需要说明的一点是:实际上在c++中,非static和非virtual的函数指针并不会在一个class中保存它的函数指针,上面把函数g的指针写在struct里面只是为了方便说明这样的问题:在编译阶段这个函数就已经是确定的不可改变的了.特此说明一下.

时间: 2024-10-12 15:29:43

覆盖与隐藏!的相关文章

JAVA中方法和变量在继承中的覆盖和隐藏

我们知道,在JAVA中,子类可以继承父类,如果子类声明的方法与父类有重名的情况怎么办,大伙儿都知道要是重写,但是实际上这又分为两种情况,就是方法和变量在继承时的覆盖和隐藏问题. 隐藏 :child隐藏了parent的变量和方法,那么,child不能访问parent被隐藏的变量或者方法,但是,讲B转换成A中,可以访问A被隐藏的变量或者方法 覆盖 :child覆盖了parent的变量或者方法,那么,child不能访问parent被覆盖的变量或者方法,将child转换成parent后同样不能访问par

C++重载,覆盖和隐藏区别

a.成员函数被重载的特征:(1)相同的范围(在同一个类中):(2)函数名字相同:(3)参数不同:(4)virtual 关键字可有可无. 因为函数参数不同,可以简单的理解为:两个重载函数是不同的函数,调用者能够明确 的根据不同的参数来调用不同的函数.那么如果存在这样两个函数,编译器怎么处理呢? class A{public:    void Func(int a, int b=0) {printf("This is Func1/n");}    void Func(int a) {pri

c++重载、覆盖和隐藏

c++成员函数的重载.覆盖和隐藏很容易混淆,因此我们必须搞清楚这一点. 1.重载 重载特点:同一访问区域(同类).函数名称相同.函数参数不同(不关心返回类型).virtual关键字可有可无. 代码示例: class OverLoad { public: void test() {} void test(int i) {} void test(float i) {} void test(int i, float j) {} }; 2.覆盖 覆盖是指派生类函数覆盖基类函数,特点是:不同访问区域(不同

C++ 成员函数的重载与覆盖与隐藏

重载与覆盖 成员函数被重载的特征: (1)相同的范围(在同一个类中): (2)函数名字相同: (3)参数不同: (4)virtual 关键字可有可无. 覆盖是指派生类函数覆盖基类函数,特征是: (1)不同的范围(分别位于派生类与基类): (2)函数名字相同: (3)参数相同: (4)基类函数必须有 virtual 关键字. 函数 Base::f(int)与 Base::f(float)相互重载,而 Base::g(void) 被 Derived::g(void)覆盖. #include <ios

C++函数的重载,覆盖和隐藏(——高质量编程)

函数重载概念 只有C++才有重载的概念,C语言没有. 靠行参列表的不同来区别不同的重载函数, 若是全局函数和成员函数同名时,不算重载,因为它们的作用域不同,所以成员函数会将全局函数给隐藏(遮蔽)了. 成员函数的重载,覆盖和隐藏 重载发生在相同的作用域中,而覆盖则发生在不同的作用域中 重载: 相同的作用域 函数的名字相同 函数的形参列表,顺序,类型,个数不同 virtual可有可无 覆盖: 作用域不同,属于不同的类中 函数的名字相同 形参列表相同 基类必须是虚函数

C++:类成员函数的重载、覆盖和隐藏区别?

#include <iostream> class A { public: void func() { std::cout << "Hello" << std::endl; } void func(int k) { } }; class B : public A { public: using A::func; // 把这句注释掉试试,嘿嘿 void func(int i) { } }; int main() { B b; b.func();//编译

C++重载、覆盖、隐藏的区别与举例

参考博客:http://blog.csdn.net/hexi_2000/article/details/4392107 //重载,覆盖,隐藏举例 #include <iostream> using namespace std; class A { public:     int n;     void fun()     {         cout<<"A::fun()"<<endl;     }     virtual void fun(int 

【非原创】C++类成员函数的重载、覆盖和隐藏

链接:https://www.nowcoder.com/questionTerminal/266d3a6d4f1b436aabf1eff3156fed95来源:牛客网 题目:类成员函数的重载.覆盖和隐藏区别描述正确的有? A.覆盖是指在同一个类中名字相同,参数不同 B.重载是指派生类函数覆盖基类函数,函数相同,参数相同,基类函数必须有virtual关键字 C.派生类函数与基类函数相同,但是参数不同,会"隐藏"父类函数 D.函数名字相同,参数相同,基类无virtual关键字的派生类的函数

C++中成员函数的重载、覆盖和隐藏的区别

转载请注明,原创地址:点击打开链接   http://blog.csdn.net/u010587274/article/details/38928561 个人微信公众号:tanzi_888 (潭子技术圈) C++中成员函数的重载.覆盖和隐藏的区别: 1 重载(overload): 是函数名相同,参数列表不同 重载只是在类的内部存在.但是不能靠返回值类型来判断.1.1)相同的范围(在同一个类中)1.2)函数名字相同1.3)参数不同 1.4)Virtual关键字可有可无 2  覆盖 (overrid

方法和变量在继承时的覆盖和隐藏问题

作者:华清远见讲师 最近有个同学问了我一个小问题,觉得很有意思,之前一直没有想到过.他说"java中存在方法覆盖,是否存在变量的覆盖呢?".我们知道,在java中,子类可以继承父类,如果子类声明的方法与父类有重名,这时就发生了方法覆盖.其实,这实际上这又分为两种情况,就是方法和变量在继承时的覆盖和隐藏问题,这些概念性的东西看似无聊,但是在面试中还是比较常见的,所以这里来讨论下 首先我们来看几个概念 隐藏 :子类隐藏了父类的变量和方法,那么,子类不能访问父类被隐藏的变量或者方法,但是,将