程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++中的虛函數(virtual function)(1)

C++中的虛函數(virtual function)(1)

編輯:C++入門知識
一.簡介
  
  虛函數是C++中用於實現多態(polymorphism)的機制。核心理念就是通過基類訪問派生類定義的函數。假設我們有下面的類層次:
  
  class A
  
  {
  
  public:
  
  virtual void foo() { cout << "A::foo() is called" << endl;}
  
  };
  
  class B: public A
  
  {
  
  public:
  
  virtual void foo() { cout << "B::foo() is called" << endl;}
  
  };
  
  那麼,在使用的時候,我們可以:
  
  A * a = new B();
  
  a->foo(); // 在這裡,a雖然是指向A的指針,但是被調用的函數(foo)卻是B的!
  
  這個例子是虛函數的一個典型應用,通過這個例子,也許你就對虛函數有了一些概念。它虛就虛在所謂“推遲聯編”或者“動態聯編”上,一個類函數的調用並不是在編譯時刻被確定的,而是在運行時刻被確定的。由於編寫代碼的時候並不能確定被調用的是基類的函數還是哪個派生類的函數,所以被成為“虛”函數。 虛函數只能借助於指針或者引用來達到多態的效果,假如是下面這樣的代碼,則雖然是虛函數,但它不是多態的:
  
  class A
  
  {
  
  public:
  
  virtual void foo();
  
  };
  
  class B: public A
  
  {
  
  virtual void foo();
  
  };
  
  void bar()
  
  {
  
  A a;
  
  a.foo(); // A::foo()被調用
  
  }
  
  1.1 多態 在了解了虛函數的意思之後,再考慮什麼是多態就很輕易了。仍然針對上面的類層次,但是使用的方法變的復雜了一些:
  
  void bar(A * a)
  
  {
  
  a->foo(); // 被調用的是A::foo() 還是B::foo()?
  
  }
  
  因為foo()是個虛函數,所以在bar這個函數中,只根據這段代碼,無從確定這裡被調用的是A::foo()還是B::foo(),但是可以肯定的說:假如a指向的是A類的實例,則A::foo()被調用,假如a指向的是B類的實例,則B::foo()被調用。
  
  這種同一代碼可以產生不同效果的特點,被稱為“多態”。
  
  1.2 多態有什麼用? 多態這麼神奇,但是能用來做什麼呢?這個命題我難以用一兩句話概括,一般的C++教程(或者其它面向對象語言的教程)都用一個畫圖的例子來展示多態的用途,我就不再重復這個例子了,假如你不知道這個例子,隨便找本書應該都有介紹。我試圖從一個抽象的角度描述一下,回頭再結合那個畫圖的例子,也許你就更輕易理解。
  
  在面向對象的編程中,首先會針對數據進行抽象(確定基類)和繼續(確定派生類),構成類層次。這個類層次的使用者在使用它們的時候,假如仍然在需要基類的時候寫針對基類的代碼,在需要派生類的時候寫針對派生類的代碼,就等於類層次完全暴露在使用者面前。假如這個類層次有任何的改變(增加了新類),都需要使用者“知道”(針對新類寫代碼)。這樣就增加了類層次與其使用者之間的耦合,有人把這種情況列為程序中的“bad smell”之一。
  
  多態可以使程序員脫離這種窘境。再回頭看看1.1中的例子,bar()作為A-B這個類層次的使用者,它並不知道這個類層次中有多少個類,每個類都叫什麼,但是一樣可以很好的工作,當有一個C類從A類派生出來後,bar()也不需要“知道”(修改)。這完全歸功於多態--編譯器針對虛函數產生了可以在運行時刻確定被調用函數的代碼。
  
  1.3 如何“動態聯編” 編譯器是如何針對虛函數產生可以再運行時刻確定被調用函數的代碼呢?也就是說,虛函數實際上是如何被編譯器處理的呢?Lippman在深度探索C++對象模型[1]中的不同章節講到了幾種方式,這裡把“標准的”方式簡單介紹一下。
  
  我所說的“標准”方式,也就是所謂的“VTABLE”機制。編譯器發現一個類中有被聲明為virtual的函數,就會為其搞一個虛函數表,也就是VTABLE。VTABLE實際上是一個函數指針的數組,每個虛函數占用這個數組的一個slot。一個類只有一個VTABLE,不管它有多少個實例。派生類有自己的VTABLE,但是派生類的VTABLE與基類的VTABLE有相同的函數排列順序,同名的虛函數被放在兩個數組的相同位置上。在創建類實例的時候,編譯器還會在每個實例的內存布局中增加一個vptr字段,該字段指向本類的VTABLE。通過這些手段,編譯器在看到一個虛函數調用的時候,就會將這個調用改寫,針對1.1中的例子:
  
  void bar(A * a)
  
  {
  
  a->foo();
  
  }
  
  會被改寫為:
  
  void bar(A * a)
  
  {
  
  (a->vptr[1])();
  
  }
  
  因為派生類和基類的foo()函數具有相同的VTABLE索引,而他們的vptr又指向不同的VTABLE,因此通過這樣的方法可以在運行時刻決定調用哪個foo()函數。
  
  雖然實際情況遠非這麼簡單,但是基本原理大致如此。
  
  1.4 overload和override 虛函數總是在派生類中被改寫,這種改寫被稱為“override”。我經常混淆“overload”和“override”這兩個單詞。但是隨著各類C++的書越來越多,後來的程序員也許不會再犯我犯過的錯誤了。但是我打算澄清一下:
  
  override是指派生類重寫基類的虛函數,就象我們前面B類中重寫了A類中的foo()函數。重寫的函數必須有一致的參數表和返回值(C++標准答應返回值不同的情況,這個我會在“語法”部分簡單介紹,但是很少編譯器支持這個feature)。這個單詞好象一直沒有什麼合適的中文詞匯來對應,有人譯為“覆蓋”,還貼切一些。
  
  overload約定成俗的被翻譯為“重載”。是指編寫一個與已有函數同名但是參數表不同的函數。例如一個函數即可以接受整型數作為參數,也可以接受浮點數作為參數。 一.簡介
  
  
  虛函數是C++中用於實現多態(polymorphism)的機制。核心理念就是通過基類訪問派生類定義的函數。假設我們有下面的類層次:
  
  class A
  
  {
  
  public:
  
  virtual void foo() { cout << "A::foo() is called" << endl;}
  
  };
  
  class B: public A
  
  {
  
  public:
  
  virtual void foo() { cout << "B::foo() is called" << endl;}
  
  };
  
  那麼,在使用的時候,我們可以:
  
  A * a = new B();
  
  a->foo(); // 在這裡,a雖然是指向A的指針,但是被調用的函數(foo)卻是B的!
  
  這個例子是虛函數的一個典型應用,通過這個例子,也許你就對虛函數有了一些概念。它虛就虛在所謂“推遲聯編”或者“動態聯編”上,一個類函數的調用並不是在編譯時刻被確定的,而是在運行時刻被確定的。由於編寫代碼的時候並不能確定被調用的是基類的函數還是哪個派生類的函數,所以被成為“虛”函數。 虛函數只能借助於指針或者引用來達到多態的效果,假如是下面這樣的代碼,則雖然是虛函數,但它不是多態的:
  
  class A
  
  {
  
  public:
  
  virtual void foo();
  
  };
  
  class B: public A
  
  {
  
  virtual void foo();
  
  };
  
  void bar()
  
  {
  
  A a;
  
  a.foo(); // A::foo()被調用
  
  }
  
  1.1 多態 在了解了虛函數的意思之後,再考慮什麼是多態就很輕易了。仍然針對上面的類層次,但是使用的方法變的復雜了一些:
  
  void bar(A * a)
  
  {
  
  a->foo(); // 被調用的是A::foo() 還是B::foo()?
  
  }
  
  因為foo()是個虛函數,所以在bar這個函數中,只根據這段代碼,無從確定這裡被調用的是A::foo()還是B::foo(),但是可以肯定的說:假如a指向的是A類的實例,則A::foo()被調用,假如a指向的是B類的實例,則B::foo()被調用。
  
  這種同一代碼可以產生不同效果的特點,被稱為“多態”。
  
  1.2 多態有什麼用? 多態這麼神奇,但是能用來做什麼呢?這個命題我難以用一兩句話概括,一般的C++教程(或者其它面向對象語言的教程)都用一個畫圖的例子來展示多態的用途,我就不再重復這個例子了,假如你不知道這個例子,隨便找本書應該都有介紹。我試圖從一個抽象的角度描述一下,回頭再結合那個畫圖的例子,也許你就更輕易理解。
  
  在面向對象的編程中,首先會針對數據進行抽象(確定基類)和繼續(確定派生類),構成類層次。這個類層次的使用者在使用它們的時候,假如仍然在需要基類的時候寫針對基類的代碼,在需要派生類的時候寫針對派生類的代碼,就等於類層次完全暴露在使用者面前。假如這個類層次有任何的改變(增加了新類),都需要使用者“知道”(針對新類寫代碼)。這樣就增加了類層次與其使用者之間的耦合,有人把這種情況列為程序中的“bad smell”之一。
  
  多態可以使程序員脫離這種窘境。再回頭看看1.1中的例子,bar()作為A-B這個類層次的使用者,它並不知道這個類層次中有多少個類,每個類都叫什麼,但是一樣可以很好的工作,當有一個C類從A類派生出來後,bar()也不需要“知道”(修改)。這完全歸功於多態--編譯器針對虛函數產生了可以在運行時刻確定被調用函數的代碼。
  
  1.3 如何“動態聯編” 編譯器是如何針對虛函數產生可以再運行時刻確定被調用函數的代碼呢?也就是說,虛函數實際上是如何被編譯器處理的呢?Lippman在深度探索C++對象模型[1]中的不同章節講到了幾種方式,這裡把“標准的”方式簡單介紹一下。
  
  我所說的“標准”方式,也就是所謂的“VTABLE”機制。編譯器發現一個類中有被聲明為virtual的函數,就會為其搞一個虛函數表,也就是VTABLE。VTABLE實際上是一個函數指針的數組,每個虛函數占用這個數組的一個slot。一個類只有一個VTABLE,不管它有多少個實例。派生類有自己的VTABLE,但是派生類的VTABLE與基類的VTABLE有相同的函數排列順序,同名的虛函數被放在兩個數組的相同位置上。在創建類實例的時候,編譯器還會在每個實例的內存布局中增加一個vptr字段,該字段指向本類的VTABLE。通過這些手段,編譯器在看到一個虛函數調用的時候,就會將這個調用改寫,針對1.1中的例子:
  
  void bar(A * a)
  
  {
  
  a->foo();
  
  }
  
  會被改寫為:
  
  void bar(A * a)
  
  {
  
  (a->vptr[1])();
  
  }
  
  因為派生類和基類的foo()函數具有相同的VTABLE索引,而他們的vptr又指向不同的VTABLE,因此通過這樣的方法可以在運行時刻決定調用哪個foo()函數。
  
  雖然實際情況遠非這麼簡單,但是基本原理大致如此。
  
  1.4 overload和override 虛函數總是在派生類中被改寫,這種改寫被稱為“override”。我經常混淆“overload”和“override”這兩個單詞。但是隨著各類C++的書越來越多,後來的程序員也許不會再犯我犯過的錯誤了。但是我打算澄清一下:
  
  override是指派生類重寫基類的虛函數,就象我們前面B類中重寫了A類中的foo()函數。重寫的函數必須有一致的參數表和返回值(C++標准答應返回值不同的情況,這個我會在“語法”部分簡單介紹,但是很少編譯器支持這個feature)。這個單詞好象一直沒有什麼合適的中文詞匯來對應,有人譯為“覆蓋”,還貼切一些。
  
  overload約定成俗的被翻譯為“重載”。是指編寫一個與已有函數同名但是參數表不同的函數。例如一個函數即可以接受整型數作為參數,也可以接受浮點數作為參數。 QQread.com 推出游戲功略 http://www.qqread.com/netgame/game/index.Html 魔獸世界 跑跑卡丁車 街頭籃球 水浒Q傳 龍與地下城OL 征服  軒轅劍5 FIFA07 熱血江湖 大唐風雲 夢幻西游 武林外傳 一.簡介
  
  
  虛函數是C++中用於實現多態(polymorphism)的機制。核心理念就是通過基類訪問派生類定義的函數。假設我們有下面的類層次:
  
  class A
  
  {
  
  public:
  
  virtual void foo() { cout << "A::foo() is called" << endl;}
  
  };
  
  class B: public A
  
  {
  
  public:
  
  virtual void foo() { cout << "B::foo() is called" << endl;}
  
  };
  
  那麼,在使用的時候,我們可以:
  
  A * a = new B();
  
  a->foo(); // 在這裡,a雖然是指向A的指針,但是被調用的函數(foo)卻是B的!
  
  這個例子是虛函數的一個典型應用,通過這個例子,也許你就對虛函數有了一些概念。它虛就虛在所謂“推遲聯編”或者“動態聯編”上,一個類函數的調用並不是在編譯時刻被確定的,而是在運行時刻被確定的。由於編寫代碼的時候並不能確定被調用的是基類的函數還是哪個派生類的函數,所以被成為“虛”函數。 虛函數只能借助於指針或者引用來達到多態的效果,假如是下面這樣的代碼,則雖然是虛函數,但它不是多態的:
  
  class A
  
  {
  
  public:
  
  virtual void foo();
  
  };
  
  class B: public A
  
  {
  
  virtual void foo();
  
  };
  
  void bar()
  
  {
  
  A a;
  
  a.foo(); // A::foo()被調用
  
  }
  
  1.1 多態 在了解了虛函數的意思之後,再考慮什麼是多態就很輕易了。仍然針對上面的類層次,但是使用的方法變的復雜了一些:
  
  void bar(A * a)
  
  {
  
  a->foo(); // 被調用的是A::foo() 還是B::foo()?
  
  }
  
  因為foo()是個虛函數,所以在bar這個函數中,只根據這段代碼,無從確定這裡被調用的是A::foo()還是B::foo(),但是可以肯定的說:假如a指向的是A類的實例,則A::foo()被調用,假如a指向的是B類的實例,則B::foo()被調用。
  
  這種同一代碼可以產生不同效果的特點,被稱為“多態”。
  
  1.2 多態有什麼用? 多態這麼神奇,但是能用來做什麼呢?這個命題我難以用一兩句話概括,一般的C++教程(或者其它面向對象語言的教程)都用一個畫圖的例子來展示多態的用途,我就不再重復這個例子了,假如你不知道這個例子,隨便找本書應該都有介紹。我試圖從一個抽象的角度描述一下,回頭再結合那個畫圖的例子,也許你就更輕易理解。
  
  在面向對象的編程中,首先會針對數據進行抽象(確定基類)和繼續(確定派生類),構成類層次。這個類層次的使用者在使用它們的時候,假如仍然在需要基類的時候寫針對基類的代碼,在需要派生類的時候寫針對派生類的代碼,就等於類層次完全暴露在使用者面前。假如這個類層次有任何的改變(增加了新類),都需要使用者“知道”(針對新類寫代碼)。這樣就增加了類層次與其使用者之間的耦合,有人把這種情況列為程序中的“bad smell”之一。
  
  多態可以使程序員脫離這種窘境。再回頭看看1.1中的例子,bar()作為A-B這個類層次的使用者,它並不知道這個類層次中有多少個類,每個類都叫什麼,但是一樣可以很好的工作,當有一個C類從A類派生出來後,bar()也不需要“知道”(修改)。這完全歸功於多態--編譯器針對虛函數產生了可以在運行時刻確定被調用函數的代碼。
  
  1.3 如何“動態聯編” 編譯器是如何針對虛函數產生可以再運行時刻確定被調用函數的代碼呢?也就是說,虛函數實際上是如何被編譯器處理的呢?Lippman在深度探索C++對象模型[1]中的不同章節講到了幾種方式,這裡把“標准的”方式簡單介紹一下。
  
  我所說的“標准”方式,也就是所謂的“VTABLE”機制。編譯器發現一個類中有被聲明為virtual的函數,就會為其搞一個虛函數表,也就是VTABLE。VTABLE實際上是一個函數指針的數組,每個虛函數占用這個數組的一個slot。一個類只有一個VTABLE,不管它有多少個實例。派生類有自己的VTABLE,但是派生類的VTABLE與基類的VTABLE有相同的函數排列順序,同名的虛函數被放在兩個數組的相同位置上。在創建類實例的時候,編譯器還會在每個實例的內存布局中增加一個vptr字段,該字段指向本類的VTABLE。通過這些手段,編譯器在看到一個虛函數調用的時候,就會將這個調用改寫,針對1.1中的例子:
  
  void bar(A * a)
  
  {
  
  a->foo();
  
  }
  
  會被改寫為:
  
  void bar(A * a)
  
  {
  
  (a->vptr[1])();
  
  }
  
  因為派生類和基類的foo()函數具有相同的VTABLE索引,而他們的vptr又指向不同的VTABLE,因此通過這樣的方法可以在運行時刻決定調用哪個foo()函數。
  
  雖然實際情況遠非這麼簡單,但是基本原理大致如此。
  
  1.4 overload和override 虛函數總是在派生類中被改寫,這種改寫被稱為“override”。我經常混淆“overload”和“override”這兩個單詞。但是隨著各類C++的書越來越多,後來的程序員也許不會再犯我犯過的錯誤了。但是我打算澄清一下:
  
  override是指派生類重寫基類的虛函數,就象我們前面B類中重寫了A類中的foo()函數。重寫的函數必須有一致的參數表和返回值(C++標准答應返回值不同的情況,這個我會在“語法”部分簡單介紹,但是很少編譯器支持這個feature)。這個單詞好象一直沒有什麼合適的中文詞匯來對應,有人譯為“覆蓋”,還貼切一些。
  
  overload約定成俗的被翻譯為“重載”。是指編寫一個與已有函數同名但是參數表不同的函數。例如一個函數即可以接受整型數作為參數,也可以接受浮點數作為參數。 QQread.com 推出游戲功略 http://www.qqread.com/netgame/game/index.html 魔獸世界 跑跑卡丁車 街頭籃球 水浒Q傳 龍與地下城OL 征服  軒轅劍5 FIFA07 熱血江湖 大唐風雲 夢幻西游 武林外傳 一.簡介
  
  
  虛函數是C++中用於實現多態(polymorphism)的機制。核心理念就是通過基類訪問派生類定義的函數。假設我們有下面的類層次:
  
  class A
  
  {
  
  public:
  
  virtual void foo() { cout << "A::foo() is called" << endl;}
  
  };
  
  class B: public A
  
  {
  
  public:
  
  virtual void foo() { cout << "B::foo() is called" << endl;}
  
  };
  
  那麼,在使用的時候,我們可以:
  
  A * a = new B();
  
  a->foo(); // 在這裡,a雖然是指向A的指針,但是被調用的函數(foo)卻是B的!
  
  這個例子是虛函數的一個典型應用,通過這個例子,也許你就對虛函數有了一些概念。它虛就虛在所謂“推遲聯編”或者“動態聯編”上,一個類函數的調用並不是在編譯時刻被確定的,而是在運行時刻被確定的。由於編寫代碼的時候並不能確定被調用的是基類的函數還是哪個派生類的函數,所以被成為“虛”函數。 虛函數只能借助於指針或者引用來達到多態的效果,假如是下面這樣的代碼,則雖然是虛函數,但它不是多態的:
  
  class A
  
  {
  
  public:
  
  virtual void foo();
  
  };
  
  class B: public A
  
  {
  
  virtual void foo();
  
  };
  
  void bar()
  
  {
  
  A a;
  
  a.foo(); // A::foo()被調用
  
  }
  
  1.1 多態 在了解了虛函數的意思之後,再考慮什麼是多態就很輕易了。仍然針對上面的類層次,但是使用的方法變的復雜了一些:
  
  void bar(A * a)
  
  {
  
  a->foo(); // 被調用的是A::foo() 還是B::foo()?
  
  }
  
  因為foo()是個虛函數,所以在bar這個函數中,只根據這段代碼,無從確定這裡被調用的是A::foo()還是B::foo(),但是可以肯定的說:假如a指向的是A類的實例,則A::foo()被調用,假如a指向的是B類的實例,則B::foo()被調用。
  
  這種同一代碼可以產生不同效果的特點,被稱為“多態”。
  
  1.2 多態有什麼用? 多態這麼神奇,但是能用來做什麼呢?這個命題我難以用一兩句話概括,一般的C++教程(或者其它面向對象語言的教程)都用一個畫圖的例子來展示多態的用途,我就不再重復這個例子了,假如你不知道這個例子,隨便找本書應該都有介紹。我試圖從一個抽象的角度描述一下,回頭再結合那個畫圖的例子,也許你就更輕易理解。
  
  在面向對象的編程中,首先會針對數據進行抽象(確定基類)和繼續(確定派生類),構成類層次。這個類層次的使用者在使用它們的時候,假如仍然在需要基類的時候寫針對基類的代碼,在需要派生類的時候寫針對派生類的代碼,就等於類層次完全暴露在使用者面前。假如這個類層次有任何的改變(增加了新類),都需要使用者“知道”(針對新類寫代碼)。這樣就增加了類層次與其使用者之間的耦合,有人把這種情況列為程序中的“bad smell”之一。
  
  多態可以使程序員脫離這種窘境。再回頭看看1.1中的例子,bar()作為A-B這個類層次的使用者,它並不知道這個類層次中有多少個類,每個類都叫什麼,但是一樣可以很好的工作,當有一個C類從A類派生出來後,bar()也不需要“知道”(修改)。這完全歸功於多態--編譯器針對虛函數產生了可以在運行時刻確定被調用函數的代碼。
  
  1.3 如何“動態聯編” 編譯器是如何針對虛函數產生可以再運行時刻確定被調用函數的代碼呢?也就是說,虛函數實際上是如何被編譯器處理的呢?Lippman在深度探索C++對象模型[1]中的不同章節講到了幾種方式,這裡把“標准的”方式簡單介紹一下。
  
  我所說的“標准”方式,也就是所謂的“VTABLE”機制。編譯器發現一個類中有被聲明為virtual的函數,就會為其搞一個虛函數表,也就是VTABLE。VTABLE實際上是一個函數指針的數組,每個虛函數占用這個數組的一個slot。一個類只有一個VTABLE,不管它有多少個實例。派生類有自己的VTABLE,但是派生類的VTABLE與基類的VTABLE有相同的函數排列順序,同名的虛函數被放在兩個數組的相同位置上。在創建類實例的時候,編譯器還會在每個實例的內存布局中增加一個vptr字段,該字段指向本類的VTABLE。通過這些手段,編譯器在看到一個虛函數調用的時候,就會將這個調用改寫,針對1.1中的例子:
  
  void bar(A * a)
  
  {
  
  a->foo();
  
  }
  
  會被改寫為:
  
  void bar(A * a)
  
  {
  
  (a->vptr[1])();
  
  }
  
  因為派生類和基類的foo()函數具有相同的VTABLE索引,而他們的vptr又指向不同的VTABLE,因此通過這樣的方法可以在運行時刻決定調用哪個foo()函數。
  
  雖然實際情況遠非這麼簡單,但是基本原理大致如此。
  
  1.4 overload和override 虛函數總是在派生類中被改寫,這種改寫被稱為“override”。我經常混淆“overload”和“override”這兩個單詞。但是隨著各類C++的書越來越多,後來的程序員也許不會再犯我犯過的錯誤了。但是我打算澄清一下:
  
  override是指派生類重寫基類的虛函數,就象我們前面B類中重寫了A類中的foo()函數。重寫的函數必須有一致的參數表和返回值(C++標准答應返回值不同的情況,這個我會在“語法”部分簡單介紹,但是很少編譯器支持這個feature)。這個單詞好象一直沒有什麼合適的中文詞匯來對應,有人譯為“覆蓋”,還貼切一些。
  
  overload約定成俗的被翻譯為“重載”。是指編寫一個與已有函數同名但是參數表不同的函數。例如一個函數即可以接受整型數作為參數,也可以接受浮點數作為參數。 QQread.com 推出游戲功略 http://www.qqread.com/netgame/game/index.html 魔獸世界 跑跑卡丁車 街頭籃球 水浒Q傳 龍與地下城OL 征服  軒轅劍5 FIFA07 熱血江湖 大唐風雲 夢幻西游 武林外傳 right">(出處:清風軟件下載學院)
 
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved