[你必须知道的.NET]第十七回:貌合神离:覆写和重载

博客园CLR基础研究团队|CLR团队精品系列|Anytao技术博客

 

[你必须知道的.NET]第十七回:貌合神离:覆写和重载

发布日期:2007.11.7 作者:Anytao
© 2007 Anytao.com ,原创作品,转贴请注明作者和出处。

 

本文将介绍以下内容:

  • 什么是覆写,什么是重载
  • 覆写与重载的区别
  • 覆写与重载在多态特性中的应用

 

 

1. 引言

覆写(override)与重载(overload),是成就.NET面向对象多态特性的基本技术之一,两个貌似相似而实则不然的概念,常常带给我们很多的误解,因此有必要以专题来讨论清楚其区别,而更重要的是关注其在多态中的应用。

在系列中,我们先后都有关于这一话题的点滴论述,本文以专题的形式再次做以深度讨论,相关的内容请对前文做以参考。  

2. 认识覆写和重载

从一个示例开始来认识什么是覆写,什么是重载?

    abstract class  Base
    {
        
//定义虚方法
        public virtual void MyFunc()
        { 
        }

        
//参数列表不同,virtual不足以区分
        public virtual void MyFunc(string str)
        {
        }

        
//参数列表不同,返回值不同
        public bool MyFunc(string str, int id)
        {
            Console.WriteLine(
"AAA");
            
return true;
        }

        
//参数列表不同表现为个数不同,或者相同位置的参数类型不同 
        public bool MyFunc(int id, string str)
        {
            Console.WriteLine(
"BBB");
            
return false;
        }

        
//泛型重载,允许参数列表相同
        public bool MyFunc<T>(string str, int id)
        {
            
return true;
        }

        
//定义抽象方法
        public abstract void Func();
    }

    
class Derived: Base
    {
        
//阻隔父类成员
        public new void MyFunc()
        {
        }

        
//覆写基类成员
        public override void MyFunc(string str)
        {
            
//在子类中访问父类成员
            base.MyFunc(str);
        }

        
//覆写基类抽象方法
        public override void Func()
        {
            
//实现覆写方法
        }
}


2.1 覆写基础篇

覆写,又称重写,就是在子类中重复定义父类方法,提供不同实现,存在于有继承关系的父子关系。当子类重写父类的虚函数后,父类对象就可以根据根据赋予它的不同子类指针动态的调用子类的方法。从示例的分析,总结覆写的基本特征包括:

  • 在.NET中只有以virtual和abstract标记的虚方法和抽象方法才能被直接覆写。
  • 覆写以关键字override标记,强调继承关系中对基类方法的重写。
  • 覆写方法要求具有相同的方法签名,包括:相同的方法名、相同的参数列表和相同的返回值类型。
概念:虚方法 

虚方法就是以virtual关键字修饰并在一个或多个派生类中实现的方法,子类重写的虚方法则以override关键字标记。虚方法调用,是在运行时确定根据其调用对象的类型来确定调用适当的覆写方法。.NET默认是非虚方法,如果一个方法被virtual标记,则不可再被static、abstrcat和override修饰。 

概念:抽象方法 

抽象方法就是以abstract关键字修饰的方法,抽象方法可以看作是没有实现体的虚方法,并且必须在派生类中被覆写,如果一个类包括抽象方法,则该类就是一个抽象类。因此,抽象方法其实隐含为虚方法,只是在声明和调用语法上有所不同。abstract和virtual一起使用是错误的。

2.2 重载基础篇

重载,就是在同一个类中存在多个同名的方法,而这些方法的参数列表和返回值类型不同。值得注意的是,重载的概念并非面向对象编程的范畴,从编译器角度理解,不同的参数列表、不同的返回值类型,就意味着不同的方法名。也就是说,方法的地址,在编译期就已经确定,是这一种静态绑定。从示例中,我们总结重载的基本特征包括:

  • 重载存在于同一个类中。
  • 重载方法要求具有相同的方法名,不同的参数列表,返回值类型可以相同也可以不同(通过operator implicit 可以实现一定程度的返回值重载,不过不值得推荐)。
  • .NET 2.0引入泛型技术,使得相同的参数列表、相同的返回值类型的情况也可以构成重载。  

3. 在多态中的应用

多态性,简单的说就是“一个接口,多个方法”,具体表现为相同的方法签名代表不同的方法实现,同一操作作用于不同的对象,产生不同的执行结果。在.NET中,覆写实现了运行时的多态性,而重载实现了编译时的多态性。

