覆盖与隐藏

题目链接:

https://www.nowcoder.com/questionTerminal/f29ec891b0284259a922d0dae964ef3a

来源:牛客网

下面程序的输出是()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class A
{
public:
void foo()
{
printf("1");
}
virtual void fun()
{
printf("2");
}
};
class B:public A
{
public:
void foo()
{
printf("3");
}
void fun()
{
printf("4");
}
};
int main(void)
{
A a;
B b;
A *p = &a;
p->foo();
p->fun();
p = &b;
p->foo();
p->fun();
A *ptr = (A *)&b;
ptr->foo();
ptr->fun();
return 0;
}

A.121434

B.121414

C.121232

D.123434

答案:

B

  1. 首先声明为A类型的指针指向实际类型为A的对象,调用的肯定是A的方法,输出1 2,
  2. 然后声明为A类型的指针指向实际类型为B的对象,则非虚函数调用A的方法,输出1,虚函数调用实际类型B的方法,输出4
  3. 声明类型为A的指针指向实际类型为B的对象,进行一个强制类型转换,其实这种父类指针指向子类会自动进行类型转换,所以是否强制类型转换都不影响结构,原理同上一步,结果输出1 4
    所以最终输出为121414

大家都觉得很自然,但是没有注意到一个小插曲,就是这个foo()触发的隐藏机制: 派生类的foo()由于函数名,参数与基类都相同,然而又没有virtual修饰,因此不可避免地会触发隐藏。

一旦有virtual修饰就成覆盖了!搞不清楚隐藏何时触发的同学请百度:重载、覆盖、隐藏的区别)

问题是,看到有同学问: 为什么此处触发隐藏了,p和ptr在调用foo()的时候仍然调用基类的,不是被隐藏了么???

这么问的原因是,很多同学知道了有隐藏这么回事,但是不清楚隐藏触发后会发生什么。 隐藏机制触发之后,指针的调用取决于指针的类型。如果定义的是派生类指针,则该基类成员不可见(隐藏),但是若为基类指针,该基类成员仍然是可见的啊!因为此处的p和ptr均为基类指针,只是分别指向了基类和派生类对象,所以调用foo()的时候仍然是基类的成员。

但是如果定义个派生类指针pb,如下:

B *pb=&b;

pb->foo();

这时只会调用派生类的foo(),虽然B继承自A,但是基类的foo()会被隐藏

这样看起来似乎莫名其妙,因为你想当然地认为派生类的指针肯定调用自己的成员啊,隐藏存在的意义是什么?就像此题,不用考虑它我也能做对!

但是一旦foo()里有参数的时候,你就会大吃一惊!

假设A中为void foo(float a),B中为void foo(int a):

做如下调用:

B *pb=&b;

pb->foo(3.14);

到底会调用谁?你可能会想: 首先foo()成员不是虚函数,但是B继承A,B中有两个foo(),调用foo(3.14)时根据参数类型应该匹配基类的void foo(float)成员。

然而并不是!

因为触发了隐藏机制,基类的void foo(float)会被隐藏,所以即使你调用foo(3.14)仍然只会调用派生类的void foo(int)成员。

你的惊讶正好解释了隐藏机制存在的意义。

(PS:牛客网上C/C++专项训练上有专门一道题考察这种情况,当时解释里提出隐藏机制时大多数人也是一脸懵逼)

总结:

1.判断要点:如果不是重载也不是覆盖,派生类和基类中一旦出现同名函数,一定触发隐藏机制(这是个简便判断技巧,你可以考虑除去重载和覆盖的任何同名函数情况,一定满足隐藏机制触发的两条规则)。

2.隐藏触发的结果:指针对成员的函数调用取决于指针类型。

若本身是基类指针(不管指向基类还是派生类)则仍然调用基类成员(不会牵扯到派生类,此处是隐藏,和多态没关系,按第1点已说明隐藏的触发可以首先排除覆盖,也就是多态问题);

若本身是派生类指针,这时你就会看到隐藏的威力!此时不是简单地继承基类的成员,然后根据参数匹配调用,而是隐藏基类成员,只会调用派生类成员。

0%