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

What is the difference between an abstract method and a virtual method?

提问人:Moran Helman 提问时间:12/24/2008 最后编辑:Géry OgamMoran Helman 更新时间:3/29/2023 访问量:649414

问:

抽象方法和虚拟方法有什么区别?在哪些情况下,建议使用抽象或虚拟方法?哪一个是最好的方法?

OOP 与语言无关的 抽象 虚函数

评论

295赞 Jordan Parmer 12/24/2008
抽象函数必须被覆盖,而虚拟函数可以被覆盖。
17赞 Martin 12/5/2012
虚拟函数可以在基类中具有默认的 /generic 实现。
7赞 Cole Tobin 5/29/2017
这里的关键词是抽象的;它们不存在,只是对函数是什么(方法签名)的模糊概念

答:

71赞 Rinat Abdullin 12/24/2008 #1

必须始终重写抽象函数。

因此:

  • 抽象函数 - 当继承者必须提供自己的实现
  • 虚拟 - 由继承者决定
2910赞 BFree 12/24/2008 #2

抽象函数不能具有功能。你基本上是说,任何子类都必须给出他们自己的方法版本,但是它太笼统了,甚至无法尝试在父类中实现。

一个虚拟函数,基本上是说看,这是对于子类来说可能足够好也可能不够好的功能。因此,如果它足够好,请使用此方法,如果没有,则覆盖我,并提供您自己的功能。

评论

428赞 Brann 7/5/2011
当然,如果重写虚拟方法,则始终可以通过调用 base 来引用父方法。傅(...)
210赞 Jake 11/1/2011
谢谢。这是一个比 MSDN 文档中的任何内容都更好、更容易的解释。(读完五分钟后我头疼:msdn.microsoft.com/en-us/library/aa645767(v=vs.71).aspx)
18赞 Rosdi Kasim 4/17/2013
我来自Java,我有点困惑为什么我们需要把它变成虚拟的,直到我读到这个: stackoverflow.com/a/1062126/193634
4赞 Usman Younas 11/7/2013
@MeqDotNet 这意味着如果你喜欢我的实现,请使用我,如果没有,请写你自己的比我更好:)
17赞 SamChen 5/28/2014
这应该在 Microsoft 参考库中,我花了 10 分钟阅读但仍然感到困惑。
329赞 JoshBerke 12/24/2008 #3

抽象函数没有实现,只能在抽象类上声明。这将强制派生类提供实现。

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

例如:

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 1/23/2013
查看示例代码非常有用 - 有助于使答案中的各种解释更加清晰。
2赞 Dirk 6/12/2014
我把答案回滚到以前的版本:这两个类只是示例,第一个类会编译,因为它被标记为抽象,第二个类不会。MyBase 是否继承自其他类是无关紧要的。
2赞 6/27/2014
你的类不需要以某种方式实现抽象类吗?我不经常这样做,所以我可能弄错了。我在你的例子中没有看到这一点。MyBase
2赞 JoshBerke 6/28/2014
在上面的例子中,MyBase显示了你不能做什么。也就是说,在非抽象类中不能有抽象方法
9赞 Rashack 4/16/2009 #4

抽象方法始终是虚拟的。他们不能有实现。

这是主要区别。

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

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

4赞 Brann 4/16/2009 #5

当您希望继承者扩展功能时,您基本上会使用虚拟方法。

当您希望继承者实现功能时,可以使用抽象方法(在这种情况下,他们别无选择)

29赞 Frederik Gheysels 4/16/2009 #6

摘要方法: 当一个类包含抽象方法时,该类必须声明为抽象。 抽象方法没有实现,因此,从该抽象类派生的类必须为此抽象方法提供实现。

虚拟方法: 类可以具有虚拟方法。虚拟方法有一个实现。 从具有虚拟方法的类继承时,可以重写虚拟方法并提供其他逻辑,或者将逻辑替换为自己的实现。

