C#这种完全面向对象的程序设计语言提供了两个重要的特性:继承和多态
简单定义
不同的对象对同一消息作出不同的响应就是多态。
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用已存在的类的功能。
下面用实例来理解
1最简单的继承
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//在c#中新建控制台程序,将代码拷贝过去就可以实现
class Duck
{
public void Swim()
{
Console.WriteLine("鸭子都会游泳");
}
}
class RealDuck : Duck
{
}
class XPDuck : Duck
{
}
class MuDuck : Duck
{
}
class Program
{
//主函数入口
static void Main(string[] args)
{
RealDuck rd = new RealDuck();
MuDuck md = new MuDuck();
XPDuck xd = new XPDuck();
rd.Swim();//子类调用父类的
md.Swim();
xd.Swim();
Console.ReadKey();
}
}
输出结果:
上面就是一个最简单的继承的例子,子类通过继承父类,调用父类的方法,这样不必在每个子类都去写“Swim()”这个方法
2.但是上面例子并没有体现出多态,下面对例子进行修改
class Duck
{
public void Swim()
{
Console.WriteLine("鸭子都会游泳");
}
}
class RealDuck : Duck
{
public new void Swim()
{
Console.WriteLine("真鸭子在游泳");
}
}
class XPDuck : Duck
{
public new void Swim()
{
Console.WriteLine("橡皮鸭子在游泳");
}
}
class MuDuck : Duck
{
public new void Swim()
{
Console.WriteLine("木头鸭子在游泳");
}
}
class Program
{
//主函数入口
static void Main(string[] args)
{
RealDuck rd = new RealDuck();
MuDuck md = new MuDuck();
XPDuck xd = new XPDuck();
rd.Swim();
md.Swim();
xd.Swim();
Console.ReadKey();
}
}
输出结果:
这里只是从效果上实现了多态,即实现了不同的对象对同一消息作出不同的响应。
(以下先不用深究,只用理解了上面多态就够了,具体new和override差别在后面6中单独详解)
在C#中:一般用override重写,是指对父类中的虚方法(标记为override)或抽象方法(标记为abstract)进行重写,实现新的功能,它必须与父类方法的签名完全一致,而且与父类方法的可访问性也必须一致
这里的new方法,是指在子类中重新定义一个签名与父类的方法相同的方法,这个方法可以不用new修饰,只是编译时会弹出一个警告信息:如果是有意隐藏,请使用关键字 new。
3.override重写实现多态
这个例子将更加规范的实现多态
要注意两点:
(1)父类要允许子类重写,方法必须标识为virtual 或 abstract
(2)重写方法 必须用override标识
class Duck
{
// 要允许子类重写,方法必须标识为virtual 或 abstract
public virtual void Swim()
{
Console.WriteLine("鸭子都会游泳");
}
}
class RealDuck : Duck
{
// 重写方法 必须用override标识
public override void Swim()
{
Console.WriteLine("真鸭子在游泳");
}
}
class XPDuck : Duck
{
public override void Swim()
{
Console.WriteLine("橡皮鸭子在游泳");
}
}
class MuDuck : Duck
{
public override void Swim()
{
Console.WriteLine("木头鸭子在游泳");
}
}
class Program
{
//主函数入口
static void Main(string[] args)
{
RealDuck rd = new RealDuck();
MuDuck md = new MuDuck();
XPDuck xd = new XPDuck();
rd.Swim();
md.Swim();
xd.Swim();
Console.ReadKey();
}
}
输出结果:
这样我们就实现了一个标准的多态。这里用的是virtual,并没有用abstract,下面介绍用abstract类
4 abstract重写实现多态
这里我只谈谈我个人的见解,上面那个例子并不完全贴切抽象类,为了更好理解抽象类的多态,我这里举另外一个例子
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace 抽象类练习
{
class Program
{
static void Main(string[] args)
{
Shape circle = new Circle(5);
Shape square = new Square(3,4);
Console.WriteLine("圆面积:"+circle.GetArea());
Console.WriteLine("矩形面积:"+square.GetArea());
Console.ReadKey();
}
}
abstract class Shape
{
//抽象成员只能存在于抽象类中
public abstract double GetArea();
}
class Circle : Shape
{
public double R { get; set; }
//构造函数,赋值圆半径
public Circle(double r)
{
this.R = r;
}
public override double GetArea()
{
return this.R * this.R * Math.PI;
}
}
class Square : Shape
{
public double Chang { get; set; }
public double Kuan { get; set; }
//构造函数,赋值矩形的长和宽
public Square(double c, double k)
{
this.Chang = c;
this.Kuan = k;
}
public override double GetArea()
{
return this.Chang * this.Kuan;
}
}
}
这个例子实现了计算圆和矩形的面积,多变形是父类,所有多边形都能够计算面积,但是计算面积的参数以及方法都不一样,这样我们把父类定义为抽象类,在子类中重写计算方法
这里我个人理解为什么说这个用抽象类更贴切,因为这里只有实例化了圆和矩形在计算面积时没有公用部分,只有实例化之后,才能去计算面积,否则没有意义,而抽象类语法中有一个规定:抽象类不能被实例化(不能被new创建),所以这里正好用抽象类
5 接口
接口是方法的抽象,如果不同的类有同样的方法,那么就应该考虑使用接口。这里看其实接口和继承差不多(这里我就不做深入理解了,只是简单使用一下接口),因为C#是单继承,接口是解决C#里面类可以同时继承多个基类的问题。
namespace 接口练习
{
class Program
{
static void Main(string[] args)
{
//真鸭子嘎嘎叫,橡皮鸭子唧唧叫,木头鸭子不会叫。 但是他们都会游泳。
//非抽象类可以实例化
Duck dk = new Duck();
dk.Swim();
XPDuck xd = new XPDuck();
//声明父类去指向子类对象
IBark bark1 = xd;
bark1.Bark();
IBark bark2 = rd;
bark2.Bark();
Console.ReadKey();
}
}
class Duck
{
public void Swim()
{
Console.WriteLine("鸭子都会游泳");
}
}
//抽象类中可以有非抽象方法。接口中则不能有实现方法
interface IBark
{
void Bark();
}
class RealDuck : Duck,IBark
{
public void Bark()
{
Console.WriteLine("真的鸭子嘎嘎叫");
}
}
class XPDuck : Duck, IBark
{
void IBark.Bark()
{
Console.WriteLine("橡皮鸭子唧唧叫");
}
}
class MuDuck : Duck
{
}
}
输出结果:
这里实现接口的时候和继承不一样,这里通过声明父类去指向子类对象,实现子类的功能。
接口就更像是一种能力,真鸭子和橡皮鸭子具有这种能力,所以实现了这个接口,而木头鸭子就没实现。
当然,这个其实也可以用继承类来实现,但是因为已经继承了Duck类,所以这里用了接口,接口和抽象类在很多地方都是很像的。这里简单区分一下接口和抽象类:
(1)abstract class 在c#语言中表示的是一种继承关系,一个类只能使用一次继承关系。但是,一个类却可以实现多个interface。
(2)在abstract class 中可以有自己的数据成员,也可以有非abstarct的成员方法,而在interface中,只能够有静态的不能被修改的数据成员(也就是必须是static final的,不过在 interface中一般不定义数据成员),所有的成员方法都是abstract的。
(3)abstract class和interface所反映出的设计理念不同。其实abstract class表示的是"is-a"关系,interface表示的是"like-a"关系。
(4)实现抽象类和接口的类必须实现其中的所有方法。抽象类中可以有非抽象方法。接口中则不能有实现方法。
(5)接口中的方法默认都是 public,abstract 类型的。
6 补充:C#中override重写与new隐藏的区别
直接看代码
class A //父类
{
public virtual String getVal(){
return "A";
}
}
class B : A
{
// 隐藏父类方法 用new标识,不标识时会出现编译警告,不影响使用
public new String getVal(){
return "B";
}
}
class C : A
{
// 重写方法 必须用override标识
public override String getVal(){
return "C";
}
}
public class Program
{
//主函数入口
public static void Main(String[] args){
//实例1
A a1 = new B();
Console.WriteLine(a1.getVal()); // 输出:B 定义是A,实例是B,执行B的方法
//实例2
B b = new B();
Console.WriteLine(b.getVal()); // 输出:B 定义是B,实例是B,执行B的方法
//实例3
A a2 = new C();
Console.WriteLine(a2.getVal()); // 输出:A 虽然实例是C,但是执行的却是A的方法
//实例4
C c = new C();
Console.WriteLine(c.getVal());// 输出: C 只有定义和实例都是C,才执行C的方法
Console.ReadKey();
}
}
输出结果:
B继承了A并对A类的方法进行了override重写,C继承了A对方法进行了重定义。
首先对比实例1和实例2,只要实例是B,无论定义是A还是B,都执行B的方法,这正是我们重写想要的结果
而对比实例3和实例4,实例3中虽然实例了C,但是此时如果拿父类A来接收,那么执行的就是父类的方法;仅当实例和定义都是C的时候,才会执行C