运行时的多态性,又称为动态联编,通过虚方法的动态调度,在运行时根据实际的调用实例类型决定调用的方法实现,从而产生不同的执行结果。

    class  Base
    {
        
public virtual void MyFunc(string str)
        {
            Console.WriteLine(
"{0} in Base", str);
        }
    }

    
class Derived: Base
    {
        
//覆写基类成员
        public override void MyFunc(string str)
        {
            Console.WriteLine(
"{0} in Derived", str);
        }

        
public static void Main()
        {
            Base B 
= new Base();
            B.MyFunc(
"Hello");
            Derived A 
= new Derived();
            B 
= A;
            B.MyFunc(
"Morning");
        }
    }

从结果中可知,对象B两次执行B.MyFunc调用了不同的方法,第一次调用基类方法MyFunc,而第二次调用了派生类方法MyFunc。在执行过程中,对象B先后指向了不同的类的实例,从而动态调用了不同的实例方法,显然这一执行操作并非确定于编译时,而是在运行时根据对象B执行的不同类型来确定的。我们在此不分析虚拟方法的动态调度机制,而只关注通过虚方法覆写而实现的多态特性,详细的实现机制请参考本系列的其它内容。

编译时的多态性,又称为静态联编,一般包括方法重载和运算符重载。对于非虚方法来说,在编译时通过方法的参数列表和返回值类型决定不同操作,实现编译时的多态性。例如,在实际的开发过程中,.NET开发工具Visual Studio的智能感知功能就很好的为方法重载提供了很好的交互手段,例如:

从智能感知中可知方法MyFunc在派生类Derived中有三次重载,调用哪种方法由程序开发者根据其参数、返回值的不同而决定。由此可见,方法重载是一种编译时的多态,对象A调用哪种方法在编译时就已经确定。

4. 比较,还是规则

  • 如果基访问引用的是一个抽象方法,则将导致编译错误。
    abstract class  Base
    {
        
public abstract void Func();
    }

    
class Derived: Base
    {
        
//覆写基类抽象方法
        public override void Func()
        {
            
base.Func();
        }
    }
  • 虚方法不能是静态的、密封的。
  • 覆写实现的多态确定于运行时,因此更加的灵活和抽象;重载实现的多态确定于编译时,因此更加的简单和高效。二者各有特点与应用,不可替代。

在下表中,将覆写与重载做以总结性的对比,主要包括:

规则

覆写(override

重载(overload

存在位置

存在于有继承关系的不同类中

存在于同一个类中

调用机制

运行时确定

编译时确定

方法名

必须相同

必须相同

参数列表

必须相同

必须不同

返回值类型

必须相同

可以不相同

泛型方法

可以覆写

可以重载


注:参数列表相同表示参数的个数相同,并且相同位置的参数类型也相同。

5. 结论

深入的理解覆写和重载,是对多态特性和面向对象机制的有力补充,本文从基本概念到应用领域将两个概念进行一一梳理,通过对比整理区别,还覆写和重载以更全面的认知角度,同时也更能从侧面深入的了解运行时多态与编译时多态的不同情况。

 

参考文献

(web)TerryLee, 再谈重载与覆写http://www.cnblogs.com/Terrylee/archive/2006/03/10/347104.html

(web)失落的BLOGSC#泛型http://www.cnblogs.com/lianyonglove/archive/2007/07/27/720682.html

 

温故知新

[开篇有益]
[第一回:恩怨情仇:is和as]
[第二回:对抽象编程:接口和抽象类]
[第三回:历史纠葛:特性和属性]
[第四回:后来居上:class和struct]
[第五回:深入浅出关键字---把new说透]
[第六回:深入浅出关键字---base和this]
[第七回:品味类型---从通用类型系统开始]
[第八回:品味类型---值类型与引用类型(上)-内存有理]
[第九回:品味类型---值类型与引用类型(中)-规则无边]
[第十回:品味类型---值类型与引用类型(下)-应用征途]
[第十一回:参数之惑---传递的艺术(上)]
[第十二回:参数之惑---传递的艺术(下)]
[第十三回:从Hello, world开始认识IL]
[第十四回:认识IL代码---从开始到现在]
[第十五回:继承本质论]
[第十六回:深入浅出关键字---using全接触]

© 2007 Anytao.com

原创作品,转贴请注明作者和出处,留此信息。

本文以“现状”提供且没有任何担保,同时也没有授予任何权利。
This posting is provided "AS IS" with no warranties, and confers no rights.

posted @ 2007-11-07 19:53 Anytao 阅读(4757) 评论(36)  编辑 收藏 所属分类: 01 [你必须知道的.NET]

  回复  引用  查看    
#1楼 2007-11-07 21:00 | 蛙蛙池塘      
呵呵,沙发一个
  回复  引用  查看    
#2楼 2007-11-07 21:08 | Adrian H.      
“神”不能“离”吧。。子类的行为要和父类统一。。
  回复  引用  查看    
#3楼 2007-11-07 21:25 | jillzhang      
@Adrian H.
楼主的貌合神离是对覆写和重载的修饰
指的是这两个词表面上看起来相像,但实质却有很大区别
  回复  引用  查看    
#4楼 [楼主]2007-11-07 21:30 | Anytao      
@蛙蛙池塘
哈哈,我下次也坐一个你的:-)
  回复  引用  查看    
#5楼 [楼主]2007-11-07 21:32 | Anytao      
@Adrian H.
呵呵,不好意思,词用的大了点儿,主要还是从二者的区别点上来说的,此神非彼神:-)
  回复  引用  查看    
