静态方法、实例方法和虚方法的区别

基础知识

对于面向对象的语言来说,所有类型都是从System.Object类型派生,正是由于这个原因,保证了每个类型都有一组最基本的方法,也就是从他们的父类System.Object继承古来的方法,Object的定义如下面的代码所示,System.Object所定义的基本方法中基本包含了CLR所有的方法类型,静态方法(Static修饰,属于类成员)、虚方法(Virtural修饰,属于实例成员)、实例方法(普通的方法,属于实例成员)。可能会有人说还有抽象方法,其实抽象方法最后的编译也是一个虚方法。

CLR的最重要的特性之一就是类型安全性,在运行时,CLR总是知道一个对象是什么类型,我们看到Object中有一个GetType方法,这个方法总是能知道一个对象的确切类型,由于GetType是一个非虚实例方法,从而保证了派生类型不能重写它,所以一个类型不可能伪装成另一个类型,其实如果我们要有意的隐藏也是可以做到的(我们可以使用New关键字覆盖GetType方法),不过一般我们不推荐这样做。

那么GetType方法是如何返回一个对象的真实类型的呢?这就要引入一个新的概念,也是是“类型对象”,当我们使用New关键字在托管堆上创建一个对象的时候,大致做了一下几件事情:

 class Program
    {
        static void Main(string[] args)
        {

            Person p = new Person("Aseven");

            Console.ReadKey();

        }
    }
    public class Person
    {
        private string _name;
        private int _age;

        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }
        public virtual void Say()
        {
            Console.WriteLine("******");
        }
        public static Person Find(string name)
        {
            return new Person(name);//模拟数据库查找
        }
        public int GetAge()
        {
            return _age;
        }
        public Person() { }
        public Person(string name)
        {
            this._name = name;
        }
    }

1、计算类型和所有基类型(直到System.Object,虽然它没有定义实例字段)的所有实例字段(注意:没有静态字段和方法)所需要的字节数,堆上的每个对象都需要一些额外的成员---即类型对象指针和同步索引块,这些成员由CLR用于管理对象,也会计入对象大小。

2、从托管堆上分配对象的内存,并把字段初始化为零(0)。

3、初始化对象的“类型对象指针”和“同步索引块”。

4、执行实例构造函数,并向其传入在对new的调用中指定的实参(上例中的“Aseven”),大多数编译器会自动生成代码来调用基类的构造器,最终调用的而是System.Object的构造器。

在New执行了之后,会返回对堆中对象的一个引用(或指针),对上例来说,这个引用(地址)保存在变量e中,创建完这个Person对象之后,内存结构大致如下,可以看到类型对象指针指向的就是person的类型对象。

总结:一个实例对象创建之后,变量e保存了托管堆中的person对象的一个引用(指针)。而person对象指示保存了对象的一个实例字段(包括类型对象指针和同步索引块),至于静态字段、方法列表都保存在person的类型对象中,特别注意的是方法的列表,这个列表包含了静态方法、实例方法、虚方法,下面我们就来介绍对于这三种方法是如何调用的。

方法的调用

1、静态方法:当调用一个静态方法时,CLR会定位与定义静态方法对应的类型对应的类型对象(有点绕)。然后在类型对象的方法列表中查找对应的记录项,进行JIT编译(如果需要),然后调用。

2、实例方法:当调用一个非虚实例方法时,JIT编译器会找到发出调用的那个变量(p)对应的类型对应的类型对象,如果类型对象的方法列表中没有包含那个被调用的方法,JIT编译器会回溯类层次结构(一直回溯到Object),并在沿途的每个类型的方法集合中查找此方法,之所以能这样回溯,是因为每个类型对象都有一个字段引用了他的积累性,这个信息在途中没有显示。

3、虚方法:当调用一个虚方法时,会生成一些额外的代码,方法每次调用时,都会执行这些代码,这些代码首先检查发出调用的变量,然后会跟随地址(也就是我们说的p中保存的对象的指针)来到发出调用的对象,然后代码检查对象的内部的“类型对象指针”成员,这个成员指向对象的类型对象,然后代码在类型对象的方法集合中查找该方法,进行JIT编译(如果需要的话),在调用JIT编译的代码。如果没有则也会向上回溯查找基类中定义的方法。

下面用示例进行介绍:

