4.5 C++重载、覆盖和遮蔽
参考:http://www.weixueyuan.net/view/6375.html
总结:
函数签名包括函数名和函数参数的个数、顺序以及参数数据类型。
需要注意的是函数签名并不包含函数返回值部分,如果两个函数仅仅只有函数返回值不同,那么系统是无法区分这两个函数的,此时编译器会提示语法错误。
函数重载是指两个函数具有相同的函数名,但是函数参数个数或参数类型不同。函数重载多发生在顶层函数之间或者同一个类中,函数重载不需要构成继承关系。
函数重载是编译期绑定,它并不是多态。
覆盖构成条件和多态构成条件是相同的,覆盖是一种函数间的表现关系,而多态描述的是函数的一种性质,二者所描述的其实是同一种语法现象。
覆盖首先要求有继承关系,其次是要求构成继承关系的两个类中必须具有相同函数签名的成员函数,并且这两个成员函数必须是虚成员函数,具备这两个条件后,派生类中的虚成员函数则会覆盖基类中的同名的虚成员函数。如果我们通过基类指针或引用来调用虚成员函数,则会形成多态。
函数覆盖属于运行期绑定,但是要注意如果函数不是虚函数,则无论采用什么方法调用函数均为编译期绑定。
函数遮蔽同样要求构成继承关系,构成继承关系的两个类中具有相同函数名的函数,如果这两个函数不够成覆盖关系,则就构成了遮蔽关系。(注意不是相同函数签名,只需要相同函数名就可以了)
覆盖要求的是函数签名相同,而遮蔽只需要函数名相同。
一般来讲,函数名相同通常会用在以下几种情况中:
- 顶层函数的函数重载。对于程序设计人员而言,实现功能相同但所处理数据类型不同的函数时,采用函数重载的方式将会带来极大的方便。例如设计一个求绝对值函数,针对整型和double类型各设及一个abs函数,调用时而无需关注参数类型,这样的设计是很方便的。
- 类中的成员函数的重载,这种函数重载和顶层函数重载同样能给我们的程序带来方便。
- 类中的构造函数重载,设计多个构造函数,用于不同的初始化对象方式。
- 在继承层次中为了使用多态特性而采用相同函数签名。
除此之外函数名相同还会导致继承层次中的函数遮蔽,而函数遮蔽这一特性通常会使得程序难以理解,因此建议谨慎使用函数遮蔽机制。
------------------------------------
多态函数是指在运行期才将函数入口地址与函数名绑定的函数,仅有虚函数才是多态。但是除了虚函数以外,重载和遮蔽同样具有函数名相同的特征,在此做一下区分。为了说明方便,我们引入函数签名这一概念。函数签名包括函数名和函数参数的个数、顺序以及参数数据类型。
例1:
void f( )
void g( )
void f(int)
例2:
void f( int)
void f(double)
例3:
void f(double, int)
void f(int, double)
为了理解函数签名的含义,我们先来看一下上面的三个例子。例1中函数f()和函数g()函数名不同,因此这两个函数的函数签名不同,f()函数和f(int)函数一个有参数,一个没有参数,函数签名同样不同,g()函数和f(int)函数函数名不同并且函数参数个数也不同,因此这两个函数的函数签名也是不相同的。例2中两个函数函数名相同,函数参数个数相同,但是函数参数的类型不同,因此这两个函数的函数签名也不是相同的。例3中的两个函数,函数名相同,函数参数个数相同,函数参数类型也是相同的,都是一个double类型和一个int类型的,只不过函数参数的顺序是不相同,如此一来这两个函数的函数签名同样是不相同的。
需要注意的是函数签名并不包含函数返回值部分,如果两个函数仅仅只有函数返回值不同,那么系统是无法区分这两个函数的,此时编译器会提示语法错误。
例4:
int f(int, double)
void f(int, double)
在本例中,两个函数的函数名相同,函数参数个数相同,函数参数类型相同,函数参数顺序相同,如此一来两个函数的函数签名是相同的。但是这两个函数的返回值不同,仅凭函数返回值,编译器无法区分这两个函数,编译器提示语法错误。
了解了函数签名的含义之后我们再来看一下重载、覆盖和遮蔽。
1) 重载
函数重载是指两个函数具有相同的函数名,但是函数参数个数或参数类型不同。函数重载多发生在顶层函数之间或者同一个类中,函数重载不需要构成继承关系。
例5:
class base
{
public :
base();
base(int a);
base(int a, int b);
base( base &);
int fun(int a);
int fun(double a);
int fun(int a, int b);
private:
int x;
int y;
}; int g(int a);
int g(double a);
int g(int a, int b);
在本例中,我们列出了几种函数重载的情形。首先是函数的构造函数重载,我们在类中声明了四个构造函数,这四个函数构成重载的关系,前面三个函数之间只是函数参数数目不同,第四个构造函数为拷贝构造函数,该函数与前面的默认构造函数和两个带参构造函数参数类型不同。类中的成员函数同样可以进行重载,如本例中base类的三个fun函数。这两种情况是类内部的函数重载,在类外部顶层函数也同样能够成函数重载关系,如本例中的g函数,这三个函数都是顶层函数,由于函数名相同,但是函数参数不同,构成函数重载关系。
函数重载是编译期绑定,它并不是多态。
2) 覆盖
覆盖构成条件和多态构成条件是相同的,覆盖是一种函数间的表现关系,而多态描述的是函数的一种性质,二者所描述的其实是同一种语法现象。
覆盖首先要求有继承关系,其次是要求构成继承关系的两个类中必须具有相同函数签名的成员函数,并且这两个成员函数必须是虚成员函数,具备这两个条件后,派生类中的虚成员函数则会覆盖基类中的同名的虚成员函数。如果我们通过基类指针或引用来调用虚成员函数,则会形成多态。
例6:
#include<iostream>
using namespace std; class base
{
public :
virtual void vir1(){}
virtual void vir2(){}
}; class derived : public base
{
public:
void vir1(){}
void vir2(){}
}; int main()
{
base * p;
p = new derived;
p->vir1();
p->vir2();
delete p;
return ;
}
本例是一个非常简单的多态的示例程序,base类和derived类构成继承关系,在这两个类中成员函数vir1和vir2同名,并且这两个同名函数都被声明为了虚函数。如此一来则构成了函数覆盖,派生类中的vir1函数覆盖了基类中的vir1函数,派生类中的vir2函数覆盖了基类中的vir2函数。在主函数中通过基类指针调用vir1和vir2虚函数,构成多态,这两个函数的运行为运行期绑定。
函数覆盖属于运行期绑定,但是要注意如果函数不是虚函数,则无论采用什么方法调用函数均为编译期绑定。如果我们将例6中的基类中的两个virtual关键字去掉,则主函数中调用vir1和vir2函数属于编译期绑定,无论p指向的是派生类对象或者是基类对象,执行的都将会是基类的vir1和vir2函数。
3) 遮蔽
函数遮蔽同样要求构成继承关系,构成继承关系的两个类中具有相同函数名的函数,如果这两个函数不够成覆盖关系,则就构成了遮蔽关系。遮蔽理解起来很简单,只要派生类与基类中具有相同函数名(注意不是相同函数签名,只需要相同函数名就可以了)并且不构成覆盖关系即为遮蔽。
遮蔽可以分为两种情况,一种是非虚函数之间,另一种则是虚函数之间。我们通过程序示例来分别介绍这两种遮蔽情况。
例7:
#include<iostream>
using namespace std; class base
{
public :
void vir1(){cout<<"base vir1"<<endl;}
void vir2(){cout<<"base vir2"<<endl;}
}; class derived : public base
{
public:
void vir1(){cout<<"derived vir1"<<endl;}
void vir2(int){cout<<"derived vir2"<<endl;}
}; int main()
{
base * p;
p = new derived;
p->vir1();
p->vir2();
delete p;
derived d;
d.vir1();
d.vir2();
d.base::vir1();
d.base::vir2();
return ;
}
在本例中没有虚函数,base类和derived类构成继承关系,因为构成继承关系的两个类中有同名函数,因此构成了函数遮蔽。派生类中的vir1函数遮蔽了基类中的vir1函数,派生类中的vir2函数遮蔽了基类中的vir1函数。需要注意的是虽然派生类中的vir2函数和基类中的vir2函数的函数签名不同,但是只需要函数名相同就构成函数遮蔽。我们接着来分析一下主函数,主函数中我们先是定义了基类类型的指针,指针指向的是基类对象,然后通过指针调用函数vir1和vir2,这个时候因为并不构成多态,因此调用的还是基类的vir1和vir2函数。之后定义了一个派生类对象d,通过该对象调用vir1和vir2函数,因为派生类中的vir1和vir2遮蔽了基类中的vir1和vir2函数,因此直接调用的将会是派生类中的vir1和vir2函数。如果需要通过派生类对象调用被遮蔽的基类中的函数,则需要通过域解析操作符来处理,在本例的最后d.base::vir1();和d.base::vir2()就是这么做的。这个程序的最终运行结果如下:
base vir1
base vir2
derived vir1
derived vir2
base vir1
base vir2
如果构成继承关系的两个类中包含同名的虚函数,则情况非常复杂,当然要判断还是非常简单,还是那个原则:如果没有构成覆盖则为遮蔽。覆盖要求的是函数签名相同,而遮蔽只需要函数名相同。
例8:
#include<iostream>
using namespace std; class base
{
public :
virtual void vir1(){cout<<"base vir1"<<endl;}
virtual void vir2(){cout<<"base vir2"<<endl;}
}; class derived : public base
{
public:
virtual void vir1(){cout<<"derived vir1"<<endl;}
virtual void vir2(int){cout<<"derived vir2"<<endl;}
}; int main()
{
base * p;
p = new derived;
p->vir1();
p->vir2();
delete p;
derived d;
d.vir1();
d.vir2();
d.base::vir1();
d.base::vir2();
return ;
}
在这个程序中,定义了两个类,base类和derived类,这两个类构成继承关系,派生类和基类中包含同名的函数,并且同名的函数均为虚函数。针对这两个同名函数,我们一个一个来分析一下,首先来看一下vir1,基类和派生类中的vir1函数的函数签名是相同的,而且又是虚函数,构成了函数覆盖关系。再来看一下vir2函数,基类中的vir2函数和派生类中的vir2函数函数名相同,但函数参数不同,则它们的函数签名不同,因此派生类中的vir2函数和基类中的vir1函数不构成函数覆盖,既然函数名相同,那么可以构成函数遮蔽。
接着我们同样来看一下主函数,在主函数中,我们定义了一个基类类型的指针,指针指向派生类对象,之后通过该指针分别调用vir1和vir2函数。由于vir1是构成函数覆盖,因此通过基类指针调用vir1构成多态,由于p指针指向的是派生类对象,故调用的vir1函数是派生类中的vir1函数。派生类中的vir2函数和基类中的vir2函数只构成函数遮蔽,因此通过基类类型指针调用vir2函数并不会形成多态,最终调用的是基类中的vir2函数。之后定义了派生类对象d,通过派生类对象d调用的函数只能是派生类中的函数,当然也包括从基类中继承来的函数。d.vir1()和d.vir2(5)这两个函数调用语句调用的都是派生类中新增的成员函数,派生类中的vir1函数虽然和基类中的vir1函数构成覆盖关系,但是由于没有通过基类指针或引用来调用,因此也没有构成多态,如此一来,如果需要通过对象来调用从基类中继承过来的vir1函数,同样是需要域解析操作符。派生类中的vir2函数和基类中vir2函数构成遮蔽,因此通过对象和成员选择符调用的仍是派生类中新增的vir2函数,如果想调用基类中的vir2函数,则需要通过域解析操作符。例8程序运行结果如下:
derived vir1
base vir2
derived vir1
derived vir2
base vir1
base vir2
以上总结了函数名相同的所有情况,函数名相同利用的好可以为程序设计带来较大的便利,使用的不好则容易误导程序设计人员。一般来讲,函数名相同通常会用在以下几种情况中:
- 顶层函数的函数重载。对于程序设计人员而言,实现功能相同但所处理数据类型不同的函数时,采用函数重载的方式将会带来极大的方便。例如设计一个求绝对值函数,针对整型和double类型各设及一个abs函数,调用时而无需关注参数类型,这样的设计是很方便的。
- 类中的成员函数的重载,这种函数重载和顶层函数重载同样能给我们的程序带来方便。
- 类中的构造函数重载,设计多个构造函数,用于不同的初始化对象方式。
- 在继承层次中为了使用多态特性而采用相同函数签名。
除此之外函数名相同还会导致继承层次中的函数遮蔽,而函数遮蔽这一特性通常会使得程序难以理解,因此建议谨慎使用函数遮蔽机制。
4.5 C++重载、覆盖和遮蔽的更多相关文章
- C++重载覆盖隐藏
写一个程序,各写出重载覆盖 1 // // main.cpp // 2013-7-17作业2 // // Created by 丁小未 on 13-7-17. // Copyright (c) 201 ...
- C++中的重载隐藏覆盖&&JAVA中的重载覆盖&&多态
class 类继承默认是private, struct 默认继承是public C++中的隐藏: 只要派生类中出现和基类一样的函数名,基类中的函数就会被派生类中的函数给隐藏(如果派生类和基类中的函数名 ...
- c/c++:重载 覆盖 隐藏 overload override overwrite
http://www.cnblogs.com/qlee/archive/2011/07/04/2097055.html 成员函数的重载.覆盖与隐藏成员函数的重载.覆盖(override)与隐藏很容易混 ...
- c++ 继承 虚函数与多态性 重载 覆盖 隐藏
http://blog.csdn.net/lushujun2011/article/details/6827555 2011.9.27 1) 定义一个对象时,就调用了构造函数.如果一个类中没有定义任何 ...
- c++中 重载 覆盖 隐藏的区别 附加 mutable笔记
成员函数被重载的特征有: 1) 相同的范围(在同一个类中): //2) 函数名字相同: 3) 参数不同: 4) virtual关键字可有可无. 覆盖的特征有: 1) 不同的范围(分别位于派生类与基类) ...
- 一道Java程序输出题(继承-重载-覆盖-向上转型的问题)
class A { public String show(D obj) { // func1 return ("A and D"); } public String show(A ...
- C++之重载覆盖和隐藏
继承体系下同名成员函数的三种关系 重载 在同一作用域内 函数名相同,参数列表不同(分三种情况:参数个数不同,参数类型不同,参数个数和类型都不同) 返回值类型可以相同也可以不同 重写(覆盖) 在不同作用 ...
- Head First Java & 重载 覆盖
- C#基础(三)—重载与覆盖
所谓重载指的是同一个类中有两个或多个名字相同但是参数不同的方法.重载,必然发生在一个类中,函数名相同,参数类型或者顺序不同构成重载,与返回类型无关. override:过载也称重写是指子类对父类中虚函 ...
随机推荐
- 通过cookies跳过验证码登陆页面,直接访问网站的其它URL
我每次手动访问去NN网的一家酒店,就不需要登陆,一旦我用脚本打开就会让我登陆,而登陆页面又有验证码,不想识别验证码,所以就想:“通过cookies跳过验证码登陆页面,直接访问网站的其它URL” 转 ...
- Windows Live Wirter
安装: 下载: Windows Live Writer (QQ 里) windows live writer 日志服务器发生问题 更新账户信息 从字面"editPost"我们不难看 ...
- 概率分布之间的推导关系 | Univariate Distribution Relationships
Univariate Distribution Relationships APPL: A Probability Programming Language Maplesoft- Software f ...
- 如何理解机器学习/统计学中的各种范数norm | L1 | L2 | 使用哪种regularization方法?
参考: L1 Norm Regularization and Sparsity Explained for Dummies 专为小白解释的文章,文笔十分之幽默 why does a small L1 ...
- 基于C# winform实现图片流存储到文件
本文所述实例实现将一张图片上传到指定的文件夹,然后在窗体上的PictrueBox控件中显示出来. 具体功能代码如下: private void btnUpload_Click(object sende ...
- 51Nod 1810 连续区间
https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1810 题目给出一个1~n的排列,问有多少连续区间.连续区间的定义为区间 ...
- 网络基础之 并发编程之进程,多路复用,multiprocess模块
并发 1. 背景知识 2. 什么是进程 3. 进程调度 4. 并发与并行 5 同步\异步\阻塞\非阻塞(重点) 6.multiprocess模块 7.僵尸进程与孤儿进程 1.背景知识 一操作系统的作用 ...
- php面向对象比较
在PHP中有 = 赋值符号.== 等于符号 和 === 全等于符号, 这些符号代表什么意思? (1).=是赋值符 (2).使用 == 符号比较两个对象 ,比较的仅仅是两个对象的内容是否一致. (3). ...
- SpringBoot项目Shiro的实现(二)
在看此小节前,您可能需要先看:http://www.cnblogs.com/conswin/p/7478557.html 紧接上一篇,在上一篇我们简单实现了一个Springboot的小程序,但我们发现 ...
- 函数使用四:采购发票MIRO BAPI_INCOMINGINVOICE_CREATE
1. 业务处理(transaction)字段选择: 创建后续借记(subsequent debit) ItemData DE_CRE_IN ...