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

我们在学习C++中总会有很多概念容易搞混,其中成员函数的重载、覆盖和隐藏这三个概念就经常让很多人头大。
下面我们就来介绍一下这三个的区别:
成员函数被重载的条件是:
(1)相同的范围也就是要在同一个类中
(2)函数名字相同
(3)参数不同(这里包括参数类型,参数的顺序,参数的个数)
(4)virtual关键字可有可无
覆盖是指派生类函数覆盖基类函数,特征是:
(1)不同的范围也就是必须一个位于基类一个位于派生类
(2)函数名字相同
(3)参数要相同
(4)基类函数必须有virtual关键字
举个例子:

#include<iostream>
using namespace std;
class Base
{
public:
    void f(int x){ cout << "Base::f(int)" << x << endl; }
    void f(float x){ cout << "Base::f(float)" << x << endl; }
    virtual void g(void){ cout << "Base::g(void)" << endl; }
};
class Derived :public Base
{
public:
    virtual void g(void){ cout << "Derived::g(void)" << endl; }
};
void main()
{
    Derived d;
    Base *pb = &d;
    pb->f(42);//Base::f(int)
    pb->f(3.14f);//Base::f(float)
    pb->g();//Derived::g(void)
}

运行结果:
这里写图片描述
在上面程序中:
Base::f(int)与Base:f(float)构成重载
Base::g(void)和Base::g(void)构成覆盖,派生类的g函数覆盖基类的g函数
从上面看重载和覆盖区分起来并不那么困难,但是C++还存在隐藏规则问题使问题的复杂性就提高了
隐藏是指派生类的函数屏蔽了与其同名的基类函数,隐藏规则如下:
(1)如果派生类的函数名和基类的函数名相同但是参数不同,此时不论有没有virtual关键字,基类的函数将被隐藏(这里容易和重载混淆)
(2)如果派生类的函数和基类的函数函数名相同,参数也相同,但是基类函数没有virtual关键字,此时基类的函数被隐藏(这里容易和覆盖混淆)
下面再看一个例子:

#include<iostream>
using namespace std;
class Base
{
public:
    void f(float x){ cout << "Base::f(float)" << x << endl; }
    void g(float x){ cout << "Base::g(float)" << x << endl; }
    void h(float x){ cout << "Base::h(float)" << x << endl; }
};
class Derived :public Base
{
public:
    virtual void f(float x){ cout << "Derived::f(float)" << x << endl; }
    void g(int x){ cout << "Derived::g(int)" << x << endl; }
    void h(float x){ cout << "Derived::h(float)" << x << endl;}
};

(1)函数Derived::f(float x)覆盖了Base::f(float)
(2)函数Derived::g(int)隐藏了Base::g(float);
(3)函数Derived::h(float)隐藏了Base::h(float);
还是有很多人注意不到隐藏这回事,由于没有认识到“隐藏”,隐藏的发生可以被称为神出鬼没,常常产生令人疑惑的结果
比如下面这个例子:

void main(void) 
{ 
Derived  d; 
Base *pb = &d; 
Derived *pd = &d; 
// Good : behavior depends solely on type of the object 
pb->f(3.14f); // Derived::f(float) 3.14  
pd->f(3.14f); // Derived::f(float) 3.14 
// Bad : behavior depends on type of the pointer 
pb->g(3.14f); // Base::g(float) 3.14  
pd->g(3.14f); // Derived::g(int) 3        (surprise!) 

// Bad : behavior depends on type of the pointer 
pb->h(3.14f); // Base::h(float) 3.14      (surprise!) 
pd->h(3.14f); // Derived::h(float) 3.14  
}

运行结果如下:
这里写图片描述
上面的代码我们看到pd和pb指向同一个地址按道理说结果应该一样,可是结果却不像我们想象的那样。
摆脱隐藏:
我们知道隐藏引起了很多麻烦,例如下面这个例子:

class Base
{
public:
    void f(int x);
};
class Derived:public Base
{
public:
    void f(char *str);
};
void Test()
{
    Derived *pd = new Derived;
    pd->f(10);//编译出错
}

语句pd->f(10);本意是想调用函数Base::f(int),但是不幸的是Base::f(int)被Derived::f(char*)隐藏了,因为int不能隐式转化为字符串,所以编译时出错。
既然隐藏带来这么多问题,为什么还要存在呢,不是自找麻烦吗?
实际上隐藏还是有它的用处的,至少它有两个存在的理由:
(1)写语句pd->f(10)的人可能真的想调用Derived::f(char*)函数,只是他将参数写错了,这时候他就可以根据编译报错改正然后得出正常结果了。有了隐藏规则编译器就可以明确指出错误了,这未必不是好事,否则编译器只会将错就错,程序员将很难发现这个错误,留下祸根。
(2)假如类Derived有多个基类(多重继承),有时搞不清楚是那个基类定义了函数f。如果没有隐藏规则,那么pd->f(10)可能会调用一个出乎意料的基类函数f。尽管隐藏规则看起来不那么有道理,但它的确能消灭这些意外。
如果语句pd->f(10)一定要调用函数Base::f(int),那么将类Derived改为:

class Derived:public Base
{
    public:
    void f(char* str);
    void f(int x){Base::f(x);}
};

这里写图片描述

猜你喜欢

转载自blog.csdn.net/flowing_wind/article/details/81284489