抽象方法和虚方法有什么区别?

1718

抽象方法和虚方法有什么区别?在哪些情况下建议使用抽象或虚方法?哪种方法是最佳的?

抽象方法是没有实现的方法,必须在子类中实现,而虚方法有默认实现但可以被覆盖。如果希望强制子类实现特定方法,则应该使用抽象方法,如果希望提供默认实现并允许子类覆盖它,则应该使用虚方法。选择哪种方法取决于你的具体需求,没有一种方法是最好的。

295
一个抽象函数必须被覆盖,而一个虚函数可以被覆盖。 - Jordan Parmer
17
虚函数可以在基类中具有默认/通用实现。 - Martin
7
关键词是“抽象”,它们并不存在,只是该函数的一个模糊概念(方法签名)。 - Cole Tobin
28个回答

2910

抽象函数不能有功能。你基本上是说,任何子类都必须提供自己的这个方法版本,但它太一般化了,甚至无法在父类中尝试实现。

虚函数,基本上是说,这里是可能或不可能足够好用于子类的功能。所以如果足够好用,就使用此方法,否则请覆盖我,并提供您自己的功能。


428
当你覆盖了一个虚方法时,你可以通过调用base.Foo(...)来引用父方法。 - Brann
210
谢谢。这篇解释比MSDN文档中的任何内容都好理解,也更容易理解。(我在阅读此文5分钟后感到头疼:http://msdn.microsoft.com/en-us/library/aa645767(v=vs.71).aspx) - Jake
18
作为来自Java的人,我有些困惑为什么我们需要把它设为虚拟的,直到我读了这篇文章:https://dev59.com/jHNA5IYBdhLWcg3wL6oc#1062126 - Rosdi Kasim
4
这句话的意思是,如果你喜欢我的实现方式,就使用它;如果不喜欢,那么就自己写一个比我更好的。请注意,这里的“better than me”是指在技术上更好,而不是人格上。 - Usman Younas
17
这应该在微软参考库中,我花了10分钟阅读仍然感到困惑。 - SamChen
显示剩余8条评论

329

抽象函数没有具体实现,只能在抽象类中声明。这迫使派生类提供一个实现。

虚函数提供一个默认实现,可以存在于抽象类或非抽象类中。

例如:

public abstract class myBase
{
    //If you derive from this class you must implement this method. notice we have no method body here either
    public abstract void YouMustImplement();

    //If you derive from this class you can change the behavior but are not required to
    public virtual void YouCanOverride()
    { 
    }
}

public class MyBase
{
   //This will not compile because you cannot have an abstract method in a non-abstract class
    public abstract void YouMustImplement();
}

30
看到示例代码非常有用-有助于使答案中的各种解释更加清晰易懂。 - Simon Elms
2
我将答案回滚到了之前的版本:这两个类只是示例,第一个类被标记为抽象类,因此可以编译通过,而第二个类则不能。无论 MyBase 是否继承自其他类都是无关紧要的。 - Dirk
2
你的“MyBase”类难道不需要以某种方式实现抽象类吗?我并不经常这样做,所以我可能错了。我在你的例子中没有看到这一点。 - user153923
2
在上面的例子中,MyBase 显示了您不能做什么。也就是说,在非抽象类中不能有抽象方法。 - JoshBerke

88
  1. 只有抽象类可以拥有抽象成员。
  2. 一个非抽象类继承自抽象类,则必须重写其抽象成员。
  3. 抽象成员隐式为虚成员。
  4. 抽象成员不能提供任何实现(在某些语言中,抽象被称为“纯虚”)。

第三点对我来说没有意义。我认为你的意思是“抽象类的成员隐式地是虚拟的”(即,您可以为其提供功能,而无需指定它是虚拟的)。 - Hobo Spider
7
不,我的意思恰好就是我所写的。抽象类的成员可以是virtual或非virtual。抽象成员(即抽象属性、抽象方法)就像虚拟方法一样,你可以覆盖它,只是它本身不带有默认实现。 - Mehrdad Afshari
引用“抽象成员是“隐式”虚拟的。”但我在某个地方看到,有人通过显式添加“virtual”关键字来创建抽象成员。这并不是必要的,事实上直到我读了你的答案才消除了我的疑虑。 - bonCodigo
请在第四点中包含支持参考文献。您的帖子并没有带来之前帖子没有提到的任何内容。 - Rafael
这只是一堆没有解释的语句。 - Reverse Engineered

71

你必须始终覆盖抽象函数。

因此:

  • 抽象函数 - 当派生类必须提供自己的实现时
  • 虚函数 - 当由继承者来决定

41

抽象函数:

  1. 只能在抽象类中声明。
  2. 在抽象类中,只包含方法的声明而不是实现。
  3. 必须在派生类中被重写。

虚函数:

  1. 可以在抽象类和非抽象类中声明。
  2. 包含方法的实现。
  3. 可以被重写。

33

解释:通过比喻来帮助你理解。

背景

我在一座21层的大楼里工作。我很担心火灾。世界上的某个地方总会有一场大火烧毁一座摩天大楼。但幸运的是,我们这里有一本关于火灾逃生指南:

FireEscape()

  1. 不要收集物品
  2. 走到消防通道
  3. 离开建筑物

这基本上是一个名为FireEscape()的虚拟方法。

虚拟方法

对于99%的情况,这个计划非常好。这是一个简单有效的计划。但有1%的机会,即消防通道被堵塞或损坏,那么你就完了,除非你采取一些激进的行动,否则你将成为一块烤面包。使用虚拟方法,你可以做到这一点:你可以用自己的计划覆盖基本的FireEscape()计划:

  1. 跑到窗户
  2. 跳出窗户
  3. 安全降落到底部

换句话说,虚拟方法提供了一个基本计划,如果需要,可以进行覆盖。子类可以覆盖父类的虚拟方法,如果程序员认为适当的话。

抽象方法

并不是所有组织都很有条理。有些组织不进行消防演习。他们没有整体逃生政策。每个人都得自己想办法。管理层只关心这样的政策是否存在。

换句话说,每个人都被“强制”开发自己的FireEscape()方法。一个人会走出消防通道,另一个人会跳伞,另一个人会使用火箭推进技术从建筑物飞离,而另一个人则会绳降下去。管理层不关心你如何逃生,只要你有一个基本的FireEscape()计划 - 如果没有,那么组织将受到OHS的严厉打击。这就是抽象方法的含义。
两者之间的区别是什么?
抽象方法:子类被“强制”实现自己的FireEscape方法。对于虚拟方法,你有一个基本的计划等待着你,但如果它还不够好,你可以选择“实现自己的”方法。
现在,这并不难吧?

29

抽象方法: 当一个类包含一个抽象方法时,该类必须被声明为抽象类。 抽象方法没有具体的实现,因此从该抽象类派生出来的类必须提供实现这个抽象方法的具体代码。

虚方法: 一个类可以拥有一个虚方法。虚方法有一个实现。 当你继承了一个拥有虚方法的类后,你可以覆盖这个虚方法并提供额外的逻辑,或者用你自己的实现替换原有的逻辑。

何时使用什么: 在某些情况下,你知道某些类型应该拥有一个特定的方法,但是你不知道这个方法的具体实现方式。
在这种情况下,你可以创建一个接口,其中包含这个方法的签名。 然而,如果你有这样一种情况,但是你知道实现这个接口的类还会有另一个常用的方法(你已经可以提供其实现),你可以创建一个抽象类。 这个抽象类包含需要被重写的抽象方法和包含“常用”逻辑的另一个方法。

如果你有一个可以直接使用的类,但是想让继承者能够更改某些行为,尽管这不是强制要求,那么应该使用虚方法。


23

抽象方法是必须实现的方法,以创建一个具体类。声明在抽象类中(任何具有抽象方法的类都必须是抽象类),并且它必须在一个具体类中被实现。

虚拟方法是可以在派生类中使用覆盖关键字重写的方法,替换超类中的行为。如果您不进行重写,则会获得原始行为。如果您这样做,总是会获得新的行为。相对于不能被重写但可以隐藏原始方法的非虚拟方法,这是使用new修饰符完成的。

请参考下面的示例:

public class BaseClass
{
    public void SayHello()
    {
        Console.WriteLine("Hello");
    }


    public virtual void SayGoodbye()
    {
        Console.WriteLine("Goodbye");
    }

    public void HelloGoodbye()
    {
        this.SayHello();
        this.SayGoodbye();
    }
}


public class DerivedClass : BaseClass
{
    public new void SayHello()
    {
        Console.WriteLine("Hi There");
    }


    public override void SayGoodbye()
    {
        Console.WriteLine("See you later");
    }
}
当我实例化DerivedClass并调用SayHello或SayGoodbye时,我会得到“ Hi There”和“ See you later”。如果我调用HelloGoodbye,我会得到“ Hello”和“ See you later”。这是因为SayGoodbye是虚拟的,并且可以由派生类替换。SayHello只是被隐藏了,所以当我从我的基类调用它时,我得到原始的方法。
抽象方法是隐式虚拟的。它们定义必须存在的行为,更像是接口。

9

抽象方法总是虚拟的,它们不能有实现。

这是主要区别。

基本上,如果您有其“默认”实现并希望允许后代更改其行为,则会使用虚拟方法。

使用抽象方法,您强制后代提供实现。


9

我通过对以下类(来自其他答案)进行了一些改进,从而使它更简单:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TestOO
{
    class Program
    {
        static void Main(string[] args)
        {
            BaseClass _base = new BaseClass();
            Console.WriteLine("Calling virtual method directly");
            _base.SayHello();
            Console.WriteLine("Calling single method directly");
            _base.SayGoodbye();

            DerivedClass _derived = new DerivedClass();
            Console.WriteLine("Calling new method from derived class");
            _derived.SayHello();
            Console.WriteLine("Calling overrided method from derived class");
            _derived.SayGoodbye();

            DerivedClass2 _derived2 = new DerivedClass2();
            Console.WriteLine("Calling new method from derived2 class");
            _derived2.SayHello();
            Console.WriteLine("Calling overrided method from derived2 class");
            _derived2.SayGoodbye();
            Console.ReadLine();
        }
    }


    public class BaseClass
    {
        public void SayHello()
        {
            Console.WriteLine("Hello\n");
        }
        public virtual void SayGoodbye()
        {
            Console.WriteLine("Goodbye\n");
        }

        public void HelloGoodbye()
        {
            this.SayHello();
            this.SayGoodbye();
        }
    }


    public abstract class AbstractClass
    {
        public void SayHello()
        {
            Console.WriteLine("Hello\n");
        }


        //public virtual void SayGoodbye()
        //{
        //    Console.WriteLine("Goodbye\n");
        //}
        public abstract void SayGoodbye();
    }


    public class DerivedClass : BaseClass
    {
        public new void SayHello()
        {
            Console.WriteLine("Hi There");
        }

        public override void SayGoodbye()
        {
            Console.WriteLine("See you later");
        }
    }

    public class DerivedClass2 : AbstractClass
    {
        public new void SayHello()
        {
            Console.WriteLine("Hi There");
        }
        // We should use the override keyword with abstract types
        //public new void SayGoodbye()
        //{
        //    Console.WriteLine("See you later2");
        //}
        public override void SayGoodbye()
        {
            Console.WriteLine("See you later");
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接