实用经验 82 区分overloading、overriding、hiding的差异

OOP存在重载(overloading)、重写(overriding)、隐藏(hiding)3个概念。他们非常相似,也容易混淆。本实用经验将详细讲述他们之间的区别。

重载(overloading)

也许你听说过函数重载,运算符重载。也许现在你已经可熟练使用重载了。重载其实就是一个名字在不同的形势下,存在不同的实现。先来看重载的官方定义:重载指同一作用域的不同函数使用相同的函数名,但是函数的参数或类型不同。简单的将就是不同的函数使用同样的标示符。并且这些函数位于同一个作用域。

在C++中,普通的全局函数可支持重载,类的成员函数也可支持重载。而且类的成员函数重载作用域仅在类中。下面看一个取整函数声明:

inline float  ceil(float X);
inline long double  ceil(long double X);
inline double  ceil(double X);

这3个全局函数ceil(float X)、ceil(double X)和ceil(long double X)互为重载。都使用了函数标识符ceil函数标示符名称。而且都位于全局作用域中。我想现在你比较关注的应该是编译器在调用时怎么区分它们的?是的,这正是重载实现的关键所在。

重载的条件:
(1)至少有两个以上的函数具有相同的标示符名称。
(2)函数的形参个数或类型不同或是形参的顺序不同。
(3)函数的返回值相同与否,不能用于判断两个函数是否互为重载函数。
(4)互为重载的函数必须具备相同的作用域。不同作用域内的同名函数通过作用域来区分不形成重载。

同样类成员函数也支持重载,在实现机制方面他们和全局函数没有太大的区别。唯一的区别是一个是全局作用域,一个是类作用域;需要明确的是父类和派生类中的同名函数不形成重载,因为他们不在一个作用域。

// 打印机类
class CPrinter
{
public:
    void  Print(int iData);
    void  Print(float fData);
    void  Print(char *pszData, int iDatalength);
};

// 字符串打印机
class CStrPrinter : public CPrinter
{
    void Print(char *pszData);
}

CStrPrinter strprinter;
Strprinter.print(2.0);  

上述代码,无法通过编译,因为void Print(float fData)被void Print(char *pszData)覆盖了。此例可有效的验证上述论断。但是如果你真的想让基类中的print和派生类中的print形成重载,亦可行。见如下代码:

// 打印机类
class CPrinter
{
public:
    void  Print(int iData);
    void  Print(float fData);
    void  Print(char *pszData, int iDatalength);
}
// 字符串打印机
class CStrPrinter : public CPrinter
{
    Using CPrinter::Print;
    void Print(char *pszData);
}

CStrPrinter strprinter;
Strprinter.print(2.0);

上述代码可以通过编译,因为void Print(float fData)与void Print(char *pszData)互为重载。基类的成员函数与派生类的成员函数不互为重载。因为他们分属于不同的作用域。通过函数声明可将基类的声明引入到派生类中,只有在此前提下才可能存在重载。

关于重载最后需要说明的是:编译器在实现重载时,编译器是以静态绑定的方式处理的,关于实现机制你可参考实用经验 48相关论述。

重写(overriding

如果你了解virtual函数,那你应该也知道重写(overriding)。基类和派生类之间的多态性,一般通过派生类重写基类中的同名且同参的虚函数实现。

//  CFile文件操作类,完成文件的操作动作。
class CFile
{
public:
	CFile();
    virtual ~CFile();
    // 文件打开操作
    virtual Open(char *pszFileName, int iOpenType);
    // 文件关闭操作
    virtual Close();
    //  文件读操作
    virtual Read(void *pBuffer, int iBufferLength);
    //  文件写操作
    virtual Write(void *pBuffer, int iBufferLength);
};

//  Text文件操作类,完成Text文件的操作动作。
class CTextFile: public CFile 
{
public:
	CTextFile();
    virtual ~ CTextFile ();
    // 文件打开操作
    virtual Open(char *pszFileName, int iOpenType);
    // 文件关闭操作
    virtual Close();
    //  文件读操作
    virtual Read(void *pBuffer, int iBufferLength);
    //  文件写操作
    virtual Write(void *pBuffer, int iBufferLength);
};

CTextFile中Open,Close()及Read就是分别对CFile类中Open,Close()及Read函数的重写。一般重写需要遵守下述准则:

  • 重写函数不能是static,且必须是virtual函数,即使父类未声明为virtual,那祖先也必须声明为virtual。
  • 重写函数必须有相同的类型,名称和参数列表 (即相同的函数原型)。
  • 重写函数的访问修饰符可以不同。父类声明virtual虚函数可见性为private,派生类重写改public,protected亦可。
  • const可能会导致虚函数成员重写失败。这是因为基类成员函数和派生类成员函数的标签不一样。
//  Text文件操作类,完成Text文件的操作动作。
class CTextFile: public CFile 
{
public:
     CTextFile();
     ~CTextFile();
     // Text 文件打开操作。
     virtual int  Open(char *pszFileName, int iOpenType) const;.
};

派生类CTextFile中的Open带了const,而基类CFile没有带const,因为具有不同的函数标签,所以派生类CTextFile中的Open函数,并没有重写CTextFile中的Open函数。

隐藏(hiding)

隐藏,就是派生类中的函数屏蔽了基类中的同名函数的非虚函数。派生类的同名非虚函数会隐藏基类中的同名函数。只要派生类中的函数和基类中的函数同名即可,而不需要两者具有相同的参数列表。

重定义后子类调用的函数是子类自己的函数,父类的函数会被隐藏。如果想调用父类的该同名函数,需要加上父类作用域来指定调用的函数。

因为编译器在实现函数调用时,首先会查找本类中函数,只要本类中有函数名称满足要求即停止查找。如果查找不到在沿着继承链逐级向上查找,直到查找到函数名称为止。由于在派生链中派生类属于下级,存在同名函数时,编译器最终选择派生类中的函数。从而导致基类的同名函数就被屏蔽了。

class Base                  // 基类
{
public:
    void f(int x);
};
class Derived : public Base    // 派生类
{
public:
    void f(char *str);
};

void Test(void)
{
    Derived *pd = new Derived;
    pd->f(10);         // 错误的
    pd->Base::f(10);  // 正确的。
}

语句pd->f(10)的本意是想调用函数Base::f(int),但是Base::f(int)不幸被Derived::f(char *)隐藏。由于数字10不能被隐式地转化为字符串,所以导致编译报错。

综上可知,重载、重写和隐藏所关注的主要是作用域,函数名,参数,返回值,有无virtual等。下面就以这些为切入点,总结三者的区别和联系,如表11-2所示。

表11-2 overloading/overriding/hiding对比
对比项作用域Virtual函数名参数列表返回值
overloading相同可有可无相同不同可同,可不同
overriding不同相同相同相同
Hiding不同可有可无相同可相同,可不同可同,可不同

请谨记

  • 在C++中,重载(overloading)、重写(overriding)、隐藏(hiding)三者极为相似。所以极易混淆,尤其是初学者。三者的差异可从作用域,函数名,参数,返回值,有无virtual等方面来理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值