首页 > 代码库 > 虚方法

虚方法

当类中的方法声明前加上了virtual   修饰符,我们称之为虚方法,反之为非虚。使用了virtual   修饰符后,不允许再有static,   abstract,   或override   修饰符。   
    
示例1:带有虚方法的类   
    
using   System   ;   
    
public   class   DrawingBase   
    
{   
    
public   virtual   void   Draw(   )   
    
{   Console.WriteLine("这是一个虚方法!")   ;   }   
    
}   
    
说明:这里定义了DrawingBase类。这是个可以让其他对象继承的基类。该类有一个名为Draw(   )的方法。Draw(   )方法带有一个virtual修饰符,该修饰符表明:该基类的派生类可以重载该方法。DrawingBase类的   Draw(   )方法完成如下事情:输出语句"这是一个虚方法!"到控制台。   
    
示例2:带有重载方法的派生类   
    
using   System   ;   
    
public   class   Line   :   DrawingBase   
    
{   
    
public   override   void   Draw(   )   
    
{   Console.WriteLine("画线.")   ;   }   
    
}   
    
public   class   Circle   :   DrawingBase   
    
{   
    
public   override   void   Draw(   )   
    
{   Console.WriteLine("画圆.")   ;   }   
    
}   
    
public   class   Square   :   DrawingBase   
    
{   
    
public   override   void   Draw(   )   
    
{   Console.WriteLine("画正方形.")   ;   }   
    
}   
    
说明:上面程序定义了三个类。这三个类都派生自DrawingBase类。每个类都有一个同名Draw(   )方法,这些Draw(   )方法中的每一个都有一个重载修饰符。重载修饰符可让该方法在运行时重载其基类的虚方法,实现这个功能的条件是:通过基类类型的指针变量来引用该类。       
对于非虚的方法,无论被其所在类的实例调用,还是被这个类的派生类的实例调用,方法的执行方式不变。而对于虚方法,它的执行方式可以被派生类改变,这种改变是通过方法的重载来实现的。   
    
下面的例子说明了虚方法与非虚方法的区别。  

 

 1   class A
 2     {
 3         public void F() { Console.WriteLine("A.F"); }
 4         public virtual void G() { Console.WriteLine("A.G"); }  //定义虚方法
 5     }
 6     class B : A
 7     {
 8         new public void F() { Console.WriteLine("B.F"); }      //这里的方法则不能重写,因为基类中的F()方法不是虚方法。
 9         public override void G() { Console.WriteLine("B.G"); }   //重写虚方法
10     }
11 static void Main(string[] args)
12         {
13             FashionCoat show = new FashionCoat();
14             show.ShowCoat();
15             BusinessFactory showbusiness = new BusinessFactory();
16             showbusiness.CreatCoat().ShowCoat();
17             
18             B b = new B();
19             A a = b;      //A对象指向B对象
20             b.F();       //显示结果B.F
21             a.F();       //显示结果A.F
22             b.G();       //显示结果B.G 
23             a.G();       //显示结果B.G 实际这里调用的是不是基类中的G方法,而是派生类中重写过的G方法。
24             Console.ReadKey();
25         }

 

    
例子中,A   类提供了两个方法:非虚的F   和虚方法G   。类B   则提供了一个新的非虚的方法F,   从而覆盖了继承的F;   类B   同时还重载了继承的方法G   。那么输出

应该是:A.F   B.F   B.G   B.G   
    
注意到本例中,方法a.G(   )   实际调用了B.G,而不是A.G,这是因为编译时值为A,但运行时值为B   ,所以B   完成了对方法的实际调用  

自己想象

得出:派生类与基类,当定义基类对象指向派生类象时(基类类名 基类对象实例=派生类对象实例),当执行基类对象.函数只有定义为虚的函数(方法),派生类才能重载(调用自己定义的重载函法.)而不是虚方法时当调用基类对象.函数(这个函数名在派生类中重复定义)时,则调用的是基类中的(函数(也可理解虚方法)),而派生类中定义的被隐藏(也不是不会被执行)..   这就是定义虚方法的用处,派生类可以利用重载改变基类中的虚方法


 

当实例方法声明包含 virtual   修饰符时,称该方法为虚拟方法。不存在   virtual   修饰符时,称该方法为非虚拟方法。   
    
非虚拟方法的实现是不变的:无论是在声明它的类的实例上调用该方法还是在派生类的实例上调用,实现都是相同的。与此相反,虚拟方法的实现可以由派生类取代。取代所继承的虚拟方法之实现的过程称为重写方法   
    
在虚拟方法调用中,为其进行调用的实例的运行时类型确定要调用的实际方法实现。在非虚拟方法调用中,实例的编译时类型是决定性因素。准确地说,当在具有编译时类型  

C   和运行时类型   R   的实例(其中   R   为   C   或者从   C   派生的类)上用参数列表   A   调用名为   N   的方法时,调用按下面这样处理:     
    
