[转]C++中重载(overload)、覆盖(override)、隐藏(hide)的区别
原文地址: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)
在说覆盖之前我们老规矩还要介绍介绍这个机制推出的背景:
举个实际的例子,我们编写一个动物类:
1 #include <iostream>
2 using namespace std;
3
4 class Animal
5 {
6
7 public:
8 void speak()
9 {
10 cout<<"Animal speak"<<endl;
11 }
12 };
13
14 class Dog:public Animal
15 {
16
17 public:
18 void speak()
19 {
20 cout<<"Dog ::wangwang!"<<endl;
21 }
22
23 };
24
25 class Cat:public Animal
26 {
27
28 public:
29 void speak()
30 {
31 cout<<"Cat::miaomiao!"<<endl;
32 }
33
34
35 };
36
37 class Cattle:public Animal
38 {
39 public:
40 void speak()
41 {
42 cout<<"Cattle::MouMou!"<<endl;
43 }
44
45
46 };
47
48 int main()
49
50 {
51 Animal* animal;
52 Dog dog;
53 Cat cat;
54 Cattle cattle;
55
56 dog.speak();
57 cat.speak();
58 cattle.speak();
59
60 animal = &dog;
61 animal->speak();
62 animal = &cat;
63 animal->speak();
64 animal= &cattle;
65 animal->speak();
66
67 return 0;
68 }
解释一下这个简单的程序。我定义了一个动物类,狗,猫和牛都继承这个类,并重写了这个speak这个方法,当我dog.speak(),cat.speak(),cattle.speakl()都没有问题,但是这样太麻烦了,于是我想用一个统一的代码来表示这个说的动作,于是我定义了一个指针animal 分别指向dog,cat ,cattle 但是结果并没有我想象的那样,结果如下:
1 Cat::miaomiao!
2 Cattle::MouMou!
3 Animal speak
4 Animal speak
5 Animal speak
这可不是我们所希望的。解释一下出现这个不希望结果的原因:
当进行animal = &cat的时候,c++编译器进行了类型转换,认为animal中保存的对象就是Animal的对象,当然调用的是Ainmal的speak了。于是聪明的人们就想出了一个解决的办法:编译时不确定具体的调用函数,等到运行时,根据对象的类型来调用时哪一个函数。这个解决问题的能力就是c++提供的多态特性。C++中多态特性是通过哪个关键字来定义的呢?相信你肯定知道答案:virtual. c++的编译器就是根据这个关键字来决定“是在编译时就确定调用哪个函数还是在执行时确定调用哪个函数呢”。编译时确定就称之为“早期绑定(early binding )"执行时确定称为"迟绑定(late binding).好啦,知道了这些知识我们动手改一改刚才的代码吧:其实就是在Animal类的speak方法上加一个关键字virtual. 结果绝对满足我们的预期:
1 Dog ::wangwang!
2 Cat::miaomiao!
3 Cattle::MouMou!
4 Dog ::wangwang!
5 Cat::miaomiao!
6 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:隐藏。
最后给出一个例子:
1 #include <iostream>
2 using namespace std;
3
4 class Base
5 {
6
7 public:
8 virtual void xfn(int i)
9 {
10 cout<<"Base::xfn(int i)"<<endl;
11 }
12
13 void yfn(float f)
14 {
15 cout<<"Base::yfn(float f)"<<endl;
16 }
17 void zfn()
18 {
19 cout<<"Base::zfn()"<<endl;
20 }
21
22 };
23
24 class Derived :public Base
25 {
26 public:
27 void xfn(int i)//覆盖了xfn的函数
28 {
29 cout<<"Derived::xfn(int i)"<<endl;
30 }
31
32 void yfn(int c) //隐藏了基类yfn的函数
33 {
34 cout<<"Derived::yfn(int i)"<<endl;
35 }
36
37 void zfn() //隐藏了基类zfn的函数
38
39 {
40 cout<<"Drived::zfn()"<<endl;
41 }
42
43 };
44
45
46 int main()
47 {
48
49 Derived d;
50
51 Base* pB = &d;
52 Derived* pD = &d;
53
54 pB->xfn(5);
55 pD->xfn(5);
56 pB->yfn(3.14f);
57 pD->yfn(3.14f);
58
59 pB->zfn();
60 pD->zfn();
61
62 return 0;
63 }
结果如下:
1 Derived::xfn(int i)
2 Derived::xfn(int i)
3 Base::yfn(float f)
4 Derived::yfn(int i)
5 Base::zfn()
6 Drived::zfn()