何时使用什么: 在某些情况下,您知道某些类型应该具有特定的方法,但是,您不知道此方法应该具有什么实现。
在这种情况下,您可以创建一个接口,其中包含具有此签名的方法。 但是,如果您有这种情况,但您知道该接口的实现者还将具有另一种通用方法(您已经可以为其提供实现),则可以创建一个抽象类。 然后,这个抽象类包含抽象方法(必须重写)和另一个包含“公共”逻辑的方法。

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

88赞 Mehrdad Afshari 4/16/2009 #7
  1. 只有类可以有成员。abstractabstract
  2. 从类继承的非类必须是其成员。abstractabstractoverrideabstract
  3. 成员是隐式的。abstractvirtual
  4. 成员不能提供任何实现(在某些语言中称为)。abstractabstractpure virtual

评论

0赞 Hobo Spider 1/15/2014
数字 3 对我来说没有意义。我想你的意思是说“抽象类的成员是隐式虚拟的”(即,你可以为它提供功能,而不必指定它是虚拟的)。
7赞 Mehrdad Afshari 1/15/2014
不,我的意思是我写的。抽象类的成员可以是 - 也可以是非 - 。成员(即抽象属性、抽象方法)就像一个虚拟方法,即你可以重写它,只是它本身不带有默认实现。virtualvirtualabstract
0赞 bonCodigo 5/20/2014
引用“抽象成员是”隐式“虚拟的。但是我在某处看到,有人通过显式添加“虚拟”关键字来创建抽象成员。这是没有必要的,事实上,在我读到你的答案之前,它给了我一个怀疑。
0赞 Rafael 11/27/2017
请提供第4点的支持性参考资料。而且你的帖子没有带来以前没有的任何其他东西。
0赞 Reverse Engineered 2/19/2020
这只是一堆没有解释的陈述。
23赞 Kamiel Wanrooij 4/16/2009 #8

抽象方法是为创建具体类而必须实现的方法。声明位于抽象类中(任何具有抽象方法的类都必须是抽象类),并且必须在具体类中实现。

虚拟方法是可以在派生类中使用重写重写的方法,替换超类中的行为。如果不重写,则会获得原始行为。如果你这样做了,你总是会得到新的行为。这与非虚拟方法相反,它不能被覆盖,但可以隐藏原始方法。这是使用修饰符完成的。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");
    }
}

当我实例化并调用 或 时,我会得到“你好”和“稍后见”。如果我打电话给 ,我会得到“你好”和“稍后再见”。这是因为它是虚拟的,可以用派生类代替。 只是隐藏的,所以当我从我的基类中调用它时,我会得到我的原始方法。DerivedClassSayHelloSayGoodbyeHelloGoodbyeSayGoodbyeSayHello

抽象方法是隐式虚拟的。它们定义必须存在的行为,更像是接口。

-4赞 Qaiser 6/27/2011 #9

据我了解:

摘要方法:

只有抽象类才能保存抽象方法。此外,派生类需要实现该方法,并且类中未提供任何实现。

虚拟方法:

类可以声明这些内容,也可以提供相同的实现。此外,派生类需要实现方法以重写它。

9赞 MeqDotNet 12/7/2011 #10

我通过对以下类(来自其他答案)进行一些改进来简化此操作:

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");
        }
    }
}
1赞 ComeIn 8/19/2013 #11

答案已经提供了很多次,但关于何时使用每个答案的问题是设计时的决定。我认为尝试将通用方法定义捆绑到不同的接口中并将它们拉入适当的抽象级别的类中是一种很好的做法。将一组通用的抽象和虚拟方法定义转储到类中会使该类不可反抗,而最好定义一个实现一组简洁接口的非抽象类。与往常一样,这取决于什么最适合您的应用程序特定需求。

41赞 Lexnim 5/28/2014 #12

抽象函数:

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

虚拟功能:

  1. 它可以在抽象类和非抽象类中声明。
  2. 它包含方法实现。
  3. 它可能会被覆盖。
2赞 Kod 8/6/2015 #13

我在某些地方看到抽象方法的定义如下。**

“抽象方法必须在子类中实现”

** 我觉得这就像.

如果子类也是抽象的,则不必在子类中实现抽象方法。

1)抽象方法不能是私有方法。 2)抽象方法不能在同一个抽象类中实现。

我会说..如果我们要实现一个抽象类,你必须重写基抽象类中的抽象方法。 因为。。实现抽象方法是使用 override 关键字。类似于虚拟方法。

在继承的类中实现虚拟方法不是必需的。

                                 ----------CODE--------------

public abstract class BaseClass
{
    public int MyProperty { get; set; }
    protected abstract void MyAbstractMethod();

    public virtual void MyVirtualMethod()
    {
        var x = 3 + 4;
    }

}
public abstract class myClassA : BaseClass
{
    public int MyProperty { get; set; }
    //not necessary to implement an abstract method if the child class is also abstract.

    protected override void MyAbstractMethod()
    {
        throw new NotImplementedException();
    }
}
public class myClassB : BaseClass
{
    public int MyProperty { get; set; }
    //You must have to implement the abstract method since this class is not an abstract class.

    protected override void MyAbstractMethod()
    {
        throw new NotImplementedException();
    }
}
1赞 Yeasin Abedin 1/13/2016 #14

抽象函数不能有主体,并且必须由子类重写

虚拟函数将有一个主体,并且可能会被子类覆盖,也可能不会被子类覆盖

2赞 BenKoshy 5/6/2016 #15

上面的大多数示例都使用代码 - 它们非常非常好。我不需要补充他们所说的内容,但以下是一个简单的解释,它使用类比而不是代码/技术术语。

简单解释 - 使用类比进行解释

Abstract 方法

想想乔治·W·布什(George W Bush)。他对他的士兵说:“去伊拉克打仗”。就是这样。他所指出的只是必须进行战斗。他没有具体说明这将如何发生。但我的意思是,你不能只是出去“战斗”:这到底意味着什么?我是用 B-52 还是我的 derringer 战斗?这些具体细节留给其他人。这是一种抽象方法。

虚拟方法

大卫·彼得雷乌斯(David Petraeus)在军队中地位很高。他定义了战斗的含义:

  1. 找到敌人
  2. 中和他。
  3. 之后喝杯啤酒

问题是这是一种非常通用的方法。这是一种有效的好方法,但有时不够具体。对彼得雷乌斯来说,好消息是他的命令有回旋余地和范围——他允许其他人根据他们的特殊要求改变他对“战斗”的定义。

私人工作博客阅读彼得雷乌斯的命令,并被允许根据他的特殊要求实施他自己的战斗版本:

  1. 寻找敌人。
  2. 朝他的头部开枪。
  3. 回家
  4. 喝啤酒。

努里·马利基(Nouri al Maliki)也收到了彼得雷乌斯(Petraeus)的相同命令。他也要打仗。但他是一个政治家,而不是一个步兵。显然,他不能四处射击他的政治敌人的头部。因为彼得雷乌斯给了他一个虚拟的方法,那么马利基可以根据自己的具体情况实现自己版本的战斗方法:

  1. 寻找敌人。
  2. 以一些 BS 捏造的罪名逮捕他。
  3. 回家
  4. 喝啤酒。

換句話說,虛擬方法提供样板指令 - 但這些是一般指令,可以由軍隊階層的人根據他們的特別情況做出更具體的指導。

两者的区别

  • 乔治·布什没有证明任何实施细节。这必须由其他人提供。这是一种抽象方法。

  • 另一方面,彼得雷乌斯确实提供了实施细节,但他允许他的下属用他们自己的版本推翻他的命令,如果他们能想出更好的东西。

希望对你有所帮助。

2赞 Kedarnath M S 5/31/2016 #16

抽象函数(方法):

● 抽象方法是用关键字 abstract 声明的方法。

●它没有身体。

