C++中重载(overload)、覆盖(override)、隐藏
原创 2009年11月15日 12:20:00
 
 
重载(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)时候编译器就糊涂了,两个都可以啊!
总结一下:
重载构成的条件:函数的参数类型,参数个数不同,才能构成函数的重载。但是返回类型不同不能构成函数重载,还要注意函数带有默认参数的情况。
 
 

覆盖(override)

在说覆盖之前我们老规矩还要介绍介绍这个机制推出的背景:
举个实际的例子,我们编写一个动物类:
 
Dog ::wangwang!
Cat::miaomiao!
Cattle::MouMou!
Animal speak
Animal speak
Animal speak
解释一下这个简单的程序。我定义了一个动物类,狗,猫和牛都继承这个类,并重写了这个speak这个方法,当我dog.speak(),cat.speak(),cattle.speakl()都没有问题,但是这样太麻烦了,于是我想用一个统一的代码来表示这个说的动作,于是我定义了一个指针animal 分别指向dog,cat ,cattle 但是结果并没有我想象的那样,结果如下:
这可不是我们所希望的。解释一下出现这个不希望结果的原因:
#include
using namespace std;
class Animal
{
public:
    void speak()
    {
        cout<<"Animal speak"<
    }
};
class Dog:public Animal
{
public:
        void speak()
    {
        cout<<"Dog ::wangwang!"<
    }
    
};
class Cat:public Animal
{
public:
        void speak()
    {
        cout<<"Cat::miaomiao!"<
    }
                    
        
};
class Cattle:public Animal
{
public:
    void speak()
    {
        cout<<"Cattle::MouMou!"<
    }
                    
        
};
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;
}
当进行animal = &cat的时候,c++编译器进行了类型转换,认为animal中保存的对象就是Animal的对象,当然调用的是Ainmalspeak了。
于是聪明的人们就想出了一个解决的办法:编译时不确定具体的调用函数,等到运行时,根据对象的类型来调用时哪一个函数。
这个解决问题的能力就是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.隐藏
 
其实前边已经介绍了隐藏了,只不过没有明确说出来,所谓的隐藏就是指在派生类中具有与基类的同名函数(可不管参数列表是不是相同)从而在派生类中隐藏了基类的同名函数。也就是说在派生类调用的一定是派生类的函数,例如基类和派生类都有int A(),在派生类中一定是调用派生类的int A(),但是如果派生类中没有基类的int B()那么在派生类中调用B()时当然是调用基类的B()了,因为派生类中没有嘛,还有一种情况,就是如果基类与派生类的参数列表不同,那么无论基类的函数是不是虚函数基类的函数都会被隐藏。总结一下覆盖的发生条件:1.派生类的函数与基类的函数完全相同,但是基类函数不是虚函数,那么基类的函数将被隐藏。2.当派生类的函数与基类函数同名,但是具有不同的参数列表,那无论基类的函数是不是虚函数都会被隐藏。
 
最后总结一下:发生在同一个类的同名函数,不同参数列表:重载
                     发生在基类和派生类中的函数,基类函数是虚函数,派生类函数与基类函数名称与参数列表完全相同:覆盖
                     派生类函数与基类函数 名称与参数列表 完全相同,但是基类函数不是虚函数:隐藏
                     派生类函数与基类函数名称相同 但是参数列表不同,无论基类函数是不是virtual:隐藏。
 
最后给出一个例子:
 #include
using namespace std;
class Base
{
public:
         virtual void xfn(int i)
        {
            cout<<"Base::xfn(int i)"<
        }
        void yfn(float f)
        {
            cout<<"Base::yfn(float f)"<
        }
        void zfn()
        {
            cout<<"Base::zfn()"<
        }
};
class Derived :public Base
{
public:
        
     void xfn(int i)//覆盖了xfn的函数
        {
         cout<<"Derived::xfn(int i)"<
        }
        
        void yfn(int c) //隐藏了基类yfn的函数
        {
         cout<<"Derived::yfn(int i)"<
        }
        void zfn() //隐藏了基类zfn的函数
        {
         cout<<"Drived::zfn()"<
        }
};
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()