2009-06-30 37 views

回答

1

here

在面向對象的編程中, 虛擬功能或者虛擬方法是 的函數或方法,其行爲 可以在繼承 類內由函數具有相同 被覆蓋簽名。

7

像任何其他語言..當你想多態。這有很多的用法。例如,您想要抽象從控制檯或文件或其他設備讀取輸入的方式。你可以有一個通用的閱讀器接口,然後是使用虛擬函數的多個具體實現。

4

例如代理方法。即在運行時重寫方法。例如,NHibernate使用它來支持延遲加載。

1

例如,您有一個基類Params和一組派生類。您希望能夠對存儲從params派生的所有可能的類的數組執行相同的操作。

沒問題 - 聲明虛方法,向派生類中添加一些基本實現,並在派生類中重寫它。現在,您可以遍歷數組並通過引用調用該方法 - 將調用正確的方法。

class Params { 
public: 
    virtual void Manipulate() { //basic impl here } 
} 

class DerivedParams1 : public Params { 
public: 
    override void Manipulate() { 
     base.Manipulate(); 
     // other statements here 
    } 
}; 

// more derived classes can do the same 

void ManipulateAll(Params[] params) 
{ 
    for(int i = 0; i < params.Length; i++) { 
     params[i].Manipulate(); 
    } 
} 
3

它用來告訴派生類,該函數可以被覆蓋。

MSDN有個很好的例子here

3

基本上虛擬成員允許你表示多態,派生類可以擁有與其基類中的方法具有相同簽名的方法,並且基類將調用派生類的方法。

一個基本的例子:

public class Shape 
{ 
    // A few example members 
    public int X { get; private set; } 
    public int Y { get; private set; } 
    public int Height { get; set; } 
    public int Width { get; set; } 

    // Virtual method 
    public virtual void Draw() 
    { 
     Console.WriteLine("Performing base class drawing tasks"); 
    } 
} 

class Circle : Shape 
{ 
    public override void Draw() 
    { 
     // Code to draw a circle... 
     Console.WriteLine("Drawing a circle"); 
     base.Draw(); 
    } 
} 
class Rectangle : Shape 
{ 
    public override void Draw() 
    { 
     // Code to draw a rectangle... 
     Console.WriteLine("Drawing a rectangle"); 
     base.Draw(); 
    } 
} 
class Triangle : Shape 
{ 
    public override void Draw() 
    { 
     // Code to draw a triangle... 
     Console.WriteLine("Drawing a triangle"); 
     base.Draw(); 
    } 
} 
+2

「,基類將調用派生類的方法。」這是一個令人困惑的聲明與示例代碼配對;該文本指示base.Draw將導致派生類Draw方法被調用(這將導致StackOverflowException,我認爲)。我明白你的觀點,但新人可能不會。這樣的示例代碼確實很好,運行它將清楚地顯示發生了什麼。 – 2009-06-30 07:23:00

71

所以基本上,如果你的祖先類,你要的方法的某些行爲。如果你的後代使用相同的方法,但有不同的實施,你可以覆蓋它,如果它有一個虛擬關鍵字。

using System; 
class TestClass 
{ 
    public class Dimensions 
    { 
     public const double pi = Math.PI; 
     protected double x, y; 
     public Dimensions() 
     { 
     } 
     public Dimensions (double x, double y) 
     { 
     this.x = x; 
     this.y = y; 
     } 

     public virtual double Area() 
     { 
     return x*y; 
     } 
    } 

    public class Circle: Dimensions 
    { 
     public Circle(double r): base(r, 0) 
     { 
     } 

     public override double Area() 
     { 
     return pi * x * x; 
     } 
    } 

    class Sphere: Dimensions 
    { 
     public Sphere(double r): base(r, 0) 
     { 
     } 

     public override double Area() 
     { 
     return 4 * pi * x * x; 
     } 
    } 

    class Cylinder: Dimensions 
    { 
     public Cylinder(double r, double h): base(r, h) 
     { 
     } 

     public override double Area() 
     { 
     return 2*pi*x*x + 2*pi*x*y; 
     } 
    } 

