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

代码编译运行环境:VS2012+Debug+Win32


1.函数重载(Function Overload)

1.1定义

C++规定在同一作用域中,同名函数的形式参数(指参数的个数、类型或者顺序)不同时,构成函数重载。

1.2用法

比如,要从两个变量中返回其中较大的一个值,可以编写如下两个构成重载的函数。

int max(int a,int b){
    return a>b?a:b;
};

double max(double a,double b){
    return a>b?a:b;
}

1.3注意事项

(1)函数返回值类型与构成函数重载无任何关系; (2)类的静态成员函数与普通成员函数可以形成重载; (3)函数重载发生在同一作用域,如类成员函数之间的重载、全局函数之间的重载。

2.函数隐藏(Function Hiding)

2.1定义

函数隐藏指不同作用域中定义的同名函数构成函数隐藏(不要求函数返回值和函数参数类型相同)。比如派生类成员函数屏蔽与其同名的基类成员函数、类成员函数屏蔽全局外部函数。请注意,如果在派生类中存在与基类虚函数同返回值、同名且同形参的函数,则构成函数重写。

2.2用法用例

请仔细研读以下代码。

#include <iostream>
using namespace std;

void func(char* s){
    cout<<"global function with name:"<<s<<endl;
}

class A{
    void func(){
        cout<<"member function of A"<<endl;
    }
public:
    void useFunc(){
        //func("lvlv");//A::func()将外部函数func(char*)隐藏
        func();
        ::func("lvlv");
    }
    virtual void print(){
        cout<<"A's print"<<endl;
    }
};

class B:public A{
public:
    void useFunc(){          //隐藏A::vodi useFunc()
        cout<<"B's useFunc"<<endl;
    }
    int useFunc(int i){      //隐藏A::vodi useFunc()
        cout<<"In B's useFunc(),i="<<i<<endl;
        return 0;
    }
    virtual int print(char* a){
        cout<<"B's print:"<<a<<endl;
        return 1;
    }

    //下面编译不通过,因为对父类虚函数重写时,需要函数返回值类型,函数名称和参数类型全部相同才行
    // virtual int print(){
        // cout<<"B's print:"<<a<<endl;
    // }
};

int main(){
    A a;
    a.useFunc();
    B b;
    b.useFunc();//A::useFunc()被B::useFunc()隐藏
    b.A::useFunc();
    b.useFunc(2);
    //b.print();//编译出错,A::print()被B::print(char* a)隐藏
    b.A::print();
    b.print("jf");
}
程序执行结果: 
member function of A
global function with name:lvlv
B's useFunc
member function of A
global function with name:lvlv
In B's useFunc(),i=2
A's print
B's print:jf

2.3注意事项

对比函数隐藏与函数重载的定义可知: (1)派生类成员函数与基类成员函数同名但参数不同。此时基类成员函数将被隐藏(注意别与重载混淆,重载发生在同一个类中); (2)函数重载发生在同一作用域,函数隐藏发生在不同作用域。

3.函数覆盖与函数重写(Function Override)

网上和很多书籍多都会涉及函数覆盖的概念,众说纷纭,加大了许多初学者的学习难度,甚至产生误导。事实上,函数覆盖就是函数重写。

3.1定义

派生类中与基类同返回值类型、同名和同参数的虚函数重定义,构成虚函数覆盖,也叫虚函数重写。

关于返回值类型存在一种特殊情况,即协变返回类型(covariant return type)。

3.2虚函数重写与协变返回类型

如果虚函数函数返回指针或者引用时(不包括value语义),子类中重写的函数返回的指针或者引用是父类中被重写函数所返回指针或引用的子类型(这就是所谓的协变返回类型)[4]^{[4]}。看示例代码:

#include <iostream>
using namespace std;

class A{}; 
class B:public A{};

class Base{
public:
    virtual A& show(){
        cout<<"In Base"<<endl;
        return *(new A);
    }
};

class Derived:public Base{
public:
     //返回值协变,构成虚函数重写
     B& show(){
        cout<<"In Derived"<<endl;
        return *(new B);
    }
};

3.3注意事项

(1)函数覆盖就是虚函数重写,而不是函数被”覆盖”。 从上面的代码可以看出,函数是不可能被“覆盖”的。有些人可能会错误地认为函数覆盖会导致函数被”覆盖”而”消失”,将不能被访问,事实上只要通过作用域运算符::就可以访问到被覆盖的函数。因此,不存在被”覆盖“的函数。

(2)函数覆盖是函数隐藏的特殊情况。 对比函数覆盖和函数隐藏的定义,不难发现函数覆盖其实是函数隐藏的特例。

如果派生类中定义了一个与基类虚函数同名但参数列表不同的非virtual函数,则此函数是一个普通成员函数(非虚函数),并形成对基类中同名虚函数的隐藏,而非虚函数覆盖(重写)。

