在面向对象(OO)的世界中存在着三个十分容易混淆的概念:重载(Overloading)、重写(Overriding)、隐藏(Hiding)。

重载

重载是指同一作用域的不同函数使用相同的函数名,但是函数的参数个数或类型不同。

重载在C中就已经存在了,正如我们所熟悉的abs函数一样,如下所示:

double abs(double);
int abs(int);
abs(1); // call abs(int);
abs(1.0); // call abs(double);

重载函数就是在一个类空间里具有相同名字、不同参数的一些函数。比如下面类Maxer中的Max函数:

class Maxer {
public: void Max(int a, int b); void Max(double a, double b); void Max(double a, double b, double c); ...// other code
};

但是,如果用派生类newMaxer继承基类Maxer:

class newMaxer : public Maxer {
public: void Max(int a, double b); ...// other code
};

派生类newMaxer中的Max函数并不是基类Maxer中Max函数的重载兄弟,因为它们分属于不同的作用域。所以当写下如下代码时,编译器会报错:

newMaxer newMax;
newMax.Max(1, 3); // 编译报错

这是因为在派生类的作用域中没有找到Max(int, int)的函数定义,基类Maxer中的Max被派生类中的Max(int, double)掩盖了,于是就出现了“参数不匹配”的错误提示。如果想让它们兄弟四个构成重载,需要将基类中的Max函数声明引入到派生类的作用域中,如下所示:

class newMaxer : public Maxer {
public: using Maxer::Max;
void Max(int a, double b); ...// other code
};

重写

重写是指在派生类中对基类中的虚函数重新实现,即函数名和参数都一样,只是函数的实现体不一样。重写是我们十分熟悉的一个操作,它与虚函数的实现息息相关。

这里涉及两个关键要素:派生类和虚函数,如下所示:

class Student {
public: Student(){} ~Student(){} virtual void Show() {
std::cout<<"Student..."<<std::endl;
} }; class CollegeStudent : public Student {
public: CollegeStudent(){} ~CollegeStudent(){} virtual void Show() {
std::cout<<"CollegeStudent..."<<std::endl;
}
}

但是重写有几点必须注意:

  • (1)函数的重写与访问层级(public、private、protected)无关。
class CollegeStudent : public Student {
public: CollegeStudent(){} ~CollegeStudent(){} private: virtual void Show() {
std::cout<<"CollegeStudent..."<<std::endl;
}
}

上述派生类中的Show与基类的访问层级不同,但还是成功地实现了对该函数的特殊定制。

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

常量成员函数与一般的成员函数在函数签名中是不同的,其常量属性是函数签名中的一部分。

class CollegeStudent : public Student {
public: CollegeStudent(){} ~CollegeStudent(){} virtual void Show()const {
std::cout<<"CollegeStudent..."<<std::endl;
}
}

因为具有不同的函数签名,所以派生类中的Show函数并没有重写基类中的Show函数。

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

因为函数的返回类型不是函数签名的一部分,所以若派生类重写了基类类型中对应的函数,那么它们必须有相同的返回类型。如果返回值不同,编译器会抛出“重写虚函数返回类型有差异”的错误警示,如下所示:

class CollegeStudent : public Student {
public: CollegeStudent(){} ~CollegeStudent(){} virtual bool Show() {
std::cout<<"CollegeStudent..."<<std::endl;
}
}

该规则存在一种例外情形,称为“协变返回值类型”。协变的返回值必须是子类或是父类的指针或是引用,如下所示:

class CollegeStudent : public Student {
public: CollegeStudent(){} ~CollegeStudent(){} CollegeStudent& Show() {
std::cout<<"CollegeStudent..."<<std::endl;
}
}

需要注意的是,如果有返回值,返回值必须是子类或父类的引用或指针,如果父类的返回值是引用,那么子类返回值也是引用;如果父类返回值是指针,那么子类返回值也是指针。否则,编译将不通过。

隐藏

隐藏是指派生类中的函数屏蔽基类中具有相同名字的非虚函数。所以它的两个重要要素就是派生类和非虚函数。

在调用一个类的成员函数时,编译器会沿着类的继承链逐级地向上查找函数的定义,如果找到就停止。如果一个派生类和一个基类有一个同名函数,由于派生类在继承链中处于下层,编译器则最终会选择派生类中的函数。如此一来,基类的同名成员函数就会屏蔽隐藏,编译器的函数查找也就到达不了基类中。

还是采用前面的newMaxer类中的Max函数来说明这一问题,如下所示:

class Maxer {
public: void Max(int a, int b); void Max(double a, double b); void Max(double a, double b, double c); ...// other code
}; class newMaxer : public Maxer {
public: bool Max(int a, int b);
void Max(int a, double b); ...// other code
};

当编译器在继承链中查找到Max函数时,派生类中的Max函数阻止了它向上寻找,隐藏了基类中的Max。