class Program
    {
        static void Main(string[] args)
        {

            Person p = new Person("test1");
            p = Person.Find("Aseven");
            int Age = p.GetAge();
            p.Say();
            Console.ReadKey();

        }
    }
    public class Person
    {
        private string _name;
        private int _age;

        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }
        public virtual void Say()
        {
            Console.WriteLine("******");
        }
        public static Person Find(string name)
        {
            return new Chinese(name);//模拟数据库查找
        }
        public int GetAge()
        {
            return _age;
        }
        public Person() { }
        public Person(string name)
        {
            this._name = name;
        }
    }

    public class Chinese : Person
    {
        public Chinese(string name)
        {
            this.Name = name;
        }
        public override void Say()
        {
            Console.WriteLine("你好!");
        }
    }
    public class American : Person
    {
        public American(string name)
        {
            this.Name = name;
        }
        public override void Say()
        {
            Console.WriteLine("Hello!");
        }
    }

1、首先我们定义Person对象,Person p=new Person();这句代码执行之后和上面的内存分配基本类似。

2、我们调用Person的静态方法,p=Person.Find("Aseven");根据上面的定义,调用一个静态方法时会直接查找类型对象的方法列表,直接调用,调用之后我们看到Find方法中直接返回了一个Chinese对象,这会在托管堆中创建一个chinese对象,并且把地址存储在变量P中,这时P中保存的不在是Person对象的地址,而是Chinese对象的地址(当然也可能会是一个American对象,如果Find返回返回的是一个American对象)。

3、然后我们调用p.GetAge()的一个非虚实例方法,当CLR调用一个非虚的实例方法时,会根据发出调用者(P)的类型(Person)的类型对象(Person类型对象)去查找GetAge方法,如果找不到则回溯基类查找。这里由于Person的类型对象的方法集合中有这个方法,所以直接调用。将返回的结果(这里是0)存储到线程栈的一个Age的变量中。

4、调用P.Say()方法,Say方法是一个虚方法,CLR会根据发出调用者(P)的地址(这里是指向Chinese对象的一个指针)找到托管堆中真实的对象(chinese对象),然后根据托管堆中的对象去找到真实的类型对象(这里是Chinese类型对象),并且遍历方法集合查找Say方法,(如遇Chinese类重写了Say方法,所以Chinese类型对象的方法集合中有这个方法)进行调用。

这是内存的分配大致如下:由于person对象已经没有其它对象引用了,那么它将是下次垃圾回收的重点对象。

测试Demo