● 它应该由派生类实现。

● 如果一个方法是抽象的,那么这个类应该是抽象的。

虚拟函数(方法):

● 虚拟方法是用关键字 virtual 声明的方法,可以使用 override 关键字被派生类方法覆盖。

● 是否覆盖它取决于派生类。

0赞 Nithila Shanmugananthan 6/7/2016 #17

在 C# 中没有所谓的虚拟类。

对于函数

  1. 抽象函数只有签名,驱动器类应该用功能覆盖。
  2. 虚拟功能将保留驱动器类可能根据要求覆盖或不覆盖它的功能部分

您可以根据自己的要求来决定。

1赞 Mohammad Nikravesh 3/1/2017 #18

从一般的面向对象的角度来看: 关于抽象方法:当你把一个抽象方法放在父类中时,实际上是在对子类说:

嘿,注意你有一个这样的方法签名。如果你想使用它,你应该实现你自己的!

关于虚函数:当你把一个虚方法放在父类中时,你是在对派生类说:嘿,这里有一个功能可以为你做一些事情。如果这对您有用,请使用它。如果没有,请覆盖它并实现您的代码,即使您可以在代码中使用我的实现!

这是关于一般OO中这两个概念之间的一些不同哲学

6赞 Rodrick Chapman 8/14/2017 #19

绑定是将名称映射到代码单元的过程。

后期绑定意味着我们使用名称,但延迟映射。换句话说,我们首先创建/提及该名称,然后让一些后续进程处理代码到该名称的映射。

现在考虑:

  • 与人类相比,机器非常擅长搜索和分类
  • 与机器相比,人类非常擅长发明和创新

所以,简短的回答是:是机器(运行时)的后期绑定指令,而是人类(程序员)的后期绑定指令virtualabstract

换言之,意味着:virtual

“亲爱的运行时,通过执行您最擅长的操作,将适当的代码绑定到此名称:搜索

鉴于意味着:abstract

“亲爱的程序员,请通过做你最擅长的事情来将适当的代码绑定到这个名字上:发明

为了完整起见,重载意味着:

“亲爱的编译器,通过做你最擅长的事情:排序,将适当的代码绑定到这个名称”。

4赞 flik 3/26/2018 #20

虚拟方法

  • 虚拟意味着我们可以覆盖它。

  • 虚拟函数有一个实现。当我们继承类时,我们 可以覆盖虚函数并提供我们自己的逻辑。

  • 我们可以在子类中实现
    函数时更改 Virtual 函数的返回类型(可以说是 Shadowing 的概念
    )。

Abstract 方法

  • 抽象意味着我们必须覆盖它。

  • 抽象函数没有实现,必须位于抽象类中。

  • 它只能被声明。这强制派生类提供它的实现。

  • 抽象成员是隐式虚拟的。在某些语言中,抽象可以称为纯虚拟。

    public abstract class BaseClass
    { 
        protected abstract void xAbstractMethod();
    
        public virtual void xVirtualMethod()
        {
            var x = 3 + 4;
        }
    } 
    
0赞 Vinay Chanumolu 6/5/2018 #21

抽象方法没有实现。它是在父类中声明的。子类负责实现该方法。

虚拟方法应该在父类中有一个实现,它有助于子类选择是使用父类的实现,还是在子类中为该方法为自己创建一个新的实现。

1赞 Sagi Berco 7/27/2018 #22

抽象函数“只是”一个签名,没有实现。 它在接口中用于声明如何使用类。 它必须在其中一个派生类中实现。

虚函数(实际上是方法)也是您声明的函数,应该在继承层次结构类之一中实现。

此类的继承实例也会在较低的层次结构类中继承实现,除非您实现它。

1赞 Yituo 10/27/2018 #23

从 C++ 背景来看,C# 虚拟对应于 C++ 虚拟,而 C# 抽象方法对应于 C++ 纯虚函数

0赞 Ciro Corvino 2/24/2019 #24

