您的位置:首页 > 编程语言 > C语言/C++

[转]C++中重载(overload)、覆盖(override)、隐藏(hide)的区别

2011-12-09 14:17 519 查看
原文地址:http://blog.csdn.net/Last_Time/archive/2009/11/15/4812520.aspx

1.重载(overload)
我们在C中常常需要两个或多个函数完成相同的任务,但是参数的数据类型不同比如large(int i ,int j),large(float i,float j)所以第一种解决方法就是large_int(),large_float(),但是这种方法比较笨拙,所以我们要找一种比较高明的方法,很幸运C++给我们提供了这样的一种机制—重载。我在同一个程序中可以使用同名的若干函数,但是当然要有限制啦,编译器可不比人聪明,我们要让编译器能唯一区分这些同名函数—函数签名。

  我们先看看如何区分两个同名函数:

    1.参数个数不同
  2.参数个数相同,但是至少有一双对应的函数的类型不同。
  例如:void large(int x,int y) 和void large(int x,int y,int z)不同
:void large(int x,int y)和 void large(float x,float y)也不同
  我们再来看下一种情况:
:void large(int x,int y)和int large(int x,int y)
  好像这两个函数可以放到一个程序中,可是事实上当你把这两个函数放到一个程序的时候编译器就糊涂啦,调用一个large(3,4)我到底该不该返回值呢?其实学习过汇编语言的人都知道:我们在调用一个子程序的时候只需要存储的是需要的参数,返回地址。跟们与返回值没关系。所以返回值是完全不能区分两个函数名相同的函数滴。
  我们上边说的就是函数签名的内涵了,现在说说函数签名的概念吧,没办法,有许多人总喜欢问什么是***,总喜欢把一些很抽象的东西概念化,既然不能改变就随波逐流吧.
  函数签名:函数的名称及其参数类型组合在一起就定义了一个唯一的特性,称为函数签名。程序中的每一个函数都必须有一个唯一的函数签名。当编写包含函数调用的语句时,编译器就使用该调用创建一个函数签名,再把它和函数原型或者定义中可用的函数签名集相比较,如果找到了匹配的函数签名就建立索调用的函数,如果没有找到匹配的函数签名,就检查转换参数类型后是否有匹配的函数签名。
  有了这个基础,我们再看看下一种情况:
:void output(int a,int b=5)和void output(int a)这两个函数能在一个程序中吗?
  相信你一定知道了,答案是“不能”。因为这两个函数的签名相同了,当我调用output(5)时候编译器就糊涂了,两个都可以啊!
  总结一下:重载构成的条件:函数的参数类型,参数个数不同,才能构成函数的重载。但是返回类型不同不能构成函数重载,还要注意函数带有默认参数的情况。

2.覆盖(override)
  在说覆盖之前我们老规矩还要介绍介绍这个机制推出的背景:
举个实际的例子,我们编写一个动物类:

#include <iostream>
using namespace std;

class Animal
{

public:
void speak()
{
cout<<"Animal speak"<<endl;
}
};

class Dog:public Animal
{

public:
void speak()
{
cout<<"Dog ::wangwang!"<<endl;
}

};

class Cat:public Animal
{

public:
void speak()
{
cout<<"Cat::miaomiao!"<<endl;
}

};

class Cattle:public Animal
{
public:
void speak()
{
cout<<"Cattle::MouMou!"<<endl;
}

};

int main()

{
Animal* animal;
Dog dog;
Cat cat;
Cattle cattle;

dog.speak();
cat.speak();
cattle.speak();

animal = &dog;
animal->speak();
animal = &cat;
animal->speak();
animal= &cattle;
animal->speak();

return 0;
}


  解释一下这个简单的程序。我定义了一个动物类,狗,猫和牛都继承这个类,并重写了这个speak这个方法,当我dog.speak(),cat.speak(),cattle.speakl()都没有问题,但是这样太麻烦了,于是我想用一个统一的代码来表示这个说的动作,于是我定义了一个指针animal 分别指向dog,cat ,cattle 但是结果并没有我想象的那样,结果如下:

Cat::miaomiao!
Cattle::MouMou!
Animal speak
Animal speak
Animal speak


  这可不是我们所希望的。解释一下出现这个不希望结果的原因:
  当进行animal = &cat的时候,c++编译器进行了类型转换,认为animal中保存的对象就是Animal的对象,当然调用的是Ainmal的speak了。于是聪明的人们就想出了一个解决的办法:编译时不确定具体的调用函数,等到运行时,根据对象的类型来调用时哪一个函数。这个解决问题的能力就是c++提供的多态特性。C++中多态特性是通过哪个关键字来定义的呢?相信你肯定知道答案:virtual. c++的编译器就是根据这个关键字来决定“是在编译时就确定调用哪个函数还是在执行时确定调用哪个函数呢”。编译时确定就称之为“早期绑定(early binding )"执行时确定称为"迟绑定(late binding).好啦,知道了这些知识我们动手改一改刚才的代码吧:其实就是在Animal类的speak方法上加一个关键字virtual. 结果绝对满足我们的预期:

