重载(Overloading)、重写(Overriding)和隐藏(Hiding)

在C++中常常遇到这几个概念:重载(Overloading)、重写(Overriding)和隐藏(Hiding),经常混淆,下面来区分一下
http://blog.csdn.net/hackbuteer1/article/details/7475622

重载(Overloading)

重载是指在同一作用域的不同函数使用相同的函数名,但是函数的参数个数或类型不同,在C++中编译器会将函数名用函数声明中的标识符和其形参的类型组合作为修饰(不包含返回值类型),生成一些不同名称的预处理函数,来实现同名函数调用时的重载。
比如:

1
2
3
4
5
6
//声明
int max(int a, int b);
double max(double a, double c);
//调用
int max_int = max(1, 2);
double max_double = max(1.0, 2.0);

以上两个函数名称相同,只是形参不同,在调用的时候,C++会根据传入实参的类型来确定调用哪个函数。给函数起相同的名字,是为了说明这类函数的功能是相同的,而为了应对不同的数据类型或者输入参数的个数,而定义了一系列函数。

在C++类中,如果在类作用域内,有同名的函数,则他们之间是函数重载,但是如果它的派生类中与之同名的函数,因为不属于同一类,不在同一个作用域中,所以就不算函数重载。

比如基类A

1
2
3
4
5
6
   class A
{
public:
int max(int a, int b);
double max(double a, double c);
};

派生类B:

class B : public A
{
 public:
     float max(float a, float b, float c);
};

此时,派生类中的B不是A中函数的重载,因为它们不在同一个作用域中,这里算作是函数隐藏,用派生类B声明的对象,也不能调用基类A中的参数为整型的max函数,而只能调用它自己类中定义的浮点型max函数。

重写(Overriding)

重写也称为覆盖,是指在派生类中对基类中的虚函数重新实现,即函数名和参数都一样,只是函数体中的实现是不同的。函数的重写有两个关键要素:派生类和虚函数。即派生类对基类中的虚函数进行重新定制,即重写。

比如

class A
{
    public:
    A();
    ~A();
    virtual void printLog()
    {
        std::cout << "这是基类" << std::endl;
    }
};

class B : public A
{
    public:
    B();
    ~B();
    virtual void printLog()
    {
        std::cout << "这是派生类" << std::endl;
    }
};

上述代码中派生类B对基类A中的虚函数printLog()进行了个性化定制,即重写。

重写要注意几个问题:

  • (1)函数的重写与访问层级(public、private、protected)无关。比如:
    class B : public A
    {
    public:
        B();
        ~B();
    private:
        virtual void printLog()
        {
            std::cout << "这是派生类,在private中" << std::endl;
        }
    };
    

上面虽然派生类中的printLog与基类的访问层级不同,但是仍然可以实现对该函数的个性化定制。通常情况下,派生类改写的函数应该与基类对应的函数有相同的访问层级。

  • (2)const可能会使虚成员函数的重写失效

常量成员函数中的常量属性也是函数签名中的一部分,所以如果对虚函数成员加上了const属性,其实并不是对原来的基类虚函数进行重写,而是重新定义了一个函数。

比如:

class B : public A
    {
    public:
        B();
        ~B();
    private:
        virtual void printLog() const
        {
            std::cout << "这是派生类,限制为const类型" << std::endl;
        }
    };

这里派生类B中的printLog函数并不是对基类中printLog函数的重写,因为它们具有不同的函数签名,它们是两个不同的函数。

  • (3)重写函数必须与原函数具有相同的返回类型

因为函数的返回类型不是函数前面的一部分,所以若派生类重写的基类中的虚函数,则两者必须具有相同的返回类型。如果返回值不同,编译器会抛出“重写虚函数返回类型有差异”的错误提示。

class B : public A
{
public:
    B();
    ~B();
private:
    virtual bool printLog() 
    {
        std::cout << "这是派生类,限制为const类型" << std::endl;
        return true;
    }
};

上述代码中派生类中的返回值与基类是不同的,会报错。

隐藏(Hiding)

隐藏是指派生类中的函数屏蔽基类中相同名字的非虚函数。函数隐藏的两个关键要素:派生类和非虚函数。

在调用一个类的成员函数时,编译器会沿着类的继承链逐级向上查找函数的定义,如果找到就停止,如果一个派生类和一个基类有一个同名的函数,由于派生类在继承链中处于下层,编译器则最终会选择派生类中的函数。从而,基类同名的函数被派生类中同名的函数隐藏,编译器的函数查找也就到达不了基类中,这里的同名是指函数名相同,参数的类型可以不同,返回值的类型也可以不同。

class A
{
public:
    A();
    ~A();
    void printLog(int data);
    void printLog(float data);//作用域相同,且函数同名,类型不同,所以是函数重载

};

class B
{
public:
    B();
    ~B();
    bool printLog(double data);//是函数隐藏
    //因为不在同一个作用域中,所以不是重载,
    //又因为不是虚函数,所以不是函数重写
};

下表列出了重载、重写、隐藏在作用域、函数名、参数类型和返回值等方面的差异。