总结

最后,列出一张简单的表格让大家可以对这三者有个清晰的理解。

关系 作用域 有无virtual 函数名 参数类型 返回值类型
重载 相同 可有可无 相同 不同 可同可不同
重写(覆盖) 不同 相同 相同 相同(协变)
隐藏(重定义) 不同 可有可无 相同 可同可不同 可同可不同

个人主页:

www.codeapes.cn

区分Overloading、Overriding及Hiding的更多相关文章

  1. Override is not allowed when implementing interface method Bytecode Version Overriding and Hiding Methods

    java - @Override is not allowed when implementing interface method - Stack Overflow https://stackove ...

  2. Polymorphism & Overloading & Overriding

    In Java, a method signature is the method name and the number and type of its parameters. Return typ ...

  3. Java 抽象类详解

    在<Java中的抽象方法和接口>中,介绍了抽象方法与接口,以及做了简单的比较. 这里我想详细探讨下抽象类. 一.抽象类的定义 被关键字“abstract”修饰的类,为抽象类.(而且,abx ...

  4. Object Pascal中文手册 经典教程

    Object Pascal 参考手册 (Ver 0.1)ezdelphi@hotmail.com OverviewOverview(概述)Using object pascal(使用 object p ...

  5. Chapter 8. Classes

    8.1. Class Declarations 8.1.1. Class Modifiers 8.1.1.1. abstract Classes 8.1.1.2. final Classes 8.1. ...

  6. DELPHI学习---类和对象(五篇)

    Classes and objects(类和对象) 类(或者类类型)定义了一个结构,它包括字段(也称为域).方法和属性:类的实例叫做对象:类的字段.方法和属性被称为它的部件(components)或成 ...

  7. IN8005 Exercise Session

    Exercise Session for Introductioninto Computer Science(for non Informatics studies, TUM BWL)(IN8005) ...

  8. J2EE相关总结

    Java Commons The Java™ Tutorials: http://docs.oracle.com/javase/tutorial/index.html Java Platform, E ...

  9. 每个 Java 开发者都应该知道的 5 个注解

    自 JDK5 推出以来,注解已成为Java生态系统不可缺少的一部分.虽然开发者为Java框架(例如Spring的@Autowired)开发了无数的自定义注解,但编译器认可的一些注解非常重要. 在本文中 ...

随机推荐

  1. django设置mysql为数据库笔记

    1,guest/settings.py中加上 import pymysql pymysql.install_as_MySQLdb() 安装好pymysql 2,guest/settings.py的DA ...

  2. LeetCode.1009-十进制数的补码(Complement of Base 10 Integer)

    这是小川的第377次更新,第404篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第238题(顺位题号是1009).每个非负整数N都具有二进制表示.例如,5可以二进制表示为 ...

  3. [JavaScript] console.log只在查看时才会读取这个打印的对象,并把此刻相关属性和值显示出来

      /** * 写个函数解决console.log只在查看时才会读取这个打印的对象,并把此刻相关属性和值显示出来 * @param arg */ const log = function (...ar ...

  4. selenium:css_selector定位详解

    selenium:css_selector定位详解(css selector和xpath的比较) 来源:https://www.cnblogs.com/haifeima/p/10138154.html ...

  5. IDEA 一次启动多个微服务模块项目

    1,打开IDEA项目 .idea 下 的workspace.xml 2,查找“RunDashboard” 节点 3,添加如下内容 <option name="configuration ...

  6. 第五周总结&实验·

                                                                  本周总结 1.final声明的变量即成为常量,常量不可以修改. 2.子类能够 ...

  7. [转帖] Linux 下面 perl 命令的简介

    https://www.jb51.net/article/123326.htm 感觉挺好的 改天需要仔细学习一下. 前言 本文主要给大家介绍了关于Perl单行命令的相关内容,分享出来供大家参考学习,下 ...

  8. 面试题 | 数据库笔试题集合·之·SQL语句(2)

    第2章 SQL 语句 2.1 选择2.1.1 DELETE FROM S WHERE 年龄>60 语句的功能是( A ) A.从 S 表中彻底删除年龄大于 60 岁的记录B.S 表中年龄大于 6 ...

  9. [BZOJ2144][国家集训队2011]跳跳棋

    题目描述 跳跳棋是在一条数轴上进行的.棋子只能摆在整点上. 每个点不能摆超过一个棋子. 我们用跳跳棋来做一个简单的游戏:棋盘上有3颗棋子,分别在\(a\),\(b\),\(c\)这三个位置. 我们要通 ...

  10. Maven出错的问题处理

    1:使用Maven部署dubbox.jar包到maven本地仓库 由于Dubbox的jar包并没有部署到Maven的中央仓库中,大家在Maven的中央仓库中可以查找到Dubbo的最终版本是2.5.3 ...