在c#的学习过程中,我们总是会遇到很多看着很相似的语法规则亦或是各种方法,以至于我们看到它们总是摸棱两可的,下面,我来说说我理解的抽象方法与虚方法、abstract与virtual、抽象方法与隐藏方法、抽象类与接口。
抽象方法与虚方法
要想掌握c#里面的抽象方法和虚方法,我们至少要了解它们的定义。
抽象方法:在基类中定义的,它要求继承该基类的派生类一定要重写(overried
)的方法,该方法使用关键字abstract
定义。同时记得抽象方法只能在抽象类里面定义。
虚方法:在基类中定义的,它允许在继承该基类的派生类中重写的方法(注意我这里的用词,是允许,而上面的抽象方法是一定),该方法使用关键字virtual
定义。
区别:
- 抽象方法一定要在派生类中重写该方法。虚方法可以在派生类中去重写方法,也可以不在派生类中去重写方法,这取决于程序员自己根据项目的需求。
- 抽象方法一定是在抽象类中声明的,也就是说,如果一个类包含抽象方法,那么这个类也是抽象的,应该声明为抽象类。
- 抽象方法的函数体是没有代码去实现的,或者可以直接省略掉函数体。虚方法的函数体里面是有代码去实现的,而且程序员必须去实现虚方法的函数体。
- 虚方法是可以直接被调用的,而抽象方法则不可以。
下面演示虚方法被直接调用的例子:
using System;
namespace Test
{
class A
{
public virtual void function()
{
Console.WriteLine("我是虚方方法,可以被直接调用");
}
}
class B
{
static void Main()
{
A.a = new A();
a.function();
Console.ReadKey();
}
}
}
上面代码的运行结果为:
abstract与virtual
通过上面的抽象方法和虚方法,我们肯定对abstract
和virtual
有所了解了,有的人认为我前面都写了抽象方法和虚方方法的区别了,还写abstract
和virtual
干嘛?是不是吃饱了没事做。我回答是:你猜?好了,废话不多说,直接进入正题!
abstract和virtual两个关键字都是用来修饰基类的,abstract可以修饰类,即抽象类咯,也可以修饰方法,即抽象方法咯;virtual只能修饰方法,即虚方法。派生类可以通过覆盖其基类里面abstract修饰的抽象方法或者virtual修饰的虚方法,去重写两种方法。
区别:
- abstract修饰的抽象方法必须被派生类重写,virtual修饰的虚方法可以被派生类重写(不是必须的)。
- 只有在abstract修饰的抽象类里面才能有abstract修饰的抽象方法。
- abstract修饰的抽象类是不能去实例化一个具体的对象的。
- abstract修饰的抽象方法一定不能去实现,而是要求在派生类里面去重写实现;而virtual修饰的虚方法一定是要去实现的,即使你很懒,去添加一对大括号都是允许的。
抽象方法与隐藏方法
抽象方法在这我就像不续写了,要不真的是吃饱了没事做了,其实说真的,c#里面的隐藏方法用法还是比较简单吧,一般不会和抽象方法弄混,那就来直接说隐藏方法吧
首先要了解隐藏方法是什么?
隐藏方法:它是使用new关键字定义,在派生类中定义的和基类中的某个方法具有相同名字的方法。
直接上一个例子大家理解一下吧!
using System;
namespace YinCang
{
class A
{
public void Test()
{
Console.WriteLine("我是基类A");
}
}
class B : A
{
new public void Test() //或者这样写:public new void Test()
{
Console.WriteLine("我是派生类B");
}
}
class C
{
static void Main(string[] args)
{
A a = new A();//这里是A的实例
A b = new B();//这里还是A的实例,但引用了派生类B对象--若不理解这里,看下面注解
B c = new B();//这里才是B的实例
a.Test();//基类的实例调用基类的方法----所以打印:我是基类A
b.Test();//基类的实例调用基类的方法----所以打印:我是基类A
c.Test();//派生类B的实例调用派生类的方法---所以打印:我是派生类B
Console.ReadKey();
}
}
}
/*注解:
变量a和变量b都是A的实例,但是一个指向A的对象,一个指向B对象。
因为B类型是A类型的派生类,可以理解为B类型就是A类型,即B继承A,那么B就是A(但反过来就不正确了),两者之间是一种"是"的关系,所有A b = new B();这种转换没有问题。这里和c++里面的转换关系是一摸一样的(小编还依稀记得大大学学这个知识点的时候,我被叫起来回答了这个问题)
*/
上述代码执行的结果为:
隐藏方法的注意点:
- 隐藏方法中是谁的实例就会调用谁的方法,即是基类的实例那么就会调用基类的方法,是派生类的方法则调用派生类的方法。(这里我还要多提一嘴:重写方法中派生类的变量调用派生类重写方法,基类的变量的话,要根据基类引用的是基类的实例还是派生类的实例,如果引用的是基类的实例那么调用基类的方法,如果引用的是派生类的实例则调用派生类的方法。)
- 隐藏方法可以隐藏非虚方法,还可以隐藏非虚方法的。
好了,说了这么多,我们写一个代码演示一下:抽象方法与虚方法、abstract与virtual、抽象方法与隐藏方法
using System;
namespace _27虚方法_抽象方法_隐藏方法
{
public abstract class A
{
public abstract void hFunction();//抽象方法--抽象方法必须在派生类中重写
}
public class B : A
{
public override void hFunction()//这里就是实现抽象方法的重写
{
Console.WriteLine("B类中重写A类里面的抽象方法hFunction");
}
public void jFunction()
{
Console.WriteLine("B jFunction");
}
public int jFunction(int a)
{
Console.WriteLine("{0}", a);
return a;
}
public virtual void sFunction()//virtual---虚方法是已经实现了的,可以被子类覆盖,也可以不覆盖,取决于需求。
{
Console.WriteLine("B类的虚方法");
}
}
public class C : B
{
public override void sFunction()//函数重写
{
Console.WriteLine("C类里面重写了B类的虚方法");
}
new public void jFunction()//隐藏方法
{
Console.WriteLine("C jFunction");
}
}
public class D : B
{
public override void sFunction()//函数重写
{
Console.WriteLine("D类里面重写了B类的虚方法");
}
}
class Program
{
static void Main(string[] args)
{
//B的实例
B b = new B();
//A的实例,引用派生类C对象
B b1 = new C();
//A的实例,引用派生类D对象
B b2 = new D();
/*
这里注意,变量b1和变量b2都是B的实例,但是一个指向C的对象,一个指向D的对象。
因为C类型是B类型的派生类,可以理解为C类型就是B类型,即C继承B,那么C就是B,所以 B b1 = new C();这种转换没有问题
*/
//C的实例
C c = new C();
//Dog的实例
D d = new D();
//重载
b.jFunction();
b.jFunction(100);
//重写和虚方法
b.sFunction();
b1.sFunction();
b2.sFunction();
//抽象方法 抽象方法--抽象方法必须在派生类中重写
b.hFunction();
//隐藏方法 隐藏方法中父类的实例调用父类的方法,子类的实例调用子类的方法。
b1.jFunction();//父类的实例调用父类的方法---打印:B jFunction
b2.jFunction();//父类的实例调用父类的方法---打印:B jFunction
c.jFunction();//子类的实例调用子类的方法---打印:C jFunction
Console.ReadKey();
}
}
}
上面代码的执行结果为:
抽象类与接口
抽象类:使用关键字abstract
修饰的类,抽象类包含抽象方法,抽象方法供派生类去实现。
值得注意的是:
- 不可以在抽象类外部声明一个抽象方法;
- 不可以实例化一个抽象类;
- 抽象类的前面不可以被关键字
sealed
修饰。(因为sealed
修饰的类为密封类,它是不能够被继承的,所以抽象类的前面不能够被sealed
修饰)
接口:使用关键字interface声明,接口默认就是public的。
interface IA//声明接口
{
void Function();
}
接口定义了所有类继承接口时应遵循的语法"合同",接口本身并没有实现任何功能,它只是和声明实现该接口的对象签订一个必须实现哪些行为的契约,即接口告诉你“要求是什么”,派生类要根据接口去完成“要求怎么实现”。
值得注意的是:
- 接口中不能有字段变量、构造函数,以及不能使用abstract、public、static、new等修饰符修饰;
- 当一个接口(接口1)继承了其他的接口(接口2),那么继承接口1的类需要实现所有接口的成员;(即接口2里面的成员也要实现)
- 实现接口时,必须和接口的格式保持一致;
- 接口内可以定义属性(有get和set的方法)。.
抽象类和接口的区别
- 抽象类中可以有某些方法的部分实现,接口不能实现方法,接口只是起到一个规范性的作用。抽象类的实例是它的派生类给出的,接口的实例是实现接口的类给出的;
- 抽象类是类,只能被单继承;而接口却是可以被多继承的,因此,接口也解决了c#里面类可以继承多个基类的问题;
- 抽象类用于共性,而接口用于规范;
- 抽象类中的成员可以是私有的、内部的、受保护的,而接口的成员只能被定义为公共的;
- 如果在抽象类里面添加一个方法,那么继承它的派生类同时也有了这个方法。但在接口中添加一个新方法,这就意味着实现它的类就必须重新编写这个方法。
- 抽象类中可以有方法,也可以定义非静态的类变量;但接口中只能声明方法、属性、事件、索引器。
(小编也还在学习中,若本文有错误的地方,欢迎指正!)