c#笔记整理 关于继承与多态等
【 塔 · 第 二 条 约 定 】
c#面向对象基础
整理private、protected、public、abstract等的异同
-
public 公有访问。不受任何限制。
-
private 私有访问。只限于本类成员访问,子类,实例都不能访问。
-
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; 不能访问 如果是子类则可以访问
}
- 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#中的继承符合下列规则:
-
继承是可传递的。如果C从B中派生,B又从A中派生,那么C不仅继承了B中声明的成员,同样也继承了A中的成员。Object 类作为所有类的基类。
-
派生类应当是对基类的扩展。派生类可以添加新的成员,但不能除去已经继承的成员的定义。
-
构造函数和析构函数不能被继承。除此以外的其它成员,不论对它们定义了怎样的访问方式,都能被继承。基类中成员的访问方式只能决定派生类能否访问它们。
-
派生类如果定义了与继承而来的成员同名的新成员,就可以覆盖已继承的成员。但这并不因为这派生类删除了这些成员,只是不能再访问这些成员。
-
类可以定义虚方法、虚属性以及虚索引指示器,它的派生类能够重载这些成员,从而实现类可以展示出多态性。
-
派生类只能从一个类中继承,可以通过接吕实现多重继承。
-
隐藏基类成员
想想看,如果所有的类都可以被继承,继承的滥用会带来什么后果?类的层次结构体系将变得十分庞,大类之间的关系杂乱无章,对类的理解和使用都会变得十分困难。有时候,我们并不希望自己编写的类被继承。另一些时候,有的类已经没有再被继承的必要。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#中,运行时的多态性通过虚成员实现。
编译时的多态性为我们提供了运行速度快的特点,而运行时的多态性则带来了高度灵活和抽象的特点。
静多态
通过
- 函数重载
- 运算符重载
实现
动态多态
通过
- 抽象类
- 虚方法
实现
虚方法
当类中的方法声明前加上了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 某一时刻的指代类型来确定的,所以还体现了动态的多态性。