抽象函数或方法是由类公开的公共“操作名称”,其目的与抽象类一样,主要是在对象设计中针对对象必须实现的结构提供一种约束形式。

事实上,从其抽象类继承的类必须为这种方法提供实现,通常编译器会在不实现时引发错误。

使用抽象类和方法很重要,主要是为了避免在设计类时关注实现细节,从而避免类结构与实现过于相关,从而在它们之间协作的类之间创建依赖关系和耦合。

虚拟函数或方法只是一个模拟类的公共行为的方法,但我们可以自由地在继承链中修改它,因为我们认为子类可能需要为该行为实现一些特定的扩展。

它们都代表了面向对象范式中的一种多法主义形式。

我们可以同时使用抽象方法和虚函数来支持良好的继承模型。

我们设计了解决方案主要对象的良好抽象结构,然后通过定位那些更容易进一步专业化的对象来创建基本实现,我们将这些实现作为虚拟实现,最后我们将基本实现专业化,最终“覆盖”继承的虚拟实现。

0赞 U.Savas 3/2/2019 #25

在这里,我正在编写一些示例代码,希望这可能是一个相当具体的例子,可以在非常基本的层面上查看接口、抽象类和普通类的行为。如果你想将这段代码作为一个项目,你也可以在 github 中找到它作为一个项目: https://github.com/usavas/JavaAbstractAndInterfaceDemo

public interface ExampleInterface {

//    public void MethodBodyInInterfaceNotPossible(){
//    }
    void MethodInInterface();

}

public abstract class AbstractClass {
    public abstract void AbstractMethod();

    //    public abstract void AbstractMethodWithBodyNotPossible(){
    //
    //    };

    //Standard Method CAN be declared in AbstractClass
    public void StandardMethod(){
        System.out.println("Standard Method in AbstractClass (super) runs");
    }
}

public class ConcreteClass
    extends AbstractClass
    implements ExampleInterface{

    //Abstract Method HAS TO be IMPLEMENTED in child class. Implemented by ConcreteClass
    @Override
    public void AbstractMethod() {
        System.out.println("AbstractMethod overridden runs");
    }

    //Standard Method CAN be OVERRIDDEN.
    @Override
    public void StandardMethod() {
        super.StandardMethod();
        System.out.println("StandardMethod overridden in ConcreteClass runs");
    }

    public void ConcreteMethod(){
        System.out.println("Concrete method runs");
    }

    //A method in interface HAS TO be IMPLEMENTED in implementer class.
    @Override
    public void MethodInInterface() {
        System.out.println("MethodInInterface Implemented by ConcreteClass runs");

    //    Cannot declare abstract method in a concrete class
    //    public abstract void AbstractMethodDeclarationInConcreteClassNotPossible(){
    //
    //    }
    }
}
1赞 user7396942 9/11/2019 #26

如果一个类派生自这个抽象类,则强制它重写抽象成员。这与 virtual 修饰符不同,后者指定可以选择性地重写该成员。

0赞 Géry Ogam 5/13/2021 #27

Traditional threefold classification of propositions

数字。— 传统的命题三重分类。

道义逻辑(对义务和许可的研究)中,每个命题都是强制性的(“必须”运算符)、可选的(“可以和不能”运算符)或不允许的(“不得”运算符),并且没有一个命题属于这三个类别中的一个以上。

此外,允许的(“可以”运算符)命题是强制性的或可选的命题,不容许的(“不能”运算符)命题是不允许的或可选的命题,非可选性(“必须或不能”运算符)命题是强制性的或不允许的命题。

特别是,一个强制性的命题是允许的,一个不允许的命题是允许的。

将这些运算符应用于命题“方法被覆盖”会产生以下命题:

  • 抽象(纯)/具体方法:该方法必须被覆盖/不得被覆盖;
  • 虚拟/真实(最终)方法:该方法可以被覆盖/不能被覆盖。

特别是,抽象方法是虚拟的,而真实的方法是具体的。