众所周知,C#作为一门OOP(面向对象程序设计)语言,在许多地方都有与C++相似的地方,然而也有很多不同的地方。
说到面向对象,脑袋里第一反应当然就是面向对象的三大原则(java中是四大原则):
封装、继承、多态。java中还包括抽象。在此不做过多讨论。
今天要讨论的虚方法、抽象方法、抽象类、接口所有的一切都是以多态作为基础的,所以让我们聚焦多态————
多态是什么?
多态(Polymorphism)按字面的意思就是“多种状态”。在面向对象语言中,接口的多种不同的实现方式即为多态。引用Charlie Calverts对多态的描述——多态性是允许你将父对象设置成为一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作(摘自“Delphi4编程技术内幕”)。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。多态性在Object Pascal和C++中都是通过虚函数实现的。 (摘自百度百科)
用我自己的理解来说:多态就是在继承的前提下,不同对象调用相同方法却表现出不同的行为,此为多态。
关键性的一句话:多态性在C++中是通过虚函数实现的,这在C#中同样适用。但是在C#中有三种方法来体现:虚方法,抽象类,接口。
所谓的虚函数,也就是我们首先要讨论的虚方法。
I.虚方法 Virtual
虚方法存在于相对于需要实现多态的子类的父类中,同时也是最基本的实现多态的方法。
具体的语法是在父类中用virtual修饰,然后在子类中使用override进行重写。以下是一个简单易懂的例子:猫和狗都是动物,它们都会叫,但是叫声是不一样的。
1.先定义父类,只定义一个叫做Dosth的方法,代表动物的嚎叫。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CsharpTest9_10
{
class Animal
{
public Animal()
{
}
public virtual void Dosth()
{
Console.WriteLine("动物的嚎叫");
}
}
}
2.定义猫类:override重写
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CsharpTest9_10
{
class cat:Animal
{
public override void Dosth()
{
base.Dosth();
Console.WriteLine("喵");
}
}
}
3.定义狗类:override重写
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CsharpTest9_10
{
class dog:Animal
{
public override void Dosth()
{
// base.Dosth();
Console.WriteLine("汪");
}
}
}
在主程序中:
cat c1 = new cat();
dog d1 = new dog();
c1.Dosth();
d1.Dosth();
运行结果:
现在反观结果:我在主程序中调用了猫类重写的父类方法和狗类重写的父类方法。唯一的区别是在猫类中重写方法的时候,在方法体内加入了这样一句:
base.Dosth();
因此运行结果中同样输出了一次父类的方法体中的语句。由此我们可以知道,在子类重写方法的时候可以使用base.方法名来实现父类原本的函数功能。
在狗类中我屏蔽了这个语句,所以狗类仅仅输出了自己重写的方法体。
通过上面的这个例子,算是对多态已经有了最基本的了解。接下来是由虚方法引出的抽象方法以及抽象类。
II.抽象方法以及抽象类 Abstract
通过上面的例子我们知道了虚方法。存在于父类中的虚方法是有自己的方法体的,而且这些方法体是必要的,少了他们就无法完成逻辑,这种情况需要使用虚方法。
然而如果父类中的方法完全不知道去干什么(即方法体中没有必要的代码),必须要子类进行重写才有意义的话,这种情况就需要使用抽象方法。
抽象方法必须存在于抽象类中,抽象类的具体语法是类名前加上abstract。抽象方法的语法实例如下:public abstract void FUN();
仍然使用上面的例子。父类(Animal)中的Dosth方法中,并没有必要的代码,即使方法里面什么都不写对子类仍然没有什么影响。这种情况就可以使用抽象方法和抽象类。
首先需要注意的是:抽象方法没有方法体,且所有继承了抽象类的子类必须重写所有的抽象方法。
父类:
namespace CsharpTest9_10
{
abstract class Animal
{
public abstract void Dosth();
}
}
子类的代码不变。但是此时就不能使用base.方法体了,因为根本就不存在方法体。
抽象类中可以包括普通方法,并且抽象类不能被实例化。
抽象类的使用场景:
1.父类方法不知道如何去实现;
2.父类没有默认实现且不需要实例化
总的来说,抽象方法和虚方法的差别并不是很大,实现的功能都差不多。抽象类保证了每个方法都必须得到重写,我们就要根据实际需要来选择对应的方法。
III.接口 Interface
同样的,接口是从抽象类演变而来的————如果抽象类中的所有方法都是抽象方法,这个抽象类就可以叫做接口。当然,接口中的所有方法都不能有方法体。
接口中不能包含字段,但是可以包含属性。这里没有字段怎么编写属性呢?这里有一个自动属性的概念,我们将在别的博文中进行讲解。
接口中的所有成员都默认为public,这是不能被修改的,自己也不能写上去。
我们可以将接口想象为一个插件,可以用来实现一些附加功能。
代码还是使用上一个例子中的那份,首先定义接口:在program中右键新建项,选择接口。
定义如下:
namespace CsharpTest9_10
{
interface Interface1
{
void Eat();
}
}
在猫类中的接口调用:
namespace CsharpTest9_10
{
class cat : Animal,Interface1
{
public override void Dosth()
{
//base.Dosth();
Console.WriteLine("喵");
}
public void Eat()
{
Console.WriteLine("猫在进食");
}
}
}
狗类同理。
我们可以看到调用的语法就是在继承的父类后加上一个逗号,再写接口名即可。
此时在接口名上右键后点击实现接口,会自动生成接口中方法的实现。
接口的作用就是实现某些类的特殊功能。
总结
三者之间的关系,我用一张图来表示。