C++中的靜態綁定和動態綁定

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函數定義,其餘不變,即spa

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是空指針;

分析:
在上面的例子中,code

1. 若是基類A中的func不是virtual函數,那麼不論pa、pb、pc指向哪一個子類對象,對func的調用都是在定義pa、pb、pc時的靜態類型決定,早已在編譯期肯定了。對象

    一樣的空指針也可以直接調用no-virtual函數而不報錯(這也說明必定要作空指針檢查啊!),所以靜態綁定不能實現多態;blog

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函數倒是動態綁定。

相關文章
相關標籤/搜索