Dog ::wangwang!
Cat::miaomiao!
Cattle::MouMou!
Dog ::wangwang!
Cat::miaomiao!
Cattle::MouMou!


  C++的多态性用一句话来概述:在基类的函数前加virtual关键字,在派生类重写该函数,运行时会根据对象的实际类型来调用相应的函数,如果对象是派生类就调用派生类的函数,如果是基类就调用基类的函数,与指针的类型无关,而是与指针实际指向的对象有关。
  再介绍一个很重要的概念:纯虚函数:纯虚函数是指明位不具体实现的虚成员函数,virtual void breathe() =0;这样就是一个纯虚函数。没有函数体,敖汉纯虚函数的类叫抽象类,如果想继承抽象类就必须要实现父类所有的纯虚函数,否则也要把这个函数在子类写成纯虚函数,这样派生类也成了抽象类,抽象类不能实例化对象。

  介绍了一大堆的知识相比你也烦了,但是当我介绍完前面的东西后“覆盖”也就呼之欲出了。
  “覆盖”:基类的虚函数A,有一个派生类重写了A,名称和参数列表与A完全相同,那么就成为函数的“覆盖”。
  看到了吧,所谓的覆盖就是我们前面介绍的speak().基类的speak是虚函数,那么派生类的speak无论是否有virtual关键字,都是虚函数,也就是说你的派生类的speak()前面可以加virtual也可以不加,效果都是一样的。
  总结一下覆盖的条件:
    (1)基类函数必须是虚函数(使用virtual关键字声明)
    (2)发生覆盖的函数要非别在派生类和基类中。
    (3)函数名称和参数列表必须完全相同(不同的话就会引出下一个概念哟)
  那么这次跟返回值有没有关系呢,我实验了一下,当基类有一个虚函数virtual void A(),如果那么派生类中0可能存在int A(),因为编译会报错,但是如果是int A(int x)就可以啦,不过就不是覆盖了。所以再一次验证了根返回值没关系,不要考虑返回值。

3.隐藏(hide)

  其实前边已经介绍了隐藏了,只不过没有明确说出来,所谓的隐藏就是指在派生类中具有与基类的同名函数(可不管参数列表是不是相同)从而在派生类中隐藏了基类的同名函数。也就是说在派生类调用的一定是派生类的函数,例如基类和派生类都有int A(),在派生类中一定是调用派生类的int A(),但是如果派生类中没有基类的int B()那么在派生类中调用B()时当然是调用基类的B()了,因为派生类中没有嘛,还有一种情况,就是如果基类与派生类的参数列表不同,那么无论基类的函数是不是虚函数基类的函数都会被隐藏。总结一下覆盖的发生条件:1.派生类的函数与基类的函数完全相同,但是基类函数不是虚函数,那么基类的函数将被隐藏。2.当派生类的函数与基类函数同名,但是具有不同的参数列表,那无论基类的函数是不是虚函数都会被隐藏。

  最后总结一下:发生在同一个类的同名函数不同参数列表:重载
    发生在基类和派生类中的函数,基类函数是虚函数,派生类函数与基类函数名称与参数列表完全相同:覆盖
    派生类函数与基类函数名称与参数列表完全相同,但是基类函数不是虚函数:隐藏
    派生类函数与基类函数名称相同但是参数列表不同,无论基类函数是不是virtual:隐藏。

  最后给出一个例子:

#include <iostream>
using namespace std;

class Base
{

public:
virtual void  xfn(int i)
{
cout<<"Base::xfn(int i)"<<endl;
}

void yfn(float f)
{
cout<<"Base::yfn(float f)"<<endl;
}
void zfn()
{
cout<<"Base::zfn()"<<endl;
}

};

class Derived :public Base
{
public:
void xfn(int i)//覆盖了xfn的函数
{
cout<<"Derived::xfn(int i)"<<endl;
}

void yfn(int c) //隐藏了基类yfn的函数
{
cout<<"Derived::yfn(int i)"<<endl;
}

void zfn() //隐藏了基类zfn的函数

{
cout<<"Drived::zfn()"<<endl;
}

};

int main()
{

Derived d;

Base* pB = &d;
Derived* pD = &d;

pB->xfn(5);
pD->xfn(5);
pB->yfn(3.14f);
pD->yfn(3.14f);

pB->zfn();
pD->zfn();

return 0;
}


  结果如下:

Derived::xfn(int i)
Derived::xfn(int i)
Base::yfn(float f)
Derived::yfn(int i)
Base::zfn()
Drived::zfn()
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