黑马程序员_多态性

来源:互联网 发布:配置网络 ubuntu 编辑:程序博客网 时间:2024/05/01 07:53

---------------------- <a href="http://edu.csdn.net"target="blank">ASP.Net+Android+IOS开发</a>、<a href="http://edu.csdn.net"target="blank">.Net培训</a>、期待与您交流! ----------------------




        面向对象的第三个主要特性。


        多态性是同一操作作用于不同的类的对象,由不同的类进行不同的解释,最终产生不同的执行效果。


        多态性按照产生多态行为的时间分为:编译时多态与运行时多态。


1  编译时多态性


        编译时多态是指通过设置不同的方法签名,在编译时由编译器根据方法的签名决定调用何种方法。


        通过指定方法的访问级别(例如 public private)、可选修饰符(例如 abstract sealed)、返回值、名称和任何方法参数,可以在类或结构中声明方法。这些部分统称为方法的“签名”。


        使用不同的方法签名实现多态性称为方法重载。为进行方法重载,方法的返回类型不是方法签名的一部分。但是,在确定委托和委托所指向方法之间的兼容性时,返回类型是方法签名的一部分。


        安装具体签名的不同可以分为:参数类型或数量重载、输出与输入型参数引用重载。


1.1 参数类型或数量重载


        在方法重载时,实参与形参一一对应,如果相应位置类型相同且方法参数数量相等,则完成匹配,即实现同名方法不同参数的重载。


如有下面方法:

void method()        {            Console.WriteLine("is a zero parameter");        }        void method(int i)        {            Console.WriteLine("is a one parameter");        }        void method(int i, double d)        {            Console.WriteLine("is a two parameter");        }

当调用函数

this.method(1, 2.0);

会调用第三个方法,输出:is a two parameter


1.2 输出与输入型参数引用重载


        输入引用从参数使用 ref关键字修饰,在方法调用传递过程中,不同于值传递,传递的是值在内存中的地址,由此,在方法内对参数的修改会作用至实参本身。


        输出引用参数使用 out关键字修饰,类似于输入引用参数,但在调用方法前不需要对变量进行初始化。


        由于本篇的重点是运行时多态,在对编译时多态介绍比较简单,仅作参考。


2  运行时多态性


        运行时多态指在基本类中定义虚方法,在派生类中重新定义方法或覆盖虚方法,在发生对象调用时,由公共语言运行时(CLR)根据对象类型决定调用何种方法。


        虚方法允许您以统一方式处理多组相关的对象。由虚方法引出的多态性有多重情形,以下着重介绍3种情形。


2.1 新成员隐藏基类成员


        通过 new关键字,在派生类中重新定义一个与基类相同名字方法,在使用该派生类的实例被当作基类的实例访问,会调用基类成员方法。

如下示例:

public class BaseClass    {        public void DoWork()         {            Console.WriteLine("base Class do work");        }           }    public class DerivedClass : BaseClass    {        public new void DoWork()         {            Console.WriteLine("derived Class do work");        }            }

执行以下语句时,

DerivedClass D = new DerivedClass();            D.DoWork();            BaseClass B = (BaseClass)D;            B.DoWork();

会输出如下结果。


derivedClass do work


baseClass do work


        程序表面,当派生类DerivedClass实例 D被当做基类BaseClass实例 B调用方法 DoWork()时,调用的是基类 baseClassDoWork()方法。


2.2 虚成员


        使用virtual在基类中定义虚成员方法,使用override在派生类中覆盖虚方法,从而使得在使用该派生类的实例被当作基类的实例访问,会调用派生类成员方法。


如下示例:

public class BaseClass    {        public virtual void DoWork()         {            Console.WriteLine("base Class do work");        }           }    public class DerivedClass : BaseClass    {        public override void DoWork()         {            Console.WriteLine("derived Class do work");        }            }


执行以下语句时,

DerivedClass D = new DerivedClass();            D.DoWork();            BaseClass B = (BaseClass)D;            B.DoWork();

会输出如下结果。


derivedClass do work


derivedClass do work


        程序表面,当派生类中对基类方法进行覆盖,派生类的实例 D被当做基类实例 B调用方法时,仍然调用的是派生类DoWork()方法。


2.3 阻止派生类重写虚拟成员


        使用关键字sealed阻止派生类重写虚方法,基类至sealed修饰的派生类将继续沿用成员继承机制,而对sealed修饰的派生类再派生时,将使用新成员隐藏基类成员机制。


示例如下:

public class A    {        public virtual void DoWork()         {            Console.WriteLine("a do work");        }    }    public class B : A    {        public override void DoWork()         {            Console.WriteLine("b do work");        }    }    public class C : B    {        public sealed override void DoWork()         {            Console.WriteLine("c do work");        }    }    public class D : C    {        public new void DoWork()         {            Console.WriteLine("d do work");        }    }

执行以下语句时,

D d = new D();            Console.WriteLine("d");            d.DoWork();            C c = (C)d;            Console.WriteLine("c");            c.DoWork();            B b = (B)d;            Console.WriteLine("b");            b.DoWork();            A a = (A)d;            Console.WriteLine("a");            a.DoWork();

会输出如下结果。


d


d do work


c


c do work


b


c do work


a


c do work


        程序表明,派生类实例对象d被当做基类实例对象c调用DoWork()方法时,执行的是基类 c的方法;派生类实例对象d被当做基类实例对象b调用DoWork()方法时,执行的是基类 c的方法;派生类实例对象d被当做基类实例对象a调用DoWork()方法时,执行的是基类 c的方法,符合新成员隐藏基成员、虚方法机制。


        在此不妨谈谈其具体实现机制,当派生类对象被强制类型转换赋值给基类对象时(这里有个著名的里氏法则,子类都可以向父类转化,反之不行,具体证明我也不会),基类对象是拿着派生类的引用,也就是说地址还指向派生类的对象,当使用override覆盖时,派生类对继承过来的基类成员会重新定义,也就是派生类中的基类副本会发生改变,但这并不会影响基类本身的定义,因而当派生类对象被当做基类对象访问时,是基类调用,但是是派生类内的基类发生调用,因而也就调用的是我们在派生类中定义的覆盖成员;而使用 new定义新的成员并不会改变派生类中基类的副本,因而当派生类对象被当做基类对象访问时,还是由派生类中的基类副本,调用其原本的基类中的成员;而密封不过是阻止了虚函数在派生类中被覆盖,并没有从根本上引入新的机制。由于认知有限,难免让各位见笑了,如有高见,可以留言。


参考


C# 编程指南


C# 自学手册


---------------------- <a href="http://edu.csdn.net"target="blank">ASP.Net+Android+IOS开发</a>、<a href="http://edu.csdn.net"target="blank">.Net培训</a>、期待与您交流! ----------------------


0 0