#6楼 [楼主]2007-11-07 21:33 | Anytao      
@jillzhang
恩,就是这个意思,不过应该琢磨个更准确的,Adrian H.的意见值得考虑,谢谢啦。
  回复  引用  查看    
#7楼 2007-11-07 21:45 | BoyLee      
这个系列不错.可以巩固我好多东西.很多我不是明白的东西.都搞清楚了.谢谢博主
  回复  引用    
#8楼 2007-11-07 22:52 | 网站优化 [未注册用户]
多我不是明白的东西.都搞清楚了
  回复  引用    
#9楼 2007-11-07 22:52 | 舞台租赁 [未注册用户]
可以巩固我好多东西
  回复  引用  查看    
#10楼 [楼主]2007-11-07 23:05 | Anytao      
@BoyLee
共同学习,欢迎经常来关注,一起进步。
  回复  引用  查看    
#11楼 2007-11-08 01:01 | Soli      
--引用--------------------------------------------------
Anytao:
……
.NET 2.0引入泛型技术,使得相同的参数列表、相同的返回值类型的情况也可以构成重载。
……
--------------------------------------------------------

是笔误么?

感谢博主共享知识。

ps:博主的页面在firefox下看着好别扭 ^_^

  回复  引用    
#12楼 2007-11-08 01:49 | 越狱第三季第八集 [未注册用户]
确实不错!
  回复  引用  查看    
#13楼 2007-11-08 09:27 | 麒麟.NET      
这个系列确实不错,期待下文。。。
  回复  引用  查看    
#14楼 [楼主]2007-11-08 09:34 | Anytao      
@Soli
并非笔误,关于泛型重载其实想展开来写的,还是等下次在详细描述吧。在此,就举一个简单的示例吧:
public class A
{
public void Method<T>(int i)
{
}

public void Method(int i)
{
}
}

上例中的两个方法,具有相同的方法名,相同的参数列表和相同的返回值,但是可以构成重载,这是泛型方法重载值得注意的地方。

呵呵,关于页面问题能否再详细些,应该怎么改进呢?
  回复  引用  查看    
#15楼 [楼主]2007-11-08 09:34 | Anytao      
@越狱第三季第八集
:-)
  回复  引用  查看    
#16楼 [楼主]2007-11-08 09:34 | Anytao      
@麒麟.NET
持续继续努力中,就是太忙了,进度有些放缓:-)
  回复  引用    
#17楼 2007-11-08 12:16 | IceH [未注册用户]
没有讲到实质性的东西 此文不如以前的文章来的精彩!不过还是非常欣赏楼主的无私奉献精神!
  回复  引用  查看    
#18楼 2007-11-08 12:34 | 钱彦云      
不错,这些概念在理解visitor模式的时候很重要。
  回复  引用  查看    
#19楼 2007-11-08 12:42 | Soli      
@Anytao

这个我还真不知道呢。C++也是这样的么?期待博主深入讲解有关知识。