    public static void Main() 
    { 
     double r = 3.0, h = 5.0; 
     Dimensions c = new Circle(r); 
     Dimensions s = new Sphere(r); 
     Dimensions l = new Cylinder(r, h); 
     // Display results: 
     Console.WriteLine("Area of Circle = {0:F2}", c.Area()); 
     Console.WriteLine("Area of Sphere = {0:F2}", s.Area()); 
     Console.WriteLine("Area of Cylinder = {0:F2}", l.Area()); 
    } 
} 

編輯:問題在評論
如果我不使用基類的虛關鍵字,它是否行得通呢?

如果在後代類中使用override關鍵字,則不起作用。您將生成編譯器錯誤CS0506'function1':由於未標記爲「virtual」,「abstract」或「override」,因此無法覆蓋繼承的成員'function2'

如果您不使用覆蓋您將獲得CS0108警告'desc.Method()'隱藏繼承的成員'base.Method()'如果隱藏的目的是使用新的關鍵字。

要解決這個問題,請將new關鍵字放在您正在使用的方法之前隱藏

例如

new public double Area() 
    { 
    return 2*pi*x*x + 2*pi*x*y; 
    } 

..是否必須重寫派生類中的虛方法?
不,如果你不重寫方法,後代類將使用它繼承的方法。

+3

如果我在基類中不使用virtual關鍵字,該怎麼辦?它會起作用嗎?在推導類中重寫虛方法是否強制? – Preeti 2010-08-20 08:16:54

+4

@Pre我已經在評論中回答了您的問題 – 2010-08-20 12:11:57

3

這允許實現遲綁定,這意味着在運行時而不是在編譯時確定哪個對象的成員將被調用。見Wikipedia

55

理解虛擬函數實際用法的關鍵是要記住,某個類的對象可以被賦予從第一個對象的類派生的類的另一個對象。

例如爲:

class Animal { 
    public void eat() {...} 
} 

class FlyingAnimal : Animal { 
    public void eat() {...} 
} 

Animal a = new FlyingAnimal(); 

Animal類具有如下功能:eat(),其通常描述了一種動物應如何吃(例如放入口中的對象和吞)。

但是,FlyingAnimal類應該定義一個新的eat()方法,因爲飛行動物有特定的飲食方式。

這樣說到這裏介意的問題是:我宣佈Animal類型的變量a和asigned它FlyingAnimal類型的新對象,什麼都會做a.eat()後?這兩種方法中的哪一種被稱爲?

這裏的答案是:因爲aAnimal,它會調用Animal的方法。編譯器是愚蠢的,不知道你打算將另一個類的對象指定給a變量。

這裏是關鍵字virtual的作用:如果你聲明方法爲virtual void eat() {...},你基本上是告訴編譯器「小心我在這裏做了一些聰明的事情,你不能處理,因爲你不聰明」。所以編譯器不會試圖將呼叫a.eat()鏈接到兩種方法中的任何一種,而是它告訴系統在運行時在運行時

因此,只有代碼執行時,系統會看a內容類型沒有在其聲明的類型和執行FlyingAnimal的方法。

你可能會問:爲什麼我會想這麼做?爲什麼不從一開始就說對了FlyingAnimal a = new FlyingAnimal()

其原因是,例如,你可以有很多派生類從AnimalFlyingAnimalSwimmingAnimalBigAnimalWhiteDog等。在一個點要定義包含許多Animal秒的世界,所以你說:

Animal[] happy_friends = new Animal[100]; 

我們有100只快樂動物世界。 你在某些時候對它們進行初始化:

... 
happy_friends[2] = new AngryFish(); 
... 
happy_friends[10] = new LoudSnake(); 
... 

,並在一天結束時,你要讓大家吃的時候,睡覺前。所以你想說:

for (int i=0; i<100; i++) { 
    happy_friends[i].eat(); 
} 

所以,你可以看到,每個動物都有自己的吃法。只有使用虛擬函數才能實現此功能。否則,每個人都將被迫以完全相同的方式「吃飯」:如Animal類中最常用的eat函數中所述。

編輯: 此行爲實際上是默認在常見的高級語言,如Java。

1

在c#使用的虛擬函數

虛擬功能大多用於覆蓋在派生類的基類的方法具有相同簽名。

當派生類繼承基類時,派生類的對象是對派生類或基類的引用。

虛擬函數是由編譯器晚解決(即運行時綁定)

在基類virtual,最派生的類的實現的功能根據所提到的實際對象的類型被稱爲,而不管指針或引用的聲明類型如何。如果它不是virtual,則該方法被解析爲early,並且根據所聲明的指針或引用的類型選擇調用的函數。