(Translated by https://www.hiragana.jp/)
虚函数 - 维基百科,自由的百科全书

きょ函数かんすう

ざいめんこう对象ほどじょ设计领域,C++Object Pascal とう语言中有ちゅううきょ函数かんすう英語えいごvirtual functionあるきょ方法ほうほう英語えいごvirtual methodてき概念がいねん。这种函数かんすうある方法ほうほう以被继承くつがえ通常つうじょう使用しよう动态分派ぶんぱ实现。这一概念がいねんめんこう对象ほどじょ设计なか(运行时)かたてき重要じゅうよう组成部分ぶぶん。简言きょ函数かんすう以给出目でめ标函すうてきてい义,ただし该目标的具体ぐたい指向しこうざい编译可能かのう无法确定。

きょ函数かんすうざい设计しき方面ほうめんふんえんじ重要じゅうようかくしょくれい如,《设计しき》一书中提到的23种设计模しきちゅう,仅5个对ぞう创建しき就有4个用いたりょうきょ函数かんすう抽象ちゅうしょうこうこう厂方ほう生成せいせい原型げんけい),ただゆう单例ぼつ有用ゆうよういた

目的もくてき

编辑

きょ函数かんすう概念的がいねんてき引入以解决这样的问题:

ざいめんこう对象ほどじょ设计なか派生はせい类继うけたまわもと类。使用しようゆびある引用いんよう访问派生はせい类对ぞう时,ゆび针或引用いんよう本身ほんみしょ指向しこうてき类型もと类而派生はせい类。如果派生はせい类覆盖了もと类中てき方法ほうほうつう上述じょうじゅつゆび针或引用いんよう调用该方ほう时,以有两种结果:

  1. 调用いたもと类的方法ほうほう:编译すえゆび针或引用いんようてき类型决定,しょうさく绑定」;
  2. 调用いた派生はせい类的方法ほうほう:语言てき运行时系统根すえ对象てき实际类型决定,しょうさく「迟绑じょう」。