首先,将重载决策应用于   C、N   和   A,以从在   C   中声明和由   C   继承的方法集中选择一个特定方法   M。     
然后,如果   M   为非虚拟方法,则调用   M。     
否则,M   为虚拟方法,调用就   R   而言   M   的派生程度最大的实现。     
对于在类中声明或者由类继承的每个虚拟方法,存在一个就该类而言的派生程度最大的实现。就类   R   而言虚拟方法   M   的派生度最大的实现按下面这样确定:     
    
如果   R   包含   M   的引入   virtual   声明,则这是   M   的派生程度最大的实现。     
否则,如果   R   包含   M   的   override,则这是   M   的派生程度最大的实现。     
否则,M   的派生程度最大的实现与   R   的直接基类的派生程度最大的实现相同。     
下列实例阐释虚拟方法和非虚拟方法之间的区别:   
    
class   A   
{   
        public   void   F()   {   Console.WriteLine("A.F");   }   
        public   virtual   void   G()   {   Console.WriteLine("A.G");   }   
}   
class   B:   A   
{   
        new   public   void   F()   {   Console.WriteLine("B.F");   }   
        public   override   void   G()   {   Console.WriteLine("B.G");   }   
}   
class   Test   
{   
        static   void   Main()   {   
              B   b   =   new   B();   
              A   a   =   b;   
              a.F();   
              b.F();   
              a.G();   
              b.G();   
        }   
}   
在该示例中,A   引入一个非虚拟方法   F   和一个虚拟方法   G。类   B   引入一个新的非虚拟方法   F,从而隐藏了继承的   F,并且还重写了继承的方法   G。此例产

生下列输出:   
    
A.F   
B.F   
B.G   
B.G   
请注意,语句   a.G()   调用   B.G   而不是   A.G。这是因为是实例的运行时类型(即   B)而不是实例的编译时类型(即   A)确定要调用的实际方法实现。   
    
由于允许方法隐藏继承的方法,因此类可以包含具有相同签名的若干个虚拟方法。由于除派生程度最大的方法外全部都被隐藏,因此这不会造成多义性问题。在下面的示例中,


 

 1 class A
 2     {
 3         public virtual void F() { Console.WriteLine("A.F"); }
 4     }
 5     class B : A
 6     {
 7         public override void F() { Console.WriteLine("B.F"); }  //重写A类中的虚方法
 8     }
 9     class C : B
10     {
11         new public virtual void F() { Console.WriteLine("C.F"); } //隐藏B类中重写过的方法,只剩下这里的虚方法会被显示,但被D重写。
12     }
13     class D : C
14     {
15         public override void F() { Console.WriteLine("D.F"); } //重写C类中的虚方法
16     }    
17 static void Main(string[] args)
18         {
19             D d = new D();
20             A a = d;
21             B b = d;
22             C c = d;
23             a.F();  //A类中的虚方法被重写为B类中的F(),所以显示结果为BF。
24             b.F();  //B类中的F()方法被调用,所以显示BF
25             c.F();  //B类中的重写方法被隐藏,调用C类中的虚方法,儿虚方法被D重写,故调用D类的F(),显示结果为DF
26             d.F();   //寻找基类C中的虚方法,C的基类A中的虚方法不被执行,也就说明虚方法重载只能从上一辈重载
27             Console.ReadKey();
28         }

 

   
C   类和   D   类包含两个具有相同签名的虚拟方法:A   引入的虚拟方法和   C   引入的虚拟方法。C   引入的方法隐藏从   A   继承的方法。因此,D   中的重写声明重

写   C   引入的方法,而   D   不可能重写   A   引入的方法。(D重载老爸C,不能从老爷那重载A)此例产生下列输出:   
    
B.F   
B.F   
D.F   
D.F   
请注意,可以通过访问   D   的实例(通过在其中未隐藏方法的派生程度较小的类型)调用隐藏的虚拟方法。  

 


 

虚方法在形式上在方法名前加virtual修饰.比如:   
在C#中这样定义一个:   
public   class   baseclass{   
          protected   virtual   int   MyFunction(){   
                return   -1   
          }   
}   
虚方法一般在基类定义,在派生类中实现具体操作,派生类实现该方法时,要用override修饰.如:   
public   class   myclass:baseclass{   
          protected   override   int   MyFunction(){   
                  return   1;   
          }   
        
}   
创建实例时,可用基类的变量而调用子类的构造函数来实例.   
如:   
baseclass   bc;   
bc   =   new   myclass();   
int   v=bc.MyFunction();//这里返回1,调用的时派生类的方法.   
这样有什么好处呢?   
比如往往在程序开始设计的类时,先进行抽象性的设计,比如:很多表都有增加、删除、查询操作,根据这样共同点,可以设计一个基类:   
public   class   baseclass{   
          public   virtual   Dataview   Select(){return   null;}   
          public   virtual   bool   Delete(){return   false;}   
          public   virtual   bool   add(){return   false;}   
}   
这样,调用基类的虚方法来执行派生类的具体操作。而在基类调用时,并不需要知道派生类是怎么样实现的。因为不同的派生类可能实现的方式不一样。但调用的方式是一样的

,这就大大增加代码的可维护性,脉络较清淅有条理。   
还有一种情需要是在程序运行时,需要派生类的赋值。  

虚方法