相信你們對NULL不會很陌生,NULL 是一個標準規定的宏定義,用來表示空指針常量,當一個指針變量被賦值爲NULL時,表示它再也不指向任何有效地址,沒法在訪問任何數據。在VS2012庫文件stdio.h中有以下定義:數組
1 #ifdef __cplusplus 2 #define NULL 0
3 #else
4 #define NULL ((void *)0)
NULL常常被用做指針初始化行爲,以及free/delete某一指針後對其的賦值操做,這都是咱們所鼓勵的一些好的習慣操做。在對NULL指針進行解引用或者指向操做,會出現coredump或者其餘undefined行爲,而若是delete NULL指針的操做則是合法的。如今來看文中提到的值爲NULL的對象指針,以下面的例子:函數
1 class A 2 { 3 public: 4 void foo() 5 { 6 cout<<"call foo"<<endl; 7 } 8 }; 9 int _tmain(int argc, _TCHAR* argv[]) 10 { 11 A *p = NULL; 12 p->foo(); 13
14 return 0; 15 }
這裏使用NULL指針p,調用成員函數foo(),運行時程序輸出了「call foo",而沒有coredump或者出現別的undefined行爲,這是怎麼回事呢?咱們知道,對於類中的成員函數,隸屬於全部類的對象,程序編譯後,成員函數的地址已經肯定,而成員函數訪問類中的成員變量時,須要藉助this指針對各個對象進行區分。看到類A的成員函數foo原型爲void A::foo();,編譯器會在發生函數在調用時,將其解釋爲void A::foo(A *const this);。注意這裏的參數,this指針表示調用此函數的類A的對象地址。對於調用端,編譯器將進行以下解釋:this
1 A a; 2 a.foo(); //->foo(&a);
3 A *p = NULL; 4 p->foo(); //->foo(p);
例子中傳入的p爲NULL,而在成員函數foo中,並未有用到經過this指針訪問成員變量的語句,因此經過NULL指針調用foo時,獲得了正確的結果。若是咱們改一下類A的定義,添加成員變量,而且在foo中進行訪問,以下:spa
1 class A 2 { 3 public: 4 void foo() 5 { 6 cout<<"call foo"<<endl; 7 cout<<"value = "<<value<<endl; 8 } 9 private: 10 int value; 11 };
這是仍然用p(NULL)指針來調用成員函數foo,則會使程序崩潰,由於編譯器會將foo解釋爲:指針
void A::foo(A *const this) { cout<<"call foo"<<endl; cout<<"value = "<<this->value<<endl; }
這裏傳入的this爲NULL,函數對NULL指針進行了指向操做,產生了爲undefined行爲。code
this指針使得類的成員函數能夠區分開各個對象之間的成員變量,那麼若是類的成員變量屬於類而不是屬於類的對象呢,即static類型的成員對象對象
相信你們對NULL不會很陌生,NULL 是一個標準規定的宏定義,用來表示空指針常量,當一個指針變量被賦值爲NULL時,表示它再也不指向任何有效地址,沒法在訪問任何數據。在VS2012庫文件stdio.h中有以下定義:blog
1 #ifdef __cplusplus 2 #define NULL 0
3 #else
4 #define NULL ((void *)0)
NULL常常被用做指針初始化行爲,以及free/delete某一指針後對其的賦值操做,這都是咱們所鼓勵的一些好的習慣操做。在對NULL指針進行解引用或者指向操做,會出現coredump或者其餘undefined行爲,而若是delete NULL指針的操做則是合法的。如今來看文中提到的值爲NULL的對象指針,以下面的例子:原型
1 class A 2 { 3 public: 4 void foo() 5 { 6 cout<<"call foo"<<endl; 7 } 8 }; 9 int _tmain(int argc, _TCHAR* argv[]) 10 { 11 A *p = NULL; 12 p->foo(); 13
14 return 0; 15 }
這裏使用NULL指針p,調用成員函數foo(),運行時程序輸出了「call foo",而沒有coredump或者出現別的undefined行爲,這是怎麼回事呢?咱們知道,對於類中的成員函數,隸屬於全部類的對象,程序編譯後,成員函數的地址已經肯定,而成員函數訪問類中的成員變量時,須要藉助this指針對各個對象進行區分。看到類A的成員函數foo原型爲void A::foo();,編譯器會在發生函數在調用時,將其解釋爲void A::foo(A *const this);。注意這裏的參數,this指針表示調用此函數的類A的對象地址。對於調用端,編譯器將進行以下解釋:編譯器
1 A a; 2 a.foo(); //->foo(&a);
3 A *p = NULL; 4 p->foo(); //->foo(p);
例子中傳入的p爲NULL,而在成員函數foo中,並未有用到經過this指針訪問成員變量的語句,因此經過NULL指針調用foo時,獲得了正確的結果。若是咱們改一下類A的定義,添加成員變量,而且在foo中進行訪問,以下:
1 class A 2 { 3 public: 4 void foo() 5 { 6 cout<<"call foo"<<endl; 7 cout<<"value = "<<value<<endl; 8 } 9 private: 10 int value; 11 };
這是仍然用p(NULL)指針來調用成員函數foo,則會使程序崩潰,由於編譯器會將foo解釋爲:
void A::foo(A *const this) { cout<<"call foo"<<endl; cout<<"value = "<<this->value<<endl; }
這裏傳入的this爲NULL,函數對NULL指針進行了指向操做,產生了爲undefined行爲。
對於C++中類的靜態函數,它只能訪問類的靜態成員變量,由於在編譯器對靜態函數的解釋不會包含this指針,根據這種特性,咱們知道NULL指針一樣能夠調用類中的靜態成員函數,以此訪問靜態成員變量:
1 class A 2 { 3 public: 4 static void foo() 5 { 6 cout<<"call foo"<<endl; 7 cout<<"value = "<<value<<endl; 8 } 9 private: 10 static int value; 11 }; 12 int A::value = 0; 13
14 int _tmain(int argc, _TCHAR* argv[]) 15 { 16 A *p = NULL; 17 p->foo(); 18 return 0; 19 }
咱們知道類的成員函數中能夠定義普通函數、靜態函數、還能夠定義虛函數,已實現多態的效果,那麼對於類中的虛函數,用NULL指針調用時會有怎樣的後果呢,將上面的代碼簡單修改下:
1 class A 2 { 3 public: 4 virtual void foo() 5 { 6 cout<<"call foo"<<endl; 7 } 8 private: 9 int value; 10 }; 11 int _tmain(int argc, _TCHAR* argv[]) 12 { 13 A *p = NULL; 14 p->foo(); 15
16 return 0; 17 }
運行的結果居然崩潰,foo函數和以前同樣,沒有訪問成員變量value,不會出現NULL指針指向的問題,但程序爲什麼崩潰了呢?緣由在於foo函數被定義爲了虛函數,這些虛函數中的地址(函數指針)保存在虛函數表中,能夠將它看作一個函數指針數組(列表),含有虛函數的類擁有一張虛函數表,而此類的每個對象在構造時,由編譯器將一個指向虛函數表的指針安插進來,成爲虛表指針,而它的做用是在咱們調用虛函數時,能更準確的訪問到虛函數表中此函數的指針,從而進行調用。回過頭來看看以前的例子,咱們根據類A的NULL指針p調用它的虛函數,而此時因爲沒有進行初始化,因此虛指針沒有安插進去,因此程序崩潰。