《C++高级进阶教程》中认为函数的隐藏与覆盖是两个不同的概念。隐藏是一个静态概念,它代表了标识符之间的一种屏蔽现象,而覆盖则是为了实现动态联编,是一个动态概念。但隐藏和覆盖也有联系:形成覆盖的两个函数之间一定形成隐藏。例如,可以对虚函数采用“实调用”,即尽管被调用的是虚函数,但是被调用函数的地址还是在编译阶段静态确定的,那么派生类中的虚函数仍然形成对基类中虚函数的同名隐藏。

参考如下代码,考察虚函数的实调用和虚调用。

#include <iostream>
using namespace std;

class Base{
public:
    virtual void show(){
        cout<<"In Base"<<endl;
    }
};

class Derived:public Base{
public:
    void show(){
        cout<<"In Derived"<<endl;
    }
};

int main(){
    Base b;
    b.show();
    Derived d;
    d.show();          //对函数show()的实调用
    d.Base::show();    //对函数show()的实调用
    Base *pb=NULL;     
    pb=&d;             
    pb->show();        //对函数show()的虚调用
    pb->Base::show();  //对函数show()的实调用
}

程序运行结果: In Base In Derived In Base In Derived In Base

4.总结

在讨论相关概念的区别时,抓住定义才能区别开来。C++中函数重载隐藏和覆盖的区别,并不难,难就难在没弄清定义,被网上各种说法弄的云里雾里而又没有自己的理解。

在这里,牢记以下几点,就可区分函数重载、函数隐藏、函数覆盖和函数重写的区别: (1)函数重载发生在相同作用域; (2)函数隐藏发生在不同作用域; (3)函数覆盖就是函数重写。准确地叫作虚函数覆盖和虚函数重写,也是函数隐藏的特例。

关于三者的对比,李健老师在《编写高质量代码:改善C++程序的150个建议》给出了较为详细的总结,如下表所示:

三者

作用域

有无virtual

函数名

形参列表

返回值类型

重载

相同

可有可无

相同

不同

可同可不同

隐藏

不同

可有可无

相同

可同可不同

可同可不同

重写

不同

相同

相同

相同(协变)

本文为个人观点,欢迎留言批评指正。


参考文献

[1]陈刚.C++高级进阶教程[M].第一版.武汉:武汉大学出版社,2008:110-P112 [2]百度百科.函数隐藏 [3]李健.编写高质量代码:改善C++程序的150个建议.第一版.北京:机械工业出版社,2012.1:122-125 [4]C++基础:函数重写(override)与协变返回类型(covariant return type)

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏云霄雨霁

Java--集合类之Vector、BitSet、Stack、Hashtable

1687
来自专栏Java编程

四道Java基础题,你能对几道?

如果这道题你能得出正确答案,并能了解其中的原理的话。说明你基础还可以。如果你的答案 是 true 和true的话,你的基础就有所欠缺了。

1.1K1
来自专栏编程

自学Python笔记(二)

作为最最基础的初学者,尤其是面对中小学生学习Python我想大概了解一下Python,能编个小程序,能看懂一般的程序就可以,如果想深一步的学习还是需要静下心来好...

2127
来自专栏java一日一条

Java有值类型吗?

有人看了我之前的文章『Swift 语言的设计错误』,问我:“你说 Java 只有引用类型(reference type),但是根据 Java 的官方文档,Jav...

1152
来自专栏码云1024

JAVA 第二天 基本数据类型

2729
来自专栏令仔很忙

深入理解HashMap(及hash函数的真正巧妙之处)

原文地址:http://www.iteye.com/topic/539465 Hashmap是一种非常常用的、应用广泛的数据类型,最近研究到相关的内容,就...

2301
来自专栏潇涧技术专栏

Python Data Structures - C2 Sort

参考内容: 1.Problem Solving with Python Chapter5: Search and Sorting online_link ...

831
来自专栏苦逼的码农

Unicode与UTF-8的区别

要弄清Unicode与UTF-8的关系,我们还得从他们的来源说起,下来我们从刚开始的编码说起,直到Unicode的出现,我们就会感觉到他们之间的关系

7842
来自专栏风口上的猪的文章

.NET面试题系列[3] - C# 基础知识(1)

重要程度:10/10,身家性命般重要。通常这也是各种招聘工作的第一个要求,即“熟悉C#”的一部分。连这部分都不清楚的人,可以说根本不知道自己每天都在干什么。我们...

1552
来自专栏Python

迭代器和生成器

一 迭代和可迭代协议 什么叫迭代 1234不可以for循环,是因为它不可迭代。那么如果“可迭代”,就应该可以被for循环了。 这个我们知道呀,字符串、列表、元组...

21510

扫码关注云+社区

领取腾讯云代金券