参考: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++重载、覆盖和遮蔽的更多相关文章

  1. C++重载覆盖隐藏

    写一个程序,各写出重载覆盖 1 // // main.cpp // 2013-7-17作业2 // // Created by 丁小未 on 13-7-17. // Copyright (c) 201 ...

  2. C++中的重载隐藏覆盖&&JAVA中的重载覆盖&&多态

    class 类继承默认是private, struct 默认继承是public C++中的隐藏: 只要派生类中出现和基类一样的函数名,基类中的函数就会被派生类中的函数给隐藏(如果派生类和基类中的函数名 ...

  3. c/c++:重载 覆盖 隐藏 overload override overwrite

    http://www.cnblogs.com/qlee/archive/2011/07/04/2097055.html 成员函数的重载.覆盖与隐藏成员函数的重载.覆盖(override)与隐藏很容易混 ...

  4. c++ 继承 虚函数与多态性 重载 覆盖 隐藏

    http://blog.csdn.net/lushujun2011/article/details/6827555 2011.9.27 1) 定义一个对象时,就调用了构造函数.如果一个类中没有定义任何 ...

  5. c++中 重载 覆盖 隐藏的区别 附加 mutable笔记

    成员函数被重载的特征有: 1) 相同的范围(在同一个类中): //2) 函数名字相同: 3) 参数不同: 4) virtual关键字可有可无. 覆盖的特征有: 1) 不同的范围(分别位于派生类与基类) ...

  6. 一道Java程序输出题(继承-重载-覆盖-向上转型的问题)

    class A { public String show(D obj) { // func1 return ("A and D"); } public String show(A ...

  7. C++之重载覆盖和隐藏

    继承体系下同名成员函数的三种关系 重载 在同一作用域内 函数名相同,参数列表不同(分三种情况:参数个数不同,参数类型不同,参数个数和类型都不同) 返回值类型可以相同也可以不同 重写(覆盖) 在不同作用 ...

  8. Head First Java & 重载 覆盖

  9. C#基础(三)—重载与覆盖

    所谓重载指的是同一个类中有两个或多个名字相同但是参数不同的方法.重载,必然发生在一个类中,函数名相同,参数类型或者顺序不同构成重载,与返回类型无关. override:过载也称重写是指子类对父类中虚函 ...

随机推荐

  1. (转) 6 ways of mean-centering data in R

    6 ways of mean-centering data in R 怎么scale我们的数据? 还是要看我们自己数据的特征. 如何找到我们数据的中心? Cluster analysis with K ...

  2. [LintCode] Binary Tree Level Order Traversal(二叉树的层次遍历)

    描述 给出一棵二叉树,返回其节点值的层次遍历(逐层从左往右访问) 样例 给一棵二叉树 {3,9,20,#,#,15,7} : 3 / \ 9 20 / \ 15 7 返回他的分层遍历结果: [ [3] ...

  3. P3784 [SDOI2017]遗忘的集合

    非常神仙的一道题! 题意:给出某n个数字跑完全背包m容量的dp数组,求满足要求的字典序最小的n个元素,不知道n是多少. 首先考虑付公主的背包这个题. 对dp数组求一个ln,设它为F. 已知 e^(G1 ...

  4. S-Nim HDU - 1536

    #include<iostream> #include<cstdio> #include<cstring> #include<vector> using ...

  5. sscanf(),sscanf_s()的相关用法

    #include<stdio.h> 定义函数 int sscanf (const char *str,const char * format,........); 函数说明  sscanf ...

  6. object对象转string字符串

    var obj = {}; obj=new Array; obj.name='小王'; obj.sex='男'; var str=JSON.string(obj)

  7. fastjson如何指定字段不序列化

    fastjson是一款由阿里巴巴提供的性能出色的json序列化与反序列化库,而且使用很方便,我们可以使用JSON.toJSONString(object)将一个对象序列化为json格式,但是如果我们不 ...

  8. python中RabbitMQ的使用(路由键)

    1.简介 当我们希望每个接收端接收各自希望的消息时,我们可以使用路由键,此时交换机的类型为direct. 2.工作原理 每个接收端的消息队列在绑定交换机的时候,可以设定相应的路由键. 发送端通过交换机 ...

  9. 【LeetCode】Valid Parentheses合法括号

    给定一个仅包含 '('.')'.'{'.'}'.'['.']'的字符串,确定输入的字符串是否合法. e.g. "()"."()[]{}"."[()]( ...

  10. MYSQL--三大范式

    MYSQL--三大范式 范式简介: 设计关系数据库时,遵从不同的规范要求,设计出合理的关系型数据库,这些不同的规范要求被称为不同的范式,各种范式呈递次规范,越高的范式数据库冗余越小.目前关系数据库有六 ...