[你必须知道的.NET]第十七回:貌合神离:覆写和重载
本文将介绍以下内容:
- 什么是覆写,什么是重载
- 覆写与重载的区别
- 覆写与重载在多态特性中的应用
1. 引言
覆写(override)与重载(overload),是成就.NET面向对象多态特性的基本技术之一,两个貌似相似而实则不然的概念,常常带给我们很多的误解,因此有必要以专题来讨论清楚其区别,而更重要的是关注其在多态中的应用。
在系列中,我们先后都有关于这一话题的点滴论述,本文以专题的形式再次做以深度讨论,相关的内容请对前文做以参考。
2. 认识覆写和重载
从一个示例开始来认识什么是覆写,什么是重载?
{
//定义虚方法
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中,覆写实现了运行时的多态性,而重载实现了编译时的多态性。
运行时的多态性,又称为动态联编,通过虚方法的动态调度,在运行时根据实际的调用实例类型决定调用的方法实现,从而产生不同的执行结果。
{
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. 比较,还是规则
- 如果基访问引用的是一个抽象方法,则将导致编译错误。
{
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)失落的BLOGS,C#泛型,http://www.cnblogs.com/lianyonglove/archive/2007/07/27/720682.html
转载自http://www.cnblogs.com/anytao/archive/2007/11/07/must_net_17.html
[你必须知道的.NET]第十七回:貌合神离:覆写和重载的更多相关文章
- [你必须知道的.NET]第二十七回:interface到底继承于object吗?
发布日期:2009.03.05 作者:Anytao © 2009 Anytao.com ,Anytao原创作品,转贴请注明作者和出处. 说在,开篇之前 在.NET世界里,我们常常听到的一句话莫过于“S ...
- [你必须知道的.NET] 第八回:品味类型---值类型与引用类型(上)-内存有理
原文地址:http://kb.cnblogs.com/page/42318/ 系列文章导航: [你必须知道的.NET] 开篇有益 [你必须知道的.NET] 第一回:恩怨情仇:is和as [你必须知道的 ...
- [你必须知道的.NET]第二十一回:认识全面的null
发布日期:2008.7.31 作者:Anytao © 2008 Anytao.com ,Anytao原创作品,转贴请注明作者和出处. 说在,开篇之前 null.nullable.??运算符.null ...
- [你必须知道的.NET]第二十回:学习方法论
说在,开篇之前 本文,源自我回答刚毕业朋友关于.NET学习疑惑的回复邮件. 本文,其实早计划在<你必须知道的.NET>写作之初的后记部分,但是因为个中原因未能如愿,算是补上本书的遗憾之一. ...
- [你必须知道的.NET]第二十三回:品味细节,深入.NET的类型构造器
发布日期:2008.11.2 作者:Anytao © 2008 Anytao.com ,Anytao原创作品,转贴请注明作者和出处. 说在,开篇之前 今天Artech兄在<关于Type Init ...
- [你必须知道的.NET]第二十四回:认识元数据和IL(上)
发布日期:2009.02.24 作者:Anytao © 2009 Anytao.com ,Anytao原创作品,转贴请注明作者和出处. 说在,开篇之前 很早就有说说Metadata(元数据)和IL(中 ...
- [你必须知道的.NET]第三十一回,深入.NET 4.0之,从“新”展望
发布日期:2009.05.22 作者:Anytao © 2009 Anytao.com ,Anytao原创作品,转贴请注明作者和出处. /// <summary> /// 本文开始,将以& ...
- [你必须知道的.NET]第二十六回:认识元数据和IL(下)
发布日期:2009.03.04 作者:Anytao © 2009 Anytao.com ,Anytao原创作品,转贴请注明作者和出处. 说在,开篇之前 书接上回: 第二十四回:认识元数据和IL(上), ...
- [你必须知道的.NET]第二十五回:认识元数据和IL(中)
发布日期:2009.02.25 作者:Anytao © 2009 Anytao.com ,Anytao原创作品,转贴请注明作者和出处. 说在,开篇之前 书接上回[第二十四回:认识元数据和IL(上)], ...
随机推荐
- bzoj3007: 拯救小云公主(二分+并查集)
挺水的题...好多题解说是对偶图,其实感觉不能算严格意义上的对偶图吧QAQ 先二分答案r,然后以boss为中心半径为r的圆不能走,求能否从左下走到右上. 不能从左下走到右上,说明这堆圆把图隔开了,于是 ...
- 【loj#139】树链剖分
#139. 树链剖分 题目描述 这是一道模板题. 给定一棵 $n$个节点的树,初始时该树的根为 111 号节点,每个节点有一个给定的权值.下面依次进行 $m$ 个操作,操作分为如下五种类型: 换根:将 ...
- C++之高级编程20170914
/*************************************************************************************************** ...
- 在 Ubuntu16.04上安装anaconda+Spyder+TensorFlow(支持GPU)
TensorFlow 官方文档中文版 http://www.tensorfly.cn/tfdoc/get_started/introduction.html https://zhyack.github ...
- width: calc(100% - 20px);
width: calc(100% - 20px); css3 的 calc()函数.这里的意思是设置宽度比100%的宽度少20px. calc()函数用于动态计算长度值. calc()函数支持 &qu ...
- 去除sql表格中的unique 唯一属性----phpmyadmin 没发现哪里好直接操作
ALTER TABLE tf_giftcard_record DROP INDEX oid;
- 前端PHP入门-014-参数的引用
我们学习了变量的引用,我们来回顾一下知识: <?php $a = 10; $b = &$a; $a = 100; echo $a.'---------'.$b; ?> 而函数的参数 ...
- 前端PHP入门-005-爱情是常量还是变量
常量 常--汉语字面为:长久,经久不变. 常量那就好翻译了:长久不变的值. 常量的使用范围非常广泛. 我们在以后,定义我们的工作目录.定义一些特点的帐户密码.版本号等我们都会使用到常量.所以这一块的知 ...
- Java 8 Stream 用法
一.Stream是什么 Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator.原始版本的 Iterator,用户只能显式地一个一个遍历元 ...
- 【CodeForces】899 E. Segments Removal
[题目]E. Segments Removal [题意]给定n个数字,每次操作删除最长的连续相同数字(等长删最左),求全部删完的最少次数.n<=2*10^6,1<=ai<=10^9. ...