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