fbpx
维基百科

虚函数

面向对象程序设计领域,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++ 编辑

以下程式碼是 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 编辑

在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# 编辑

在 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++ 编辑

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). 
  • Copyright © 1991-2006, Marshall Cline.

虚函数, 此條目需要补充更多来源, 2013年3月21日, 请协助補充多方面可靠来源以改善这篇条目, 无法查证的内容可能會因為异议提出而被移除, 致使用者, 请搜索一下条目的标题, 来源搜索, 网页, 新闻, 书籍, 学术, 图像, 以检查网络上是否存在该主题的更多可靠来源, 判定指引, 在面向对象程序设计领域, object, pascal, 等语言中有, 英語, virtual, function, 或虚方法, 英語, virtual, method, 的概念, 这种函数或方法可以被子类继承和覆盖, 通常使用动. 此條目需要补充更多来源 2013年3月21日 请协助補充多方面可靠来源以改善这篇条目 无法查证的内容可能會因為异议提出而被移除 致使用者 请搜索一下条目的标题 来源搜索 虚函数 网页 新闻 书籍 学术 图像 以检查网络上是否存在该主题的更多可靠来源 判定指引 在面向对象程序设计领域 C Object Pascal 等语言中有虚函数 英語 virtual function 或虚方法 英語 virtual method 的概念 这种函数或方法可以被子类继承和覆盖 通常使用动态分派实现 这一概念是面向对象程序设计中 运行时 多型的重要组成部分 简言之 虚函数可以给出目标函数的定义 但该目标的具体指向在编译期可能无法确定 虚函数在设计模式方面扮演重要角色 例如 设计模式 一书中提到的23种设计模式中 仅5个对象创建模式就有4个用到了虚函数 抽象工厂 工厂方法 生成器 原型 只有单例没有用到 目录 1 目的 2 程式範例 2 1 C 2 2 Java 2 3 C 3 抽象类和纯虚函数 3 1 C 4 构造与析构时的行为 5 参考文献目的 编辑虚函数概念的引入可以解决这样的问题 在面向对象程序设计中 派生类继承自基类 使用指针或引用访问派生类对象时 指针或引用本身所指向的类型是基类而不是派生类 如果派生类覆盖了基类中的方法 通过上述指针或引用调用该方法时 可以有两种结果 调用到基类的方法 编译器根据指针或引用的类型决定 称作 早绑定 调用到派生类的方法 语言的运行时系统根据对象的实际类型决定 称作 迟绑定 虚函数的效果属于后者 如果问题中基类的函数是 虚 的 则调用到的都是最终派生类 英語 most derived class 中的函数实现 与指针或引用的类型无关 反之 如果函数非 虚 调用到的函数就在编译期根据指针或者引用所指向的类型决定 有了虚函数 程序甚至能够调用编译期还不存在的函数 在 C 中 在基类的成员函数声明前加上关键字 virtual 即可让该函数成为 虚函数 派生类中对此函数的不同实现都会继承这一修饰符 允许后续派生类覆盖 达到迟绑定的效果 即便是基类中的成员函数调用虚函数 也会调用到派生类中的版本 程式範例 编辑例如 一個基礎類別 Animal 有一個虛擬函式 eat 子類別 Fish 要實做一個函式 eat 這個子類別 Fish 與子類別 Wolf 是完全不同的 但是你可以引用類別 Animal 底下的函式 eat 定義 而使用子類別 Fish 底下函式 eat 的處理程序 C 编辑 以下程式碼是 C 的程式範例 要注意的是 這個範例沒有异常處理的程式碼 尤其是 new 或是 vector push back 丟出一個异常時 程式在執行時有可能會出現崩溃或是錯誤的現象 nbsp 類別 Animal 的區塊圖 include lt iostream gt include lt vector gt using namespace std class Animal public virtual void eat const cout lt lt I eat like a generic Animal lt lt endl virtual Animal class Wolf public Animal public void eat const cout lt lt I eat like a wolf lt lt endl class Fish public Animal public void eat const cout lt lt I eat like a fish lt lt endl class GoldFish public Fish public void eat const cout lt lt I eat like a goldfish lt lt endl class OtherAnimal public Animal int main std vector lt Animal gt 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 lt Animal gt const iterator it animals begin it animals end it it gt 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 编辑 在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 lt Animal gt animals new LinkedList lt Animal gt 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 编辑 在 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 lt Animal gt animals new List lt Animal gt 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 编辑 在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 等另一些语言中 构造时可以调用派生类中的实现 抽象工厂模式等设计模式也鼓励在支持这一特性的语言中使用这种用法 参考文献 编辑 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 Standard C 98 10 4 2 Meyers Scott Never Call Virtual Functions during Construction or Destruction June 6 2005 2018 06 22 原始内容存档于2021 01 26 C FAQ Lite Copyright c 1991 2006 Marshall Cline 取自 https zh wikipedia org w index php title 虚函数 amp oldid 65621060, 维基百科,wiki,书籍,书籍,图书馆,

文章

,阅读,下载,免费,免费下载,mp3,视频,mp4,3gp, jpg,jpeg,gif,png,图片,音乐,歌曲,电影,书籍,游戏,游戏。