C++中重载、重写(覆盖)和隐藏的区别实例分析

这篇文章主要介绍了C++中重载、重写(覆盖)和隐藏的区别,是C++面向对象程序设计非常重要的概念,需要的朋友可以参考下

函数重载:

在C++程序中,可以将语义、功能相似的几个函数用同一个名字表示,即函数重载。

重载的实现:

几个同名的重载函数仍然是不同的函数,它们是如何区分的呢?我们自然想到函数接口的两个要素:参数与返回值。如果同名函数的参数不同(包括类型、顺序不同),那么容易区别出它们是不同的函数。

重载与覆盖成员函数被重载的特征:

(1)相同的范围(在同一个类中);

(2)函数名字相同;

(3)参数不同;

(4)virtual 关键字可有可无。

覆盖是指派生类函数覆盖基类函数,特征是:

(1)不同的范围(分别位于派生类与基类);

(2)函数名字相同;

(3)参数相同;

(4)基类函数必须有virtual 关键字。

隐藏规则:本来仅仅区别重载与覆盖并不算困难,但是C++的隐藏规则使问题复杂性增加了许多。这里“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:

(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。

(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。

本文实例讲述了C++中重载、重写(覆盖)和隐藏的区别,对于C++面向对象程序设计来说是非常重要的概念。具体分析如下:

1.重载:重载从overload翻译过来,是指同一可访问区内被声明的几个具有不同参数列(参数的类型,个数,顺序不同)的同名函数,根据参数列表确定调用哪个函数,重载不关心函数返回类型

示例代码如下:

?
1
2
3
4
5
6
7
8
class A{
public :
   void test( int i);
   void test( double i);
   void test( int i, double j);
   void test( double i, int j);
   int test( int i);         //错误,非重载
};

前四个互为重载函数,最后一个和第一个不是重载函数。

2.隐藏:隐藏是指派生类的函数屏蔽了与其同名的基类函数。注意只要同名函数,不管参数列表是否相同,基类函数都会被隐藏

实例代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include
using namespace std;
class A{
public :
   void fun1( int i, int j){
     cout << "A::fun1() : " << i << " " << j << endl;
   }
 
};
class B : public A{
public :
     //隐藏
   void fun1( double i){
     cout << "B::fun1() : " << i << endl;
   }
};
int main(){
   B b;
     b.fun1(5);          //调用B类中的函数
   b.fun1(1, 2);        //出错,因为基类函数被隐藏
   system ( "pause" );
   return 0;
}   

3.重写:重写翻译自override,也翻译成覆盖(更好一点),是指派生类中存在重新定义的函数。其函数名,参数列表,返回值类型,所有都必须同基类中被重写的函数一致。只有函数体不同(花括号内),派生类调用时会调用派生类的重写函数,不会调用被重写函数。重写的基类中被重写的函数必须有virtual修饰

实例代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include
using namespace std;
class A{
public :
   virtual void fun3( int i){
     cout << "A::fun3() : " << i << endl;
   }
 
};
class B : public A{
public :
     //重写
   virtual void fun3( double i){
     cout << "B::fun3() : " << i << endl;
   }
};
int main(){
     A a;
   B b;
   A * pa = &a;
   pa->fun3(3);
   pa = &b;
   pa->fun3(5);
   system ( "pause" );
   return 0;
}

上面为虚函数实现多态的代码,不明白的先看虚函数实现多态的原理。

重载和重写的区别:

(1)范围区别:重写和被重写的函数在不同的类中,重载和被重载的函数在同一类中

(2)参数区别:重写与被重写的函数参数列表一定相同,重载和被重载的函数参数列表一定不同

(3)virtual的区别:重写的基类必须要有virtual修饰,重载函数和被重载函数可以被virtual修饰,也可以没有

隐藏和重写,重载的区别:

(1)与重载范围不同:隐藏函数和被隐藏函数在不同类中

(2)参数的区别:隐藏函数和被隐藏函数参数列表可以相同,也可以不同,但函数名一定同;当参数不同时,无论基类中的函数是否被virtual修饰,基类函数都是被隐藏,而不是被重写

调试运行如下代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include
using namespace std;
class A{
public :
   void fun1( int i, int j){
     cout << "A::fun1() : " << i << " " << j << endl;
   }
   void fun2( int i){
     cout << "A::fun2() : " << i << endl;
   }
   virtual void fun3( int i){
     cout << "A::fun3(int) : " << i << endl;
   }
};
class B : public A{
public :
     //隐藏
   void fun1( double i){
     cout << "B::fun1() : " << i << endl;
   }
     //重写
   void fun3( int i){
     cout << "B::fun3(int) : " << i << endl;
   }
     //隐藏
   void fun3( double i){
     cout << "B::fun3(double) : " << i << endl;
   }
};
int main(){
   B b;
   A * pa = &b;
   B * pb = &b;
   pa->fun3(3);        //重写,多态性,调用B的函数
   b.fun3(10);         //隐藏,调用B的函数
   pb->fun3(20);       //隐藏,调用B的函数
   system ( "pause" );
   return 0;
}

输出结果为:

?
1
2
3
4
B::fun3( int ) : 3
B::fun3( int ) : 10
B::fun3( int ) : 20
请按任意键继续. . .

希望本文所述对大家C++面向对象程序设计有所帮助。

•概念

一、重载(overload)
指函数名相同,但是它的参数表列个数或顺序,类型不同。但是不能靠返回类型来判断。
(1)相同的范围(在同一个作用域中) ;
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。
(5)返回值可以不同;

二、重写(也称为覆盖 override)
是指派生类重新定义基类的虚函数,特征是:
(1)不在同一个作用域(分别位于派生类与基类) ;
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有 virtual 关键字,不能有 static 。
(5)返回值相同(或是协变),否则报错;<—-协变这个概念我也是第一次才知道…

(6)重写函数的访问修饰符可以不同。尽管 virtual 是 private 的,派生类中重写改写为 public,protected 也是可以的

三、重定义(也成隐藏)
(1)不在同一个作用域(分别位于派生类与基类) ;
(2)函数名字相同;
(3)返回值可以不同;
(4)参数不同。此时,不论有无 virtual 关键字,基类的函数将被隐藏(注意别与重载以及覆盖混淆) 。
(5)参数相同,但是基类函数没有 virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆) 。

•例子

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include
using namespace std;
class SParent
{
public :
   SParent( ){};
   SParent( const SParent &p )
   {
     cout << "parent copy construct" << endl;
   }
   int add( int a, int b )
   {
     cout << "parent int add" << endl;
     return a + b;
   }
   double add( double a, double b )
   {
     cout << "parent double add" << endl;
     return a + b;
   }
   virtual int dec( int a, int b )
   {
     cout << "parent int dec" << endl;
     return a - b;
   }
};
class SChild : public SParent
{
public :
   //using SParent::add;
   float add( float a, float b )
   {
     cout << "child float add" << endl;
     return a + b;
   }
   int dec( int a, int b)
   {
     cout << "child int dec" << endl;
     return a - b;
   }
};
int main()
{
   /* 测试重载 */
   SParent parent;
   parent.add( 3,5 );
   parent.add( ( double )3,( double )5 );
   cout << endl;
   /* 测试覆盖 */
   SChild *pchild = (SChild *) new SParent(); /* 基类强转为子类...危险...,用dynamic_cast转换也不行 */
   pchild->dec( 10,3 );
   SParent *pparent = new SChild();
   pparent->dec( 11,3 );
   cout << endl;
   /* 测试隐藏 */
   SChild child;
   child.add( ( int )3,( int )5 );
   cout << endl;
   /* 测试函数表 */
   ((SParent *)NULL)->add( 4,6 );
   ((SChild *)NULL)->add( 4,6 );
   int a = 0;
   ((SChild *)&a)->add( 4,6 );
    cout << endl;
   /* 测试函数地址 */
   ((SParent)child).add( ( int )4,( int )8 );
   child.SParent::add( 3,5 );
 
   return 0;
}

输出结果:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
parent int add
parent double add
 
parent int dec
child int dec
 
child float add
 
parent int add
child float add
child float add
 
parent copy construct
parent int add
parent int add
来关闭窗口...

•理解
int SParent::add(int a,int b)与double SParent::add( double a,double b )是重载

int SParent::add(int a,int b)与double SParent::add( double a,double b )都被子类SChild中的float SChild::add( float a,float b )隐藏

int SParent::dec( int a,int b )被子类SChild中的int SChild::dec( int a,int b )覆盖

•测试

1.重载测试,简单易懂,略过。
2.覆盖测试。dec函数在基类、子类中同名同参,为虚函数,故称覆盖。

SChild *pchild = (SChild *)new SParent()创建的是一个基类对象,其函数表应该为

SParent *pparent = new SChild();创建一个子类对象,其函数表应该为

由上面的函数表可见,当发生覆盖时,子类的函数名会把基类的同名函数覆盖(这也就是为什么叫覆盖的原因吧)。这样我们可以利用一个指向子类的基类指针实现多态。但重点只有一
个,就是函数表里到底指向谁(不管这个指针经过转换后是什么类型的).故输出分别为父类、子类。这是一个运行时多态。

3.隐藏测试

int SParent::add(int a,int b)与double SParent::add( double a,double b )都被子类SChild中的float SChild::add( float a,float b )覆盖,是因为他们同名,而且在不同的作用域中(基类、子类作用域是不同的)。child.add( (int)3,(int)5 );这行代码中,编译器在子类中查找add函数,只找到了一个(基类的add(int a,int b)会被编译根据隐藏规则略过),再根据隐式类型转换发现该函数适用。如果无法隐式转换,则编译不过。隐藏的原因:防止隐式类型转换造成错误。比如int也是可以转换成char的,假如基类有一函数add(char a,char b),子类也有一函数add(double a,double b)。程序员想着在子类隐式把int转换为double,但编译器可能调的是基类的。这也防止了一些库或封装好的基类对程序员造成困扰。

  像上面的代码,如果你确实需要基类的函数,可以用using SParent:add。则把基类的add函数域扩大到了子类,构成重载。

4.函数表测试

上面我们说到函数表,这个是在编译时定好的,程序运行时加载到内存中。这意味着我们可以直接通过地址去调用函数。所以((SChild *)NULL)->add( 4,6 );这种代码也是能运行通过的。网上还有人通过计算直接取到了函数表的地址直接调用了。但这种代码不安全不规范不说,还有个更大的问题。当成员函数里需要调用成员变量时,通过这种假的对象指针肯定找不到成员变量表,直接访问了非法内存。

5.函数地址测试

有了隐藏、覆盖,哪么我们要怎么调用被隐藏、覆盖的函数呢。下面有两种方法:

    ((SParent)child).add( (int)4,(int)8 );
    child.SParent::add( 3,5 );

第一种是比较低效的方法。事实上它是通过拷贝构造函数生成一个临时的基类变量去调用基类的add函数。
第二种通过::指定域去访问。这种方法是编译器根据域直接找到了基类的函数地址,跟函数表没有多大关系。


转载:http://www.jb51.net/article/54225.htm

http://www.jb51.net/article/63462.htm


你可能感兴趣的:(c/c++)