=======
页面的问题是:右边的导航栏被挤到下面去了。firefox中是这样,IE中是正常的。
好像把左边的content块的宽度减小点就好了。
在你的CSS里:
#content {
    padding: 0.5em 1em;
    width:75%;
    margin-top:0px;
    float: left;
    }


  回复  引用  查看    
#20楼 [楼主]2007-11-08 12:48 | Anytao      
@IceH
呵呵,感谢你的意见,可能和选题有些关系,下回注意深入的角度。另外,有些内容限于篇幅没有深入展开,值得思考以后的阐释关注度。
  回复  引用  查看    
#21楼 [楼主]2007-11-08 12:49 | Anytao      
@钱彦云
覆写和重载是理解多态的重要内容之一,对于设计模式的深入也是有益的。
  回复  引用  查看    
#22楼 [楼主]2007-11-08 12:51 | Anytao      
@Soli
关于C++,我不是很清楚,有些惭愧:-)
关于泛型重载等问题,相信以后有所涉猎,谢谢关注。

谢谢你关于页面显示问题的帮助,合适的时间,我修改一下CSS。
  回复  引用    
#23楼 2007-11-10 15:16 | noon009 [未注册用户]
好文章
  回复  引用  查看    
#24楼 [楼主]2007-11-10 16:14 | Anytao      
@noon009
:-)
  回复  引用    
#25楼 2007-11-11 12:37 | abc123 [未注册用户]
主要责任还是以前不严格的翻译书,如果能将 override 和 overload翻译准确点,如统一叫做“覆盖”和“重写”,根本就不会出现令很多人混淆的情况。
  回复  引用  查看    
#26楼 [楼主]2007-11-12 09:15 | Anytao      
@abc123
从这两个概念的本质上来说,我认为覆写和重载更为恰当。:-),你提到的重写表达重载不够准确,很多时候重写反而被认为是覆写的语义。
  回复  引用    
#27楼 2007-11-20 23:11 | kade [未注册用户]
重载方法要求具有相同的方法名,不同的参数列表或者不同的返回值类型。

那个"或者"有点儿不对吧,这次应该是笔误吧,
当只是返回值不同时,肯定不行的,编译都不可以.

顶了!写得不错,学习中!希望还有更多的好文!
  回复  引用  查看    
#28楼 [楼主]2007-11-20 23:49 | Anytao      
@kade
谢谢你的提醒,没有表达清楚,及时修改:-)
  回复  引用  查看    
#29楼 [楼主]2007-11-21 09:54 | Anytao      
@kade
很抱歉,修改提交总是有错误,不知怎么回事,只好联系dudu解决再说了:-)
  回复  引用  查看    
#30楼 [楼主]2007-12-06 11:51 | Anytao      
@Anytao
已做修改,谢谢了:-)
  回复  引用    
#31楼 2008-05-28 17:53 | 深圳搬家 [未注册用户]
我按你的修改了,正在测试中
  回复  引用  查看    
#32楼 [楼主]2008-05-28 23:01 | Anytao      
@深圳搬家
:-)
  回复  引用    
#33楼 2008-08-19 00:48 | mthgh [未注册用户]
@Anytao 您好,请教问题,用子类的构造函数来初始化父类的实例 以及 把子类的实例赋值给父类的实例,这两种情况我一直不是非常的理解,觉的有点别扭,我想问的是,这样的应用通常用于什么场合下?为什么要这样用?
  回复  引用  查看    
#34楼 [楼主]2008-08-19 16:07 | Anytao      
@mthgh

以程序来表达你的观点,是不是可以翻译为:
Animal animal = new Dog();

public void AnimalAction(Animal animal)
{
animal.ToDoSomething();
}

子类可以替换父类,是面向对象的一本原则之一,也是继承体系得以维系和应用的基础原则,如果觉得有点别扭,则必须学会适应。正是因为有了这一原则,才有了面向对象编程的基础架构,才有了设计模式的无限发挥。

至于应用场合,应该说这种方式可以应用于OO编程的任何方面,没有具体的场合而言,只要继承存在的情况下,都可以应用Liskov替换原则来完成一定的设计思想。

而关于为什么用的问题,我想上面的AnimalAction方法,可以给你些细启示,这里参数animal本身为Animal类型,那么也就意味着AnimalAction方法可以无缝的接受任何Animal或者派生自Animal的类型,这正是面向抽象编程的基础。

标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2008-09-22 14:04 编辑过
"五向定位"职业成长路线公开课(上海、南京、大连)
Google站内搜索


相关链接: