c#笔记整理 关于继承与多态等

【 塔 · 第 二 条 约 定 】

c#面向对象基础

整理private、protected、public、abstract等的异同

  1. public 公有访问。不受任何限制。

  2. private 私有访问。只限于本类成员访问,子类,实例都不能访问。

  3. protected 保护访问。只限于本类和子类访问,实例不能访问

    三者的对比:

  • private和protected的共同点:外部都不可以访问。
  • private和protected的不同点:在同一类中可视为一样,但在继承中就不同了,private在派生类中不可以被访问,而protected可以。
  • public对任何类和成员都完全公开,无限制访问。
  • class 内默认为private。struct 内默认为public
    eg:
class music
{
    public string singer;
    protected int age;
    private int weight;
}
class palyer
{
     music obj = new music();
     obj.singer = sam;    
     //obj.age = 21;  不能访问
    //obj.weight = 100;  不能访问 如果是子类则可以访问
}
  1. abstract类不能被实例化,它只提供其他类的继承的接口

整理有关继承、多态等概念

为了提高软件模块的可复用性和可扩充性,以便提高软件的开发效率,我们总是希望能够利用前人或自己以前的开发成果,同时又希望在

自己的开发过程中能够有足够的灵活性,不拘泥于复用的模块。C#这种完全面向对象的程序设计语言提供了两个重要的特性--

继承性inheritance 和多态性polymorphism。

  继承是面向对象程序设计的主要特征之一,它可以让您重用代码,可以节省程序设计的时间。继承就是在类之间建立一种相交关系,使得

新定义的派生类的实例可以继承已有的基类的特征和能力,而且可以加入新的特性或者是修改已有的特性建立起类的新层次。

  现实世界中的许多实体之间不是相互孤立的,它们往往具有共同的特征也存在内在的差别。人们可以采用层次结构来描述这些实体之间的相似之处和不同之处。
最高层的实体往往具有最一般最普遍的特征,越下层的事物越具体,并且下层包含了上层的特征。
它们之间的关系是基类与派生类之间的关系。
为了用软件语言对现实世界中的层次结构进行模型化,面向对象的程序设计技术引入了继承的概念。一个类从另一个类派生出来时,

派生类从基类那里继承特性。派生类也可以作为其它类的基类。从一个基类派生出来的多层类形成了类的层次结构。

  注意:C#中,派生类只能从一个类中继承。这是因为,在C++中,人们在大多数情况下不需要一个从多个类中派生的类。从多个基类中派生一个类这往往会带来许多问题,从而抵消了这种灵活性带来的优势。
C#中,派生类从它的直接基类中继承成员:方法、域、属性、事件、索引指示器。除了构造函数和析构函数,派生类隐式地继承了直接基类的所有成员。

eg:(搜刮的栗子,看着挺好理解。)

using System ;

class Vehicle //定义交通工具(汽车)类

{

     protected int wheels ; //公有成员:轮子个数

     protected float weight ; //保护成员:重量

     public Vehicle( ){;}

     public Vehicle(int w,float g)

     {

        wheels = w ;

        weight = g ;

     }

     public void Speak( )

     {

        Console.WriteLine( "交通工具的轮子个数是可以变化的! " ) ;

     }

} ;

class Car:Vehicle //定义轿车类:从汽车类中继承

{

    int passengers ; //私有成员:乘客数

    public Car(int w , float g , int p) : base(w, g)

    {

        wheels = w ;

        weight = g ;

        passengers=p ;

    }

}//Vehicle 作为基类,体现了"汽车"这个实体具有的公共性质:汽车都有轮子和重量。Car 类继承了Vehicle 的这些性质,并且添加了自身的特性:可以搭载乘客。

C#中的继承符合下列规则:

  1. 继承是可传递的。如果C从B中派生,B又从A中派生,那么C不仅继承了B中声明的成员,同样也继承了A中的成员。Object 类作为所有类的基类。

  2. 派生类应当是对基类的扩展。派生类可以添加新的成员,但不能除去已经继承的成员的定义。

  3. 构造函数和析构函数不能被继承。除此以外的其它成员,不论对它们定义了怎样的访问方式,都能被继承。基类中成员的访问方式只能决定派生类能否访问它们。

  4. 派生类如果定义了与继承而来的成员同名的新成员,就可以覆盖已继承的成员。但这并不因为这派生类删除了这些成员,只是不能再访问这些成员。

  5. 类可以定义虚方法、虚属性以及虚索引指示器,它的派生类能够重载这些成员,从而实现类可以展示出多态性。

  6. 派生类只能从一个类中继承,可以通过接吕实现多重继承。

  7. 隐藏基类成员

  想想看,如果所有的类都可以被继承,继承的滥用会带来什么后果?类的层次结构体系将变得十分庞,大类之间的关系杂乱无章,对类的理解和使用都会变得十分困难。有时候,我们并不希望自己编写的类被继承。另一些时候,有的类已经没有再被继承的必要。C#提出了一个

  • 密封类(sealedclass)的概念,帮助开发人员来解决这一问题。
      密封类在声明中使用sealed修饰符,这样就可以防止该类被其它类继承。如果试图将一个密封类作为其它类的基类,C#将提示出错。

理所当然,密封类不能同时又是抽象类,因为抽象总是希望被继承的。

在哪些场合下使用密封类呢?密封类可以阻止其它程序员在无意中继承该类。而且密封类可以起到运行时优化的效果。实际上,密封类中不可能有派生类。如果密封类实例中存在虚成员函数,该成员函数可以转化为非虚的,函数修饰符virtual 不再生效。
eg:

abstract class A

   {

       public abstract void F( ) ;

   }

   sealed class B: A

   {

       public override void F( )

       { // F 的具体实现代码 }

   }

  如果我们尝试写下面的代码

    class C: B{ }

  C#会指出这个错误,告诉你B是一个密封类,不能试图从B 中派生任何类。
  

  •  使用 new 修饰符可以显式隐藏从基类继承的成员。若要隐藏继承的成员,请使用相同名称在派生类中声明该成员,并用 new 修饰符修饰它。

  面向对象程序设计中的另外一个重要概念是多态性。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。可以把一组对象放到一个数组中,然后调用它们的方法,在这种场合下,多态性作用就体现出来了,这些对象不必是相同类型的对象。当然,如果它们都继承自某个类,你可以把这些派生类,都放到一个数组中。如果这些对象都有同名方法,就可以调用每个对象的同名方法。

同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,这就是多态性。多态性通过派生类重载基类中的虚函数型方法来实现。

在面向对象的系统中,多态性是一个非常重要的概念,它允许客户对一个对象进行操作,由对象来完成一系列的动作,具体实现哪个动作、如何实现由系统负责解释。

“多态性”一词最早用于生物学,指同一种族的生物体具有相同的特性。在C#中,多态性的定义是:同一操作作用于不同的类的实例,不同的类将进行不同的解释,最后产生不同的执行结果。C#支持两种类型的多态性:

  • 编译时的多态性

编译时的多态性是通过重载来实现的。对于非虚的成员来说,系统在编译时,根据传递的参数、返回的类型等信息决定实现何种操作。

  • 运行时的多态性

运行时的多态性就是指直到系统运行时,才根据实际情况决定实现何种操作。C#中,运行时的多态性通过虚成员实现。

编译时的多态性为我们提供了运行速度快的特点,而运行时的多态性则带来了高度灵活和抽象的特点。

静多态

通过

  1. 函数重载
  2. 运算符重载

实现

动态多态

通过

  1. 抽象类
  2. 虚方法

实现

虚方法

当类中的方法声明前加上了virtual修饰符,我们称之为虚方法,反之为非虚。使用了virtual修饰符后,不允许再有static, abstract,或override 修饰符。

eg:带有虚方法的类

using System ;

public class DrawingBase

{

    public virtual void Draw( )

    {

        Console.WriteLine("这是一个虚方法!") ;

    }

}
虚方法与非虚方法的区别
using System ;

class A

{

   public void F( )

   {

       Console.WriteLine("A.F") ;

   }

   public virtual void G( )

   {

       Console.WriteLine("A.G") ;

   }

}

class B: A

{

   new public void F( )

   {

       Console.WriteLine("B.F") ;

   }

   public override void G( )

   {

       Console.WriteLine("B.G") ;

   }

}

class Test

{

   static void Main( )

   {

      B b = new B( ) ;

      A a = b;

      a.F( ) ;

      b.F( ) ;

      a.G( ) ;

      b.G( ) ;

   }

}

A 类提供了两个方法:非虚的F 和虚方法G 。类B 则提供了一个新的非虚的方法F, 从而覆盖了继承的F; 类B 同时还重载了继承的方法G 。那么输出应该是:A.F B.F B.G B.G
注意到本例中,方法a.G(实际调用了B.G,而不是A.G,这是因为编译时值为A,但运行时值为B ,所以B 完成了对方法的实际调用。在派生类中对虚方法进行重载

注意:
普通的方法重载指的是:类中两个以上的方法(包括隐藏的继承而来的方法),取的名字相同,只要使用的参数类型或者参数个数不同,编译器便知道在何种情况下应该调用哪个方法。

而对基类虚方法的重载是函数重载的另一种特殊形式。在派生类中重新定义此虚函数时,要求的是方法名称,返回值类型、参数表中的参数个数、类型顺序都必须与基类中的虚函数完全一致。在派生类中声明对虚方法的重载,要求在声明中加上override 关键字,而且不能有new,static 或virtual 修饰符。

多态性的栗子:

eg:

using System ;

class Vehicle//定义汽车类

{

   public int wheels; //公有成员轮子个数

   protected float weight; //保护成员重量

   public Vehicle(int w,float g)

   {

       wheels = w;

       weight = g;

   }

   public virtual void Speak( )

   {

      Console.WriteLine( " the w vehicle is speaking!" ) ;

   }

};

class Car:Vehicle //定义轿车类

{

   int passengers; //私有成员乘客数

    public Car(int w,float g,int p) : base(w,g)

    {

        wheels = w;

        weight = g;

        passengers = p;

    }

    public override void Speak( )

    {

       Console.WriteLine( " The car is speaking:Di-di!" ) ;

    }

}

class Truck:Vehicle //定义卡车类

{

    int passengers; //私有成员乘客数

    float load; //私有成员载重量

    public Truck (int w,float g,int p, float l) : base(w,g)

    {

        wheels = w;

        weight = g;

        passengers = p;

        load = l;

    }

    public override void Speak( )

    {

        Console.WriteLine( " The truck is speaking:Ba-ba!" ) ;

    }

    public static void Main( )

    {

        Vehicle v1 = new Vehicle(0,0 ) ;

        Car c1 = new Car(4,2,5) ;

        Truck t1 = new Truck(6,5,3,10) ;

        v1.Speak( ) ;

        v1 = c1;

        v1.Speak( ) ;

        c1.Speak( ) ;

        v1 = t1;

        v1.Speak( ) ;

        t1.Speak( ) ;

    }

}
  • Vehicle 类中的Speak 方法被声明为虚方法,那么在派生类中就可以重新定义此方法。

  • 在派生类Car 和Truck 中分别重载了Speak 方法,派生类中的方法原型和基类中的方法原型必须完全一致。

  • 在Test 类中,创建了Vehicle 类的实例v1, 并且先后指向Car 类的实例c1 和Truck 类的实例t1。

  • 运行该程序结果应该是:

The Vehicle is speaking!

The car is speaking:Di-di!

The car is speaking:Di-di!

The truck is speaking:Ba-ba!

The truck is speaking:Ba-ba!

  • 这里,Vehicle 类的实例v1 先后被赋予Car类的实例c1, 以及Truck类的实例t1的值。在执行过程中,v1先后指代不同的类的实例,从而调用不同的版本。这里v1 的Speak 方法实现了多态性,并且v1.Speak究竟执行哪个版本,不是在程序编译时确定的,而是在程序的动态运行时,根据v1 某一时刻的指代类型来确定的,所以还体现了动态的多态性。
posted @ 2017-02-09 20:42  c_yh  阅读(268)  评论(0编辑  收藏  举报