ChatGPT解决这个技术问题 Extra ChatGPT

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

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

抽象函数必须被覆盖,而虚拟函数可能被覆盖。
虚函数可以在基类中有一个默认的 /generic 实现。
这里的关键词是抽象的;它们不存在,只是对函数是什么的模糊概念(方法签名)

A
AMing

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

一个虚函数,基本上是说看,这里的功能可能对子类来说可能不够好,也可能不够好。所以如果足够好,就用这个方法,如果不行,那就覆盖我,提供你自己的功能。


当然,如果你重写了一个虚方法,你总是可以通过调用 base.Foo(...) 来引用父方法
谢谢。这是一个比 MSDN 文档中的任何内容都更好、更容易的解释。(读了五分钟后我头疼:msdn.microsoft.com/en-us/library/aa645767(v=vs.71).aspx
来自 Java,我有点困惑为什么我们需要将它虚拟化,直到我读到这个:stackoverflow.com/a/1062126/193634
@MeqDotNet 这意味着如果你喜欢我的实现,如果你自己写得比我好,请使用我:)
这应该在我花了 10 分钟阅读但仍然感到困惑的 Microsoft 参考库中。
F
Franco Roura

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

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

例如:

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();
}

查看示例代码非常有用 - 有助于使答案中的各种解释更加清晰。
我回滚了以前版本的答案:这两个类只是示例,第一个类将编译,因为它被标记为抽象,第二个不会。 MyBase 是否继承自其他类无关紧要。
您的 MyBase 类是否必须以某种方式实现 abstract 类?我不经常这样做,所以我可能会弄错。我在你的例子中没有看到这一点。
在上面的示例中,MyBase 显示了您不能做什么。那就是你不能在非抽象类中有抽象方法
m
mmx

只有抽象类可以有抽象成员。从抽象类继承的非抽象类必须覆盖其抽象成员。抽象成员是隐式虚拟的。抽象成员不能提供任何实现(抽象在某些语言中称为纯虚拟)。


3号对我来说没有意义。我认为您的意思是说“抽象类的成员是隐式虚拟的”(即,您可以为其提供功能而不必指定它是虚拟的)。
不,我的意思正是我写的。抽象类的成员可以是 virtual 或非 virtualabstract 成员(即抽象属性、抽象方法)就像一个虚拟方法,即您可以覆盖它,只是它本身不带有默认实现。
引用“抽象成员是‘隐式’虚拟的。”但我在某处看到,有人通过显式添加“虚拟”关键字来创建抽象成员。这是没有必要的,事实上,在我阅读您的答案之前,它给了我一个疑问。
请包括第 4 点的支持参考资料。而且您的帖子没有带来任何其他以前的帖子没有的内容。
这只是一堆没有任何解释的陈述。
P
Peter Mortensen

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

因此:

抽象函数——当继承者必须提供自己的实现时

虚拟 - 由继承人决定


H
Hasmukh Savaliya

抽象功能:

它只能在抽象类中声明。它只包含方法声明而不包含抽象类中的实现。它必须在派生类中被覆盖。

虚拟功能:

它可以在抽象类和非抽象类中声明。它包含方法实现。它可能会被覆盖。


B
BenKoshy

解释:用比喻。希望它会帮助你。

语境

我在一栋大楼的 21 楼工作。我对火很偏执。时不时地,在世界的某个地方,一场大火正在烧毁一座摩天大楼。但幸运的是,我们这里有一本关于发生火灾时该怎么做的说明手册:

火灾逃生()

不要收拾财物 走到防火梯 走出大楼

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

虚方法

这个计划在 99% 的情况下都很好。这是一个有效的基本计划。但是有 1% 的可能性是火灾逃生通道被阻塞或损坏,在这种情况下你完全被搞砸了,除非你采取一些激烈的行动,否则你会被烤焦。使用虚拟方法,您可以做到这一点:您可以使用您自己的计划版本覆盖基本 FireEscape() 计划:

跑到窗口跳出窗口安全降落伞到底部

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

抽象方法

并非所有组织都经过良好训练。一些组织不进行消防演习。他们没有整体的逃生政策。每个人都是为自己。管理层只对现有的这种政策感兴趣。

换句话说,每个人都被迫开发自己的 FireEscape() 方法。一个人会走出防火梯。另一个人会跳伞。另一个人将使用火箭推进技术飞离建筑物。另一个人将绕道而行。只要你有一个基本的 FireEscape() 计划,管理层并不关心你是如何逃跑的——如果他们没有,你可以保证 OHS 会像一吨砖一样砸到组织上。这就是抽象方法的含义。

两者又有什么区别?

抽象方法:子类被迫实现自己的 FireEscape 方法。使用虚拟方法,您有一个基本计划等着您,但如果不够好,您可以选择实施您自己的计划。

现在这不是那么难,是吗?


F
Frederik Gheysels

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

虚方法:一个类可以有一个虚方法。虚方法有一个实现。当您从具有虚拟方法的类继承时,您可以覆盖该虚拟方法并提供额外的逻辑,或者用您自己的实现替换该逻辑。

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

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


K
Kamiel Wanrooij

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

虚拟方法是一种可以在派生类中使用覆盖覆盖的方法,替换超类中的行为。如果您不覆盖,您将获得原始行为。如果你这样做,你总是会得到新的行为。这与非虚拟方法相反,它不能被覆盖但可以隐藏原始方法。这是使用 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 并调用 SayHelloSayGoodbye 时,我得到“Hi There”和“See you later”。如果我拨打 HelloGoodbye,我会收到“Hello”和“See you later”。这是因为 SayGoodbye 是虚拟的,可以被派生类替换。 SayHello 只是隐藏的,所以当我从我的基类调用它时,我得到了我原来的方法。

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


P
Peter Mortensen

抽象方法总是虚拟的。他们不能有一个实现。

这是主要的区别。

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

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


P
Peter Mortensen

我通过对以下类进行一些改进(来自其他答案)使这变得更简单:

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");
        }
    }
}

R
Rodrick Chapman

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

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

现在考虑:

与人类相比,机器非常擅长搜索和分类

与机器相比,人类真的很擅长发明和创新

因此,简短的回答是:virtual 是机器(运行时)的后期绑定指令,而 abstract 是人类(程序员)的后期绑定指令

换句话说,virtual 意味着:

“亲爱的运行时,通过做你最擅长的事情将适当的代码绑定到这个名称:搜索”

abstract 表示:

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

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

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


B
Brann

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

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


E
Eduard Malakhov

虚拟方法:

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

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

我们可以在子类中实现函数的同时改变虚函数的返回类型(可以说是Shadowing的一个概念)。

抽象方法

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

抽象函数没有实现,必须在抽象类中。

它只能被声明。这迫使派生类提供它的实现。

抽象成员是隐式虚拟的。在某些语言中,抽象可以称为纯虚拟。公共抽象类 BaseClass { 受保护的抽象 void xAbstractMethod();公共虚拟 void xVirtualMethod() { var x = 3 + 4; } }


T
TomazStoiljkovic

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

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

** 我觉得它就像。

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

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

我想说..如果我们正在实现一个抽象类,你必须重写基础抽象类的抽象方法。因为..实现抽象方法是用重写关键字.类似于Virtual方法。

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

                                 ----------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();
    }
}

B
BenKoshy

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

简单的解释 - 使用类比的解释

抽象方法

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

虚方法

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

找到敌人消灭他。之后喝啤酒

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

Private Job Bloggs 阅读了彼得雷乌斯的命令,并被允许根据他的特殊要求实施他自己的战斗版本:

寻找敌人。朝他的头部开枪。回家喝啤酒。

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

寻找敌人。让他以一些 BS 捏造的罪名被捕。回家喝啤酒。

换句话说,虚拟方法提供样板指令 - 但这些是一般指令,可以由军队层级的人们根据他们的特定情况制定更具体的指令。

两者的区别

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

另一方面,Petraeus 确实提供了实施细节,但他允许他的下属使用他们自己的版本覆盖他的命令,如果他们能想出更好的东西的话。

希望有帮助。


D
Daniel

抽象函数(方法):

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

● 没有本体。

● 由派生类实现。

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

虚函数(方法):

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

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


C
ComeIn

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


Y
Yeasin Abedin

抽象函数不能有主体,必须被子类覆盖

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


M
Mohammad Nikravesh

从一般面向对象的角度来看:关于抽象方法:当您将抽象方法放在父类中时,实际上您是在对子类说:嘿,请注意,您有这样的方法签名。如果你想使用它,你应该实现你自己的!关于虚函数:当您在父类中放置一个虚方法时,您是在对派生类说:嘿,这里有一个功能可以为您做一些事情。如果这对您有用,请使用它。如果没有,请覆盖它并实现您的代码,即使您可以在您的代码中使用我的实现!这是一般OO中这两个概念之间不同的一些哲学


S
Sagi Berco

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

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

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


Y
Yituo

从 C++ 的背景来看,C# virtual 对应于 C++ virtual,而 C# abstract methods 对应于 C++ 纯虚函数


佚名

如果一个类派生自这个抽象类,那么它就会被强制覆盖这个抽象成员。这与 virtual 修饰符不同,它指定可以选择性地覆盖该成员。


r
rollstuhlfahrer

C# 中没有调用虚拟类。

对于函数

抽象函数只有签名,驱动类应该用功能覆盖。虚函数将包含驱动器类可能会或可能不会根据要求覆盖它的部分功能

你可以根据你的要求来决定。


V
Vinay Chanumolu

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

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


C
Ciro Corvino

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

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

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

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

它们都代表了面向对象范式中的一种多态性。

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

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


U
U.Savas

在这里,我正在编写一些示例代码,希望这可能是一个相当有形的示例,可以在非常基本的层面上查看接口、抽象类和普通类的行为。如果您想将其用作演示,也可以在 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(){
    //
    //    }
    }
}

M
Maggyero

https://i.stack.imgur.com/rw7ux.png

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

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

此外,允许的('may' 运算符)命题是强制性或可选的,可忽略的('may not' 运算符)命题是不允许的或可选的,非可选的('must or must not' 运算符)命题是强制性的或不允许的。

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

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

抽象(纯)/具体方法:该方法必须被覆盖/不能被覆盖;

虚拟/真实(最终)方法:该方法可以被覆盖/不能被覆盖。

尤其是抽象方法是虚拟的,而真实的方法是具体的。


B
Brad Mace

据我了解:

抽象方法:

只有抽象类可以保存抽象方法。派生类也需要实现该方法,并且类中没有提供实现。

虚拟方法:

一个类可以声明这些并提供相同的实现。派生类也需要实现方法来覆盖它。