1. 程式人生 > >C++中的靜態繫結和動態繫結

C++中的靜態繫結和動態繫結

(感謝原作者分享:http://www.cnblogs.com/lizhenghn/p/3657717.html)

C++在面向物件程式設計中,存在著靜態繫結和動態繫結的定義,本節即是主要講述這兩點區分。
我是在一個類的繼承體系中分析的,因此下面所說的物件一般就是指一個類的例項。
首先我們需要明確幾個名詞定義:

  • 靜態型別:物件在宣告時採用的型別,在編譯期既已確定;
  • 動態型別:通常是指一個指標或引用目前所指物件的型別,是在執行期決定的;
  • 靜態繫結:繫結的是靜態型別,所對應的函式或屬性依賴於物件的靜態型別,發生在編譯期;
  • 動態繫結:繫結的是動態型別,所對應的函式或屬性依賴於物件的動態型別,發生在執行期;

從上面的定義也可以看出,非虛擬函式一般都是靜態繫結,而虛擬函式都是動態繫結(如此才可實現多型性)。
先看程式碼和執行結果:

複製程式碼
 1 class A
 2 {
 3 public:
 4     /*virtual*/ void func(){ std::cout << "A::func()\n"; }
 5 };
 6 class B : public A
 7 {
 8 public:
 9     void func(){ std::cout << "B::func()\n"; }
10 };
11 class C : public A
12 {
13 public:
14     void func(){ std::cout << "C::func()\n"; }
15 };
複製程式碼

下面逐步分析測試程式碼及結果,

1 C* pc = new C(); //pc的靜態型別是它宣告的型別C*,動態型別也是C*;
2 B* pb = new B(); //pb的靜態型別和動態型別也都是B*;
3 A* pa = pc;      //pa的靜態型別是它宣告的型別A*,動態型別是pa所指向的物件pc的型別C*;
4 pa = pb;         //pa的動態型別可以更改,現在它的動態型別是B*,但其靜態型別仍是宣告時候的A*;
5 C *pnull = NULL; //pnull的靜態型別是它宣告的型別C*,沒有動態型別,因為它指向了NULL;

如果明白上面程式碼的意思,請繼續, 

1
pa->func(); //A::func() pa的靜態型別永遠都是A*,不管其指向的是哪個子類,都是直接呼叫A::func(); 2 pc->func(); //C::func() pc的動、靜態型別都是C*,因此呼叫C::func(); 3 pnull->func(); //C::func() 不用奇怪為什麼空指標也可以呼叫函式,因為這在編譯期就確定了,和指標空不空沒關係;

如果註釋掉類C中的func函式定義,其他不變,即

複製程式碼
1 class C : public A
2 {
3 };
4 
5 pa->func();      //A::func() 理由同上;
6 pc->func();      //A::func() pc在類C中找不到func的定義,因此到其基類中尋找;
7 pnull->func();   //A::func() 原因也解釋過了;
複製程式碼

如果為A中的void func()函式新增virtual特性,其他不變,即

複製程式碼
1 class A
2 {
3 public:
4     virtual void func(){ std::cout << "A::func()\n"; }
5 };
6 
7 pa->func();      //B::func() 因為有了virtual虛擬函式特性,pa的動態型別指向B*,因此先在B中查詢,找到後直接呼叫;
8 pc->func();      //C::func() pc的動、靜態型別都是C*,因此也是先在C中查詢;
9 pnull->func();   //空指標異常,因為是func是virtual函式,因此對func的呼叫只能等到執行期才能確定,然後才發現pnull是空指標;
複製程式碼

分析:
在上面的例子中,

1. 如果基類A中的func不是virtual函式,那麼不論pa、pb、pc指向哪個子類物件,對func的呼叫都是在定義pa、pb、pc時的靜態型別決定,早已在編譯期確定了。

    同樣的空指標也能夠直接呼叫no-virtual函式而不報錯(這也說明一定要做空指標檢查啊!),因此靜態繫結不能實現多型;

2. 如果func是虛擬函式,那所有的呼叫都要等到執行時根據其指向物件的型別才能確定,比起靜態繫結自然是要有效能損失的,但是卻能實現多型特性;

 本文程式碼裡都是針對指標的情況來分析的,但是對於引用的情況同樣適用。

至此總結一下靜態繫結和動態繫結的區別
1. 靜態繫結發生在編譯期,動態繫結發生在執行期;

2. 物件的動態型別可以更改,但是靜態型別無法更改;

3. 要想實現動態,必須使用動態繫結;

4. 在繼承體系中只有虛擬函式使用的是動態繫結,其他的全部是靜態繫結;

 建議:

絕對不要重新定義繼承而來的非虛(non-virtual)函式(《Effective C++ 第三版》條款36),因為這樣導致函式呼叫由物件宣告時的靜態型別確定了,而和物件本身脫離了關係,沒有多型,也這將給程式留下不可預知的隱患和莫名其妙的BUG;

另外,在動態繫結也即在virtual函式中,要注意預設引數的使用。當預設引數和virtual函式一起使用的時候一定要謹慎,不然出了問題怕是很難排查。
看下面的程式碼:

複製程式碼
 1 class E
 2 {
 3 public:
 4     virtual void func(int i = 0)
 5     { 
 6         std::cout << "E::func()\t"<< i <<"\n";
 7     }
 8 };
 9 class F : public E
10 {
11 public:
12     virtual void func(int i = 1)
13     {
14         std::cout << "F::func()\t" << i <<"\n";
15     }
16 };
17 
18 void test2()
19 {
20     F* pf = new F();
21     E* pe = pf;
22     pf->func(); //F::func() 1  正常,就該如此;
23     pe->func(); //F::func() 0  哇哦,這是什麼情況,呼叫了子類的函式,卻使用了基類中引數的預設值!
24 }
複製程式碼

為什麼會有這種情況,請看《Effective C++ 第三版》 條款37。
這裡只給出建議:
絕對不要重新定義一個繼承而來的virtual函式的預設引數值,因為預設引數值都是靜態繫結(為了執行效率),而virtual函式卻是動態繫結。

相關推薦

C++靜態動態

(感謝原作者分享:http://www.cnblogs.com/lizhenghn/p/3657717.html) C++在面向物件程式設計中,存在著靜態繫結和動態繫結的定義,本節即是主要講述這兩點區分。 我是在一個類的繼承體系中分析的,因此下面所說的物件一般就是指一個類的例項。 首先我們需要明確幾個名詞定

C++)C++多型性靜態動態

靜態繫結和動態繫結是C++多型性的一種特性。 1、物件的靜態型別和動態型別: 物件的靜態型別: 物件在宣告是採用的型別,在編譯期確定; 物件的動態型別: 當前物件所指的型別,在執行期決定,物件的動態型別可以更改,但靜態型別無法更改。 class B{ }; clas

Java靜態動態

Java靜態繫結和動態繫結 靜態繫結 動態繫結 將方法呼叫連線到方法體稱為繫結。也就是通過呼叫方法連線到具體的方法體。 Java兩種繫結模式 首先了解一下變數和型別 每個基本變數都會有個型別 int a =1;double b =

Java靜態變量動態變量

.com pan 1-1 一道 args ict print stat 類的加載 這是我面試遇到的一道題,題目如下: 1 public class StaticTest { 2 private static int b = 1; 3 priv

C++靜態成員變數靜態成員函式的用法

昨天在修改專案函式功能時,在其中一個類中添加了一個靜態成員變數和兩個靜態成員函式,結果在連結時出現一個非常典型的錯誤:“The following symbols referenced in processor “p0” could not be resolve

C++的靜態聯編動態聯編技術

聯編是指一個計算機程式自身彼此關聯的過程。按照聯編所進行的階段不同,可分為兩種不同的聯編方法:靜態聯編和動態聯編。 靜態聯編 靜態聯編是指聯編工作出現在編譯連線階段,這種聯編又稱早期聯編,因為這種聯編過程是在程式開始執行之前完成的。 在編譯時所進行的這種聯編又稱靜態束定。在編

c++的靜態多型動態多型(筆記)

多型(polymorphism)一詞最初來源於希臘語polumorphos,含義是具有多種形式或形態的情形。在程式設計領域,一個廣泛認可的定義是“一種將不同的特殊行為和單個泛化記號相關聯的能力”。和純粹的面向物件程式設計語言不同,C++中的多型有著更廣泛的含義。除了常見

Java靜態動態事例

在這個例子中使用Java面向物件中的多型來簡單介紹靜態繫結和動態繫結的區別。 /* * 建立一個Father類。 * 為了方便獲取,成員變數a設定為public的。 * */ class Father{ public int a = 10; public void saying(

“全棧2019”Java第五十五章:方法的靜態動態

難度 初級 學習時間 10分鐘 適合人群 零基礎 開發語言 Java 開發環境 JDK v11 IntelliJ IDEA v2018.3 文章原文連結 “全棧2019”Java第五十五章:方法的靜態繫結與動態繫結 下一章 “全棧2019”Java第五十六章:多型與欄位

C語言呼叫靜態庫函式動態庫函式的方式

C語言中呼叫動態庫函式的兩種方式 方式一.隱式呼叫 將動態庫的相關檔案拷貝到當前目錄下(lib、dll),然後新增以下程式碼,在程式中指定連線庫函式。 注意:第二個引數給出的是引入庫檔案(或稱“匯出庫檔案”),而不是dll。在程式執行過程中,lib將dll中需要用到的函式對映到對應的記憶

Java方法的靜態動態講解(向上轉型的執行機制詳解)

   今天看設計模式-模板方法模式時發現一個實現父類呼叫子類方法的效果的程式碼,不理解其中的原理,然後詢問大佬之後,發現這原來是動態繫結的知識,所以惡補了一下。 package com.practice; /** * 父類呼叫子類的方法 * * @author li

C++的虛擬函式與靜態聯編動態聯編

          程式在呼叫函式時,將使用哪個可執行程式碼塊呢?編譯器負責回答這個問題,將原始碼中的函式呼叫解釋為執行特定的函式程式碼塊被稱為函式名聯編。在C中,因為每個函式名都對應一個不同的函式,而在C++中,由於函式過載的緣故,編譯器必須檢視函式引數以及函式名才能確定

Java靜態動態

程式繫結的概念:繫結指的是一個方法的呼叫與方法所在的類(方法主體)關聯起來。對java來說,繫結分為靜態繫結和動態繫結;或者叫做前期繫結和後期繫結. 靜態繫結:在程式執行前方法已經被繫結(也就是說在編譯過程中就已經知道這個方法到底是哪個類中的方法),此時由編譯器或其它連線程

bootstrap-select下拉框模糊搜尋動態資料

前言 今天做專案的時候,需要對下拉框做一個模糊搜尋,就想到了bootstrap-select外掛, 在運用中遇到的問題進行總結下。 引入 引入bootstrap和bootstrap-select檔案 <link rel="stylesheet

c++靜態動態庫的建立與連結

2、動態庫      在使用動態庫的時候,往往提供兩個檔案:一個引入庫(.lib)和一個DLL(.dll)檔案。雖然引入庫的字尾也是.lib ,但是動態庫的引入庫檔案和靜態庫檔案有著本質的區別,對一個DLL來說,其引入庫檔案(.lib)包含DLL匯出的函式和變數的符號名,而.dll檔案包含該DLL實際的函式

java 上溯造型(向上轉型)動態

/* * 理解什麼叫 “向上轉型” * 定義一個子類Cat,它繼承至Animal類,現在這樣定義: Animal a = new Cat();這代表什麼意思呢? * 解釋:首先我們定義了一個Anima

多型動態--定義

package test; class Employee{ public String getSalary(){ return "基本工資"; } } class Manager extends Employee{ public String get

java繼承向上轉型向下轉型動態

1 概念: 把引用變數轉化為子類型別,則成為向下轉型。如果把引用變數轉化為父類型別,則成為向上轉型。  Java程式碼 publicclass Base {   /**       * 父類例項變數       */    String var = "baseVar";   /**      

DHCP靜態地址分配ARP的理解

DHCP靜態地址分配 某IP地址只能被指定的裝置(mac地址)使用 設A是一個IP地址,a是一臺裝置。使用DHCP靜態地址分配將A分配給a。 那麼IP地址A只能被裝置a使用,但是a也可以使用其他IP地址。 如果裝置a使用了其他IP地址,那麼IP

例項分析JavaScript的事件委託事件

 我們在學習JavaScript中,難免都會去網上查一些資料。也許偶爾就會遇到“事件委託”(也有的稱我“事件代理”,這裡不評論誰是誰非。以下全部稱為“事件委託”),尤其是在查JavaScript的事件處理的時候。但是,大多數時說的是“事件繫結”,對於“事件委託”,或是