C# 继承、接口与多态

本文探讨了C#中的继承、多态和接口的概念。继承允许类从基类继承属性和方法,多态则允许不同的对象执行相同方法时表现出不同行为。接口则定义了对象的兼容性,但不关心其实现。C#支持单一继承和多重接口实现,抽象类和虚函数是实现多态的关键。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C# 继承、接口与多态

  我在这里想谈一谈在C#中的继承(继承于基类或接口)和多态(如方法的覆写)。继承和多态是面向对象编程中两个重要的特性,这种特性和编程语言本身是没多大关系的,因此我先会用非编程的思维来谈一谈我对它们的认识,然后再谈一谈它们在C#中的实现方法。

  1、继承的含义

  所谓继承,就是“站在巨人的肩膀上”进行扩展。例如,最开始的铅笔尾端是没有橡皮擦的,后来有个男孩在尾端装上了橡皮擦,铅笔的销量一路飙升,他也成了百万富翁,这说明好的灵感多么的重要!(偏题了啊喂!感兴趣的童鞋请戳:http://baike.baidu.com/view/1628703.htm)好吧,回归正题,这就是一个典型的继承。我们在做一个“橡皮头铅笔”时,不需要想如何做一支铅笔,而是拿来一支现成的铅笔,在它上面装上橡皮而已。继承可以帮助我们重用组件,继承是人类不断进步的源泉(貌似又偏题了!)。

  2、多态的含义

  从非编程的角度来看“多态”,可以理解成“不同的人执行相同的条例,却有不同的行为”。在编程的角度来看,是“不同的对象执行相同的方法,却有不同的行为”。怎么来理解呢,我可以打以下这个比方。

  某学校的学生规范中有写道:“老师进教室时,学生坐好如下课前准备:安静坐好。此时值日生应该上讲台擦黑板,课代表收本科目作业。”教室里,除了老师外,其他的人都是学生(包括课代表和值日生),在执行这些行为的时候,课代表和值日生为什么不和规范中所提到的“学生”一样安静坐好呢?因为规范中还说到,他们有自己的职责:一个应当擦黑板,一个应当收作业。这就是多态,虽然学生们都要做好课前准备,但是却不全部相同。由“学生”派生出来的“课代表”要收作业,由“学生”派生出来的值日生要擦黑板,而“学生”这个“基类”的各个同学只需要安静坐好。他们都执行着相同的条例(做课前准备),却有不同的行为。

  3、接口的含义

  通俗地来打比方,我要开发一个多功能插线板,其中的插头孔要支持大陆标准、香港标准、英国标准和美国标准。我只需要把插线板按照标准挖出这4个孔就可以了,我就可以说,我这个插线板可以插大陆、香港、美国、英国的电源。至于插上去后怎么样,我不关心,我只关心,它可以插这些电源。再例如,我开发了一个新的电子产品,此产品要支持用USB来传数据,我就要预留一个USB接口,至于接上USB数据线之后怎么样,这个是以后的事情,总之它先要支持USB接口。

  从程序的角度通俗的来理解接口,就是定义程序有这样的一种“兼容性”,而不去管接口内部是怎么实现的。因此,作为接口,它只包含定义,而不包含具体的实现,这也是它和抽象类的最大区别(抽象类中可以包含方法的实现)。

  4、C#中的继承与多态

  C#不像C++那样区分“共有继承"、"私有继承"。所有的继承均是共有的,也就是基类的所有成员的访问性在派生类中都不会变。并且,C#中只支持单一继承,但是可以继承多个接口。这个很容易理解,一辆高级汽车,它只继承于一个对象(一辆普通汽车),而不会同时继承于一辆汽车和一架飞机;但是它可以用很多个接口,如加油的接口和充电的接口。Java和C#的设计值发现多重继承在使用起来太蛋疼了,因此在他们的语言中去掉了这个特性。

  类的访问性、成员定义方法我就不进行阐述了,因为这是很基本的东西。下面我主要来谈一谈抽象类、虚函数,它们是实现多态的重要部分。

  有如下代码,定义了一个Creature类,Creature类派生了两个子类:Animal和Human。Creature类有一个抽象方法Eat。抽象方法是指,继承于本抽象类的派生类都必须自己实现这个方法。抽象方法只是一个声明,没有方法体,包含抽象方法的类一定要为抽象类。由于Eat是抽象方法,Human和Animal继承于抽象类Creature,因此它们都必须要实现自己的Eat方法。代码如下:

using System;

namespace Demo
{
    public abstract class Creature
    {
        public abstract void Eat();
    }

    public class Human : Creature
    {
        public override void Eat()
        {
            Console.WriteLine("用火先将食物烤熟再吃。");
        }
    }

    public class Animal : Creature
    {
        public override void Eat()
        {
            Console.WriteLine("直接吞下肚子。");
        }
    }

    public class Program
    {
        static void Main(string[] args)
        {
            Creature h = new Human();
            Creature a = new Animal();
            h.Eat();
            a.Eat();
        }
    }
}

  运行后,屏幕输出如下:

  用火先将食物烤熟再吃。
  直接吞下肚子。

  显然,前者是执行h.Eat()的结果,后者是执行a.Eat()的结果。派生于同一个类的两个派生类,其Eat方法经过了重写表现出了不同的行为。需要注意的是,在覆写基类的抽象方法时,必须要使用override关键字,否则无法通过编译。

  抽象类Creature是无法实例化的(无法用new Creature()来实例化),因为它还有一个方法Eat没有实现。如果我们想让Creature有个“默认”的Eat方法,应该怎么做呢?这个时候就需要利用虚函数了。

  代码如下:

using System;

namespace Demo
{
    public class Creature
    {
        public virtual void Eat()
        {
            Console.WriteLine("生物消化了食物。");
        }
    }

    public class Human : Creature
    {
        public override void Eat()
        {
            Console.WriteLine("用火先将食物烤熟再吃。");
        }
    }

    public class Animal : Creature
    {
        public override void Eat()
        {
            Console.WriteLine("直接吞下肚子。");
        }
    }

    public class Program
    {
        static void Main(string[] args)
        {
            Creature h = new Human();
            Creature a = new Animal();
            Creature c = new Creature();
            h.Eat();
            a.Eat();
            c.Eat();
        }
    }
}
  上例中,我们为Creature类添加了一个虚方法Eat。也就是说,如果Creature的派生类没有被覆写,它就调用基类的Eat。所谓被覆写,就是派生类中存在同名函数,且使用了关键字override。在之前提过的学生、课代表、值日生的例子中,代码如下所示:

using System;

namespace Demo
{
    public class Student
    {
        public virtual void ClassBegin()
        {
            Console.WriteLine("安静坐好。");
        }
    }

    public class StudentOnDuty : Student
    {
        public override void ClassBegin()
        {
            Console.WriteLine("擦黑板。");
        }
    }

    public class Representative : Student
    {
        public override void ClassBegin()
        {
            Console.WriteLine("收作业。");
        }
    }

    public class Program
    {
        static void Main(string[] args)
        {
            //定义学生
            Student s = new Student();
            //定义课代表
            Student r = new Representative();
            //定义值日生
            Student d = new StudentOnDuty();
            s.ClassBegin();
            r.ClassBegin();
            d.ClassBegin();
        }
    }
}

  你可能会注意到,只有我们用基类的类型来定义一个派生类的对象时,才有多态的机制。多态有什么意义呢?举例来说,假设有一个方法,需要传入一个Student类,它可以调用Student类中的ClassBegin。如果没有多态,我们就要这样做:

public void WhenClassBegin(Student s)
{
    s.ClassBegin();
}

public void WhenClassBegin(Representative s)
{
    s.ClassBegin();
}

public void WhenClassBegin(StudentOnDuty s)
{
    s.ClassBegin();
}

  如果再增加一个派生类,我们就要为WhenClassBegin多增加一个版本,这样做工作量巨大而且不易于维护!现在有了多态,有了虚函数,我们只需要这样来写:

public void WhenClassBegin(Student s)
{
    s.ClassBegin();
}

  因为Student对象能知道,它究竟要调用哪个ClassBegin方法。


  5、C#中的接口

   C#中定义接口十分简单。接口中的方法不包含可访问性修饰符,也不包含方法体。如以下就是一个接口的声明和使用:

using System;

namespace Demo
{
    public interface IEdible
    {
        void Eat();
    }

    public class Program : IEdible
    {
        public static void Main(string[] args)
        {
            IEdible food = new Program();
            food.Eat();
        }

        public void Eat()
        {
            Console.WriteLine("Yummy!");
        }
    }
}

  需要注意的是IEdible food = new Program()这句话的意思是,实例化一个继承了IEdible的对象。一定要注意,接口是不能实例化的,只能实例化一个具有某种接口的对象。如果Program没有继承IEdible,那么编译器就会报错。

  这个就是我对继承、接口、多态的一些理解,这些概念广泛地运用在各种面向对象编程中,希望对大家有用。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值