public class A

    {

        public void MethodF() 

        { 

            Console.WriteLine("A.F"); 

        }

        public virtual void MethodG() 

        { 

            Console.WriteLine("A.G"); 

        }

    }

    public class B : A

    {

        new public void MethodF() 

        { 

            Console.WriteLine("B.F"); 

        }

        public override void MethodG() 

        { 

            Console.WriteLine("B.G"); 

        }

    }

    class Test

    {

        static void Main()

        {

            B b;

            b = new B();

            A a = b;

            a.MethodF();

            b.MethodF();

            a.MethodG();

            b.MethodG();

        }

输出结果:A.F、B.F、B.G、B.G

1、首先MethodF是一个非虚实例方法,这时候我们用a.MethodF();由于a是A类型的实例,所以输出的是A.F。

2、接着调用b.MethodF(),因为b是一个B类型的实例,且B重写了A的MethodF方法,那么在B类型对象的类型对象的方法表中就已经有了MethodF方法,会直接调用,所以输出的是B.F

3、由于MethodG是一个虚方法,我们用a.MethodG调用的时候,首先会根据a中保存的地址(指针)找到托管堆中的具体对象,然后根据具体对象找到真实的类型对象,这里a中保存的是一个b的实例,所以对象的类型对象也就是B的类型对象的类型对象,

调用的时候则会直接查找B类型对象的类型对象中的方法集合,查找MethodG方法并调用,所以输出的是B.G

4、对于b.MehtodG,首先会根据b中保存的地址(指针)找到托管堆中的具体对象,然后根据具体对象找到真实的类型对象,这里b中保存的是一个b的实例,所以对象的类型对象也就是B的类型对象的类型对象,调用的时候则会直接查找B类型对象的类型对象        中的方法集合,查找MethodG方法并调用,所以输出的是B.G

总结

1、方法的调用都是通过查找类型对象中的方法集合来实现的。

2、静态方法直接查找类型对象中方法结合进行调用。

3、非虚实例方法是根据发出调用者(对于上面的Demo,线程栈中的变量a、b是发出调用者)的类型去产找对应的类型对象,然后查找该类型对象的方法集合进行调用,没有找到则回溯基类进行查找。

4、虚方法是根据发出调用者(对于上面的Demo,线程栈中的变量a、b是发出调用者)的地址找到托管堆中的具体对象,然后根据对象去查找真实的类型对象,再根据类型对象去查找方法集合进行的。

时间: 2024-10-13 22:28:04

静态方法、实例方法和虚方法的区别的相关文章

OO 抽象方法与虚方法的区别

 抽象方法与虚方法的区别 抽象方法与虚方法的区别: 一.抽象方法: 1.只能在抽象类中定义: 2.不能有方法的实现:(方法体)[因为抽象类无法实例化,所以抽象方法没有办法被调用,也就是说抽象方法永远不可能被实现.] 3.被子类继承后,必须重写: 4.使用关键字abstract修饰: 二.虚方法: 1.可以在抽象类和普通类中定义: 2.可以有方法的实现(方法体,即使空的也要声明) 3.被子类继承后,可以重写也也可以不重写: 4.使用关键字virtrual修饰:

对抽象类和虚方法的区别

抽象类定义:它的作用就是产生子类的同时给于子类一些特定的属性和方法.abstract修饰符可以和类.方法.属性.索引器及事件一起使用.在类声明中使用abstract修饰符以指示某个类只能是其他类的父类.标记为抽象或包含在抽象类中的成员必须通过从抽象类的子类来实现. 特性:1.抽象类不能被实例化:2.抽象类可以包含抽象方法和抽象访问器:3.不能用sealed修饰符修饰:4.从抽象类派生的非抽象类必须包括继承的所有抽象方法和抽象访问器的实现. 总结:~抽象方法是隐式的虚方法:~只容许在抽象类中使用抽

浅谈接口、抽象类、抽象方法和虚方法的区别

C#抽象类和接口孩子间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,因此很多开发者在进行抽象类定义时对于抽象类和接口的选择显得比较随意.其实,两者之间还是有很大区别的. 首先说一下接口与抽象类的异同: 相同点: 1.他们都不能实例化自己,也就是说都是用来被继承的. 2.抽象类中的抽象方法和接口方法一样,都不能有方法体 不同点: 1.抽象类里可以有字段,接口中不能有字段. 2.抽象类中的普通方法可以有方法体,而接口方法没有方法体. 3.接口中的方法不能有访问修饰符,抽象类中的抽象方

C#中抽象方法与虚方法的区别

一.抽象方法:只在抽象类中定义,方法修饰符不能使用private,virtual,static. 抽象方法如下示: public abstract class People   //声明一个抽象类 { public abstract void study();  //抽象方法只能定义在抽象类中. } public class Student:People   //继承抽象类 { public  override void study()     //重写抽象类的抽象方法 { Console.Wr

C# 抽象方法和虚方法的区别

一.抽象方法:只在抽象类中定义,方法修饰符不能使用private,virtual,static. (1)象方法只能声明在抽象类中,使用关键字abstract (2)抽象类中的抽象方法必须被子类重写. [抽象方法没有方法体,子类必须重写方法体!!,因此抽象方法可以看成是一个没有方法体的虚方法] 二.虚方法:使用virtual修饰的方法: 虚方法可以有方法体. 注意事项:virtual修饰符不能与private.static.abstract.override修饰符同时使用. ps:override

抽象方法和虚方法的区别

1.抽象类关键字 abstract 2.抽象类不能实例化 3.抽象方法没有方法体 4.子类实现抽象方法的快捷键 Ctrl+.+Enter 5.一个类如果继承抽象类,那么重写重写类中的所有方法 7.抽象类不能是静态类和密封类 8.抽象类方法只能存在于抽象类中 9.抽象类中抽象方法只是用来规定子类方法的形式(参数,返回值)约束子类方法的形式

虚方法

当类中的方法声明前加上了virtual   修饰符,我们称之为虚方法,反之为非虚.使用了virtual   修饰符后,不允许再有static,   abstract,   或override   修饰符.       示例1:带有虚方法的类       using   System   ;       public   class   DrawingBase       {       public   virtual   void   Draw(   )       {   Console.W

关于虚方法

1.什么是虚方法 虚方法可以有实现体,若一个实例方法的声明中含有virtual修饰符,则称该方法为虚方法.使用了virtual修饰符后,不允许再有static.abstract或者override修饰符. 2.虚方法的调用 在一个虚方法调用中,该调用所设计的那个实例运行时的类型确定了要被调用的究竟是该方法的哪一个实现.(类比java中的动态绑定) 3.抽象方法和虚方法的区别 4.java中关于虚方法的说明 原文地址:https://www.cnblogs.com/quanhaijie/p/121

虚方法、抽象类

虚方法: 1.virtual方法表示此方法可以被重写, 也就是说这个方法具有多态.父类中的方法是通用方法,可以在子类中重写以重新规定方法逻辑. 2.virtual方法可以直接使用,和普通方法一样 3.不是必须重写的. 子类可以使用base.方法 的方式调用, 无论有没有在子类使用override去重写 virtual关键字只是明确标示此方法可以被重写, 其实它和一般的方法没有什么区别 相应的 sealed关键字标示此方法不可以被重写 虚方法和抽象方法的区别: 1.虚方法可以有实现体,抽象方法不能