zoukankan      html  css  js  c++  java
  • 【基础】理解接口、抽象类及虚函数

    一、前面的话

    对于C#中的接口、抽象类中的抽象方法以及虚方法的比较,网上有很多的例子,讲得也很到位,这篇博文的目的一方面是为了巩固自己的基础知识,另一方面是能够让初学者更加深刻、轻松地去理解和接受这三者之间的区别与联系。

    二、接口与抽象类

    首先说说接口和抽象类的区别与联系,接口是什么?接口是一组规则,它对应着自然界中“如果你是……则必须能……”的思想,如:猫猫是动物,动物可以吃食物、可以移动,也就是说某个类实现了某个接口,就必须实现该接口的规则(方法),如果接口中的方法(规则)太多,可以考虑接口分离,接口是可以继承的,当然这不是本篇文章的重点。既然接口是一组规则,那么它的作用就是约束其他实现了该接口的类,而自己本身不能实例化。既然接口是一组规则,那么这个规则是什么样呢?就是包含了一系列的方法,在具体一点就是没有修饰符和方法体的方法。好了,现在在重新理解“接口是一组规则”这句话,接口就是包含了一系列没有修饰符和方法体的方法的集合,这就是接口。结合代码看一下:

    1     interface IAnimal
    2     {
    3         //public void run();
    4         //The modifier 'public' is not valid for this item
    5         void run();
    6         void eat();
    7         string speak();
    8     }

    接口中的方法不能有修饰符和方法体,如果加上修饰符就会报错(第4行)。而实现了该接口的类必须实现其中的run、eat、speak方法。此外,接口中不能有字段。

     1     class Cat:IAnimal
     2     {
     3         #region IAnimal Members
     4 
     5         public void run()
     6         {
     7             Console.WriteLine("run run!");
     8         }
     9 
    10         public void eat()
    11         {
    12             Console.WriteLine("delicious~");
    13         }
    14 
    15         public string speak()
    16         {
    17             return "miao~";
    18         }
    19 
    20         #endregion
    21     }

    Cat类实现了IAnimal接口,那么它必须实现IAnimal中的所有方法(规则)。

    理解了接口,抽象类就比较简单了,与接口相同的是,抽象类的抽象方法不能有方法体,都是需要子类来实现具体的逻辑(如Cat中的eat方法)。但是,抽象类中可以有字段,抽象类其实就是一个特殊的普通类,只要有抽象方法,那么这个类就是抽象类。

     1     abstract class AbstractCat
     2     {
     3         //field
     4         private double weight;
     5         //abstract method
     6         public abstract void eat();
     7         //normal method
     8         public void run()
     9         {
    10             Console.WriteLine("run run!");
    11         }
    12     }
    13 
    14     class BlackCat:AbstractCat
    15     {
    16         //Implement abstract method
    17         public override void eat()
    18         {
    19             Console.WriteLine("delicious!");
    20         }
    21     }

    三、抽象方法与虚方法

    我们可以把抽象方法看成没有方法体的虚方法,二者都可以被子类重写,并以override关键字修饰。不同的是,虚方法必须有方法体

     1     abstract class AbstractCat
     2     {
     3         //field
     4         private double weight;
     5         //abstract method
     6         public abstract void eat();
     7         //normal method
     8         public void run()
     9         {
    10             Console.WriteLine("run run!");
    11         }
    12     }
    13 
    14     class BlackCat:AbstractCat
    15     {
    16         //Implement abstract method
    17         public override void eat()
    18         {
    19             Console.WriteLine("delicious!");
    20         }
    21 
    22         public virtual string speak()
    23         {
    24             return "Miao!";
    25         }
    26 
    27         //public virtual void run();
    28     }
    29 
    30     class ChinaBlackCat : BlackCat
    31     {
    32         public override string speak()
    33         {
    34             return base.speak();
    35         }
    36     }

    四、细说虚方法

    虚方法也就是虚拟函数,我们习惯称之为虚函数,虚函数从C#的程序编译的角度来看,它和其它一般的函数有什么区别呢?一般函数在编译时就静态地编译到了可执行文件中,其相对地址在程序运行期间是不发生变化的,也就是写死了的!而虚函数在编译期间是不被静态编译的,它的相对地址是不确定的,它会根据运行时期对象实例来动态判断要调用的函数,其中那个声明时定义的类叫声明类,执行时实例化的类是实例类。虚函数被调用时会执行下列检查:

    1、当调用一个对象的函数时,系统会直接去检查这个对象申明定义的类,即申明类,看所调用的函数是否为虚函数;
    2、如果不是虚函数,则直接执行该函数。而如果有virtual关键字,也就是一个虚函数,那么这个时候它就不会立刻执行该函数了,而是转去检查对象的实例类;
    3、在这个实例类中,编译器会检查这个实例类的定义中是否有重写该虚函数(通过override关键字),如果有,那么编译器不会再继续寻找父类,而马上执行该实例类中的这个重新实现的函数。而如果没有的话,编译器就会不停地往上找实例类的父类,并对父类重复刚才在实例类里的检查,直到找到第一个重载了该虚函数的父类为止,然后执行该父类里重载后的函数。

    理解上面的规则就不难判断如下代码的运行结果了:

     1 namespace VirtualMethod
     2 {
     3     class Program
     4     {
     5         static void Main(string[] args)
     6         {
     7             A a;         // 定义一个a这个A类的对象.这个A就是a的申明类
     8             A b;         // 定义一个b这个A类的对象.这个A就是b的申明类
     9             A c;         // 定义一个c这个A类的对象.这个A就是b的申明类
    10             A d;         // 定义一个d这个A类的对象.这个A就是b的申明类
    11 
    12             a = new A(); // 实例化a对象,A是a的实例类
    13             b = new B(); // 实例化b对象,B是b的实例类
    14             c = new C(); // 实例化b对象,C是b的实例类
    15             d = new D(); // 实例化b对象,D是b的实例类
    16 
    17             a.Function();    // 执行a.Function:1.先检查申明类A 2.检查到是虚拟方法 3.转去检查实例类A,就为本身 4.执行实例类A中的方法 5.输出结果 Function In A
    18             b.Function();    // 执行b.Function:1.先检查申明类A 2.检查到是虚拟方法 3.转去检查实例类B,有重载的 4.执行实例类B中的方法 5.输出结果 Function In B
    19             c.Function();    // 执行c.Function:1.先检查申明类A 2.检查到是虚拟方法 3.转去检查实例类C,无重载的 4.转去检查类C的父类B,有重载的 5.执行父类B中的Function方法 5.输出结果 Function In B
    20             d.Function();    // 执行d.Function:1.先检查申明类A 2.检查到是虚拟方法 3.转去检查实例类D,无重载的(这个地方要注意了,虽然D里有实现Function(),但没有使用override关键字,所以不会被认为是重载) 4.转去检查类D的父类A,就为本身 5.执行父类A中的Function方法 5.输出结果 Function In A
    21             D d1 = new D();
    22             d1.Function(); // 执行D类里的Function(),输出结果 Function In D
    23             Console.ReadLine();
    24         }
    25 
    26 
    27         class A
    28         {
    29             public virtual void Function() // 注意virtual,表明这是一个虚拟函数
    30             {
    31                 Console.WriteLine("Function In A");
    32             }
    33         }
    34 
    35         class B : A // 注意B是从A类继承,所以A是父类,B是子类
    36         {
    37             public override void Function() // 注意override ,表明重新实现了虚函数
    38             {
    39                 Console.WriteLine("Function In B");
    40             }
    41         }
    42 
    43         class C : B // 注意C是从A类继承,所以B是父类,C是子类
    44         {
    45         }
    46 
    47         class D : A // 注意B是从A类继承,所以A是父类,D是子类
    48         {
    49             public new void Function() // 注意new ,表明覆盖父类里的同名类
    50             {
    51                 Console.WriteLine("Function In B");
    52             }
    53         }
    54 
    55     }
    56 }
  • 相关阅读:
    Android Volley入门到精通:定制自己的Request
    Android高效加载大图、多图解决方案,有效避免程序OOM
    Android Volley入门到精通:使用Volley加载网络图片
    Android Volley入门到精通:初识Volley的基本用法
    彻底理解ThreadLocal
    Android中Parcelable接口用法
    Handler详解系列(四)——利用Handler在主线程与子线程之间互发消息,handler详解
    Storm流处理项目案例
    021 使用join()将数组转变为字符串
    020 $.each的使用
  • 原文地址:https://www.cnblogs.com/xhb-bky-blog/p/4290123.html
Copyright © 2011-2022 走看看