きょ函数かんすうてき效果こうかぞく于后しゃ。如果问题ちゅうもと类的函数かんすうきょてき,则调よういたてきさい派生はせい类(英語えいごmost-derived classちゅうてき函数かんすう实现,あずかゆび针或引用いんようてき类型无关。はんこれ,如果函数かんすうきょ」,调用いたてき函数かんすう就在编译すえゆび针或しゃ引用いんようしょ指向しこうてき类型决定。

ゆうりょうきょ函数かんすうほどじょ甚至のう够调よう编译还不存在そんざいてき函数かんすう

ざい C++ なかざいもと类的なり员函すう声明せいめいぜんじょう关键 virtual そく让该函数かんすうなりきょ函数かんすう派生はせい类中对此函数かんすうてき不同ふどう实现都会とかい继承这一おさむ饰符,まこと许后续派生はせい类覆盖,达到迟绑じょうてき效果こうかそく便びんもと类中てきなり员函すう调用きょ函数かんすう,也会调用いた派生はせい类中てき版本はんぽん

ほどしきはんれい

编辑

れい如,いち基礎きそ類別るいべつ Animal ゆういちきょなずらえはこしき eat類別るいべつ Fish ようじついちはこしき eat(),這個類別るいべつ Fish あずか類別るいべつ Wolf 完全かんぜん不同ふどうてきただし你可以引用いんよう類別るいべつ Animal そこてきはこしき eat() 定義ていぎ,而使用しよう類別るいべつ Fish そこはこしき eat() てき處理しょりほどじょ

以下いかほどしき碼是 C++ てきほどしきはんれい要注意ようちゅういてき,這個はんれいぼつゆう异常處理しょりてきほどしき碼。ゆう其是 new ある vector::push_back 丟出いち异常ほどしきざい執行しっこう有可ゆか能會のうかい出現しゅつげんくずし溃或錯誤さくごてき現象げんしょう

 
類別るいべつ Animal てきかたまり
# include <iostream>
# include <vector>

using namespace std;
class Animal
{
public:
    virtual void eat() const { cout << "I eat like a generic Animal." << endl; }
    virtual ~Animal() {}
};
 
class Wolf : public Animal
{
public:
    void eat() const { cout << "I eat like a wolf!" << endl; }
};
 
class Fish : public Animal
{
public:
    void eat() const { cout << "I eat like a fish!" << endl; }
};
 
class GoldFish : public Fish
{
public:
    void eat() const { cout << "I eat like a goldfish!" << endl; }
};
 
 
class OtherAnimal : public Animal
{
};
 
int main()
{
    std::vector<Animal*> animals;
    animals.push_back( new Animal() );
    animals.push_back( new Wolf() );
    animals.push_back( new Fish() );
    animals.push_back( new GoldFish() );
    animals.push_back( new OtherAnimal() );
 
    for( std::vector<Animal*>::const_iterator it = animals.begin();
       it != animals.end(); ++it) 
    {
        (*it)->eat();
        delete *it;
    }
 
   return 0;
}

以下いかきょなずらえはこしき Animal::eat() てき輸出ゆしゅつ

I eat like a generic Animal.
I eat like a wolf!
I eat like a fish!
I eat like a goldfish!
I eat like a generic Animal.

とう Animal::eat() 宣告せんこくためきょなずらえはこしき輸出ゆしゅつ如下しょしめせ

I eat like a generic Animal.
I eat like a generic Animal.
I eat like a generic Animal.
I eat like a generic Animal.
I eat like a generic Animal.

ざいJava语言ちゅう, 所有しょゆうてき方法ほうほうだま认都"きょ函数かんすう". ただゆう以关键字 final 标记てき方法ほうほうざい是非ぜひきょ函数かんすう. 以下いか Java ちゅうきょ方法ほうほうてきいち个例:

import java.util.*;

public class Animal {
   public void eat() { System.out.println("I eat like a generic Animal."); }
 
   public static void main(String[] args) {
      List<Animal> animals = new LinkedList<Animal>();

      animals.add(new Animal());
      animals.add(new Wolf());
      animals.add(new Fish());
      animals.add(new OtherAnimal());

      for (Animal currentAnimal : animals) {
         currentAnimal.eat();
      }
   }
}
 
public class Wolf extends Animal {
   public void eat() { System.out.println("I eat like a wolf!"); }
}
 
public class Fish extends Animal {
   public void eat() { System.out.println("I eat like a fish!"); }
}
 
public class OtherAnimal extends Animal {}

输出:

I eat like a generic Animal.
I eat like a wolf!
I eat like a fish!
I eat like a generic Animal.

ざい C# 语言ちゅう, 对基类中てきにんなんきょ方法ほうほう必须よう virtual おさむ饰, 而衍せい类中よしもと类继承而来てきじゅう载方ほう必须よう override おさむ饰. 以下いか C# てきいち个程じょ实例:

using System;
using System.Collections.Generic;

namespace ConsoleApplication1
{
  public class Animal
  {
    public virtual void Eat()
    {
      Console.WriteLine("I eat like a generic Animal.");
    }
  }

  public class Wolf : Animal
  {
    public override void Eat()
    {
      Console.WriteLine("I eat like a wolf!");
    }
  }

  public class Fish : Animal
  {
    public override void Eat()
    {
      Console.WriteLine("I eat like a fish!");
    }
  }

  public class GoldFish : Fish
  {
    public override void Eat()
    {
      Console.WriteLine("I eat like a goldfish!");
    }
  }

  public class OtherAnimal : Animal
  {
    // Eat() method is not overridden, so the base class method will be used.
  }

  public class Program
  {
    public static void Main(string[] args)
    {
      IList<Animal> animals = new List<Animal>();
      
      animals.Add(new Animal());
      animals.Add(new Wolf());
      animals.Add(new Fish());
      animals.Add(new GoldFish());
      animals.Add(new OtherAnimal());

      foreach (Animal currentAnimal in animals)
      {
        currentAnimal.Eat();
      }
    }
  }
}

输出:

I eat like a generic Animal.
I eat like a wolf!
I eat like a fish!
I eat like a goldfish!
I eat like a generic Animal.

抽象ちゅうしょう类和纯虚函数かんすう

编辑

纯虚函数かんすうある纯虚方法ほうほう一个需要被非抽象的派生类覆盖(override)てききょ函数かんすう. 包含ほうがん纯虚方法ほうほうてき类被しょうさく抽象ちゅうしょう; 抽象ちゅうしょう不能ふのう直接ちょくせつ实例。 一个抽象基类的一个ただゆうざい所有しょゆうてき纯虚函数かんすうざい该类(ある其父类)ない给出实现时, 才能さいのう直接ちょくせつ实例. 纯虚方法ほうほう通常つうじょうただゆう声明せいめい(签名)而没有定ありさだ义(实现),ただしゆう特例とくれいじょうがた要求ようきゅう纯虚函数かんすう必须给出函数かんすうたいてい义.

さく为一个例, 抽象ちゅうしょうもと类"MathSymbol"可能かのう提供ていきょういち个纯きょ函数かんすう doOperation(), かず衍生类 "Plus" "Minus" 提供ていきょうdoOperation() てき具体ぐたい实现. よし于 "MathSymbol" いち抽象ちゅうしょう概念がいねん, 为其ごと个子类定义了同一どういつてき动作, ざい "MathSymbol" 类中执行 doOperation() ぼつゆうにんなん义. 类似てき, いち个给じょうてき "MathSymbol" 类如はてぼつゆう doOperation() てき具体ぐたい实现不完全ふかんぜんてき.

虽然纯虚方法ほうほう通常つうじょうざいてい义它てき类中ぼつゆう实现, ざい C++ 语言ちゅう, まこと许纯きょ函数かんすうざいてい义它てき类中包含ほうがん其实现, 这为衍生类提供ていきょうりょう备用あるだま认的ぎょう为. C++てききょもと类的きょ析构函数かんすう必须提供ていきょう函数かんすうたいてい义,いや链接时(linking)ざい析构该抽象ちゅうしょう类的派生はせい实例对象てき语句处会报错。[1]

ざいC++语言ちゅう, 纯虚函数かんすうよう一种特别的语法[=0]てい义(ただし VS 也支持しじ abstract 关键:virtual ReturnType Function()abstract;), 见以しめせれい.

class Abstract {
public:
   virtual void pure_virtual() = 0;
};

纯虚函数かんすうてきてい义仅提供ていきょう方法ほうほうてき原型げんけい. 虽然ざい抽象ちゅうしょう类中通常つうじょう提供ていきょう纯虚函数かんすうてき实现, ただし抽象ちゅうしょう类中以包含其实现, 而且以不ざい声明せいめいてきどう时给てい[2]. まい个非抽象ちゅうしょう类仍しか需要じゅようじゅう载该方法ほうほう, 抽象ちゅうしょう类中实现てき调用以采よう以下いか这种形式けいしき:

 void Abstract::pure_virtual() {
   // do something
 }
 
 class Child : public Abstract {
   virtual void pure_virtual(); // no longer abstract, this class may be
                                // instantiated.
 };
 
 void Child::pure_virtual() {
   Abstract::pure_virtual(); // the implementation in the abstract class 
                             // is executed
 }

构造あずか析构时的ぎょう

编辑

ざい对象てき构造函数かんすう析构函数かんすうなかわたる及虚函数かんすう时,不同ふどうてき语言ゆう不同ふどうてき规定。ざい以 C++ 为代表だいひょうてきいち些语ごとちゅうきょ函数かんすう调度つくえせいざい对象てき构造析构时有不同ふどうてき语义,一般建议尽可能避免在 C++ てき构造函数かんすうちゅう调用きょ函数かんすう[3]。而在 C#Java とう另一些语ごとちゅう,构造时可以调よう派生はせい类中てき实现,抽象ちゅうしょうこう厂模しきひとし设计しき也鼓励在支持しじ这一特性的语言中使用这种用法。

参考さんこう文献ぶんけん

编辑
  1. ^ MSDN "Using dllimport and dllexport in C++ Classes"页面そん档备份そん互联网档あん): However, because a destructor for an abstract class is always called by the destructor for the base class, pure virtual destructors must always provide a definition. Note that these rules are the same for nonexportable classes.
  2. ^ Standard C++ 98 - 10.4/2
  3. ^ Meyers, Scott. Never Call Virtual Functions during Construction or Destruction. June 6, 2005 [2018-06-22]. (原始げんし内容ないようそん于2021-01-26).