在寫代碼的過程中,有時候需要將指針賦值爲空指針,以防止野指針。在C中,都是使用NULL來實現的;在C++中,除了NULL之外,還提供了nullptr來進行定義。那麼兩者之間有什麼區別呢,分別適用於什麼類型的場景呢?
本文實例源碼github地址:https://github.com/yngzMiao/yngzmiao-blogs/tree/master/2020Q1/20200329。
NULL在C/C++中的含義
NULL
是一個宏定義,它的值是一個空指針常量,由實現來進行定義。C語言中常數0和(void*)0都是空指針常量;C++中常數0是,而(void*)0 不是。
那問題來了,爲什麼C中(void*)0是空指針常量,而C++中不是?
因爲C語言中任何類型的指針都可以(隱式地)轉換爲void* 型,反過來也行;而C++中void* 型不能隱式地轉換爲別的類型指針(例如:int* p = (void*)0,使用C++編譯器編譯會報錯)。
可以查看到NULL的宏定義內容:NULL在C和C++中的定義不同,C中NULL爲(void * )0,而C++中NULL爲整數0。
// C語言中NULL定義
#define NULL (void*)0 //c語言中NULL爲void類型的指針,但允許將NULL定義爲0
// C++中NULL的定義
#ifndef NULL
#ifdef _cpluscplus //用於判定是c++類型還是c類型
#define NULL 0 //c++中將NULL定義爲整數0
#else
#define NULL ((void*)0) //C語言中NULL爲void類型的指針
#endif
#endif
那既然C/C++標準中,常數0都可作爲空指針常量,爲什麼不統一使用0?
可能覺得由於(void * )0更能體現指針的意義,而常數0更多的時候是用作整數。因此,C語言中NULL定義選擇了(void*)0。
因此,如果在C++中如下定義:
int *p = NULL;
實際表示將指針p的值賦爲0,而C++中當一個指針的值爲0時,認爲指針爲空指針。
但是,如果單純這樣設計,在使用過程中可能會產生一個問題:
#include <iostream>
void f(int) {
std::cout <<"invoke f(int)" << std::endl;
}
void f(void*) {
std::cout << "invoke f(void*)" << std::endl;
}
int main(int argc, char *argv[]) {
f(0);
f(NULL);
return 0;
}
當實參是NULL的時候,到底表示的是0調用f(int)函數,還是表示指針調用f(void*)呢?絕大多數編譯器都是選擇調用f(int)函數,當然也有的編譯器直接編譯出錯。
nullptr
爲了避免上面的情況出現,C++11引入了一個新關鍵字nullptr
(也有的稱之爲:空指針常量),它的類型爲std::nullptr_t
。在C++中,void * 不能隱式地轉化爲任意類型的指針(可以強制轉化),但空指針常數可以隱式地轉換爲任意類型的指針類型。
nullptr與nullptr_t
在stddef.h
中有如下的描述:
typedef decltype(nullptr) nullptr_t;
- nullptr_t是一種數據類型,而nullptr是該類型的一個實例。通常情況下,也可以通過nullptr_t類型創建另一個新的實例;
- 所有定義爲nullptr_t類型的數據都是等價的,行爲也是完全一致的;
- std::nullptr_t類型,並不是指針類型,但可以隱式轉換成任意一個指針類型(注意不能轉換爲非指針類型,強轉也不行);
- nullptr_t類型的數據不適用於算術運算表達式。但可以用於關係運算表達式(僅能與nullptr_t類型數據或指針類型數據進行比較,當且僅當關係運算符爲==、<=、>=等時)。
nullptr與NULL的區別
- NULL是一個宏定義,C++中通常將其定義爲0,編譯器一般優先把它當作一個整型常量(C標準下定義爲(void*)0);
- nullptr是一個編譯期常量,其類型爲nullptr_t。它既不是整型類型,也不是指針類型;
- 在模板推導中,nullptr被推導爲nullptr_t類型,仍可隱式轉爲指針。但0或NULL則會被推導爲整型類型;
- 要避免在整型和指針間進行函數重載。因爲NULL會被匹配到整型形參版本的函數,而不是預期的指針版本。
nullptr與(void*)0的區別
- nullptr到任意類型指針的轉換是隱式的(儘管nullptr不是指針類型,但仍可當指針使用);
- (void*)0只是一個強制轉換表達式,其返回void*指針類型,只能經過類型轉換到其他指針才能用。
例如:
#include <iostream>
int main(int argc, char *argv[]) {
void* px = NULL;
// int* py = (void*)0; //編譯錯誤,不能隱式將void*轉爲int*類型
int* pz = (int*)px; //void*不能隱式轉爲int*,必須強制轉換!
int* pi = nullptr; //ok!nullptr可以隱式轉爲任何其他指針類型
void* pv = nullptr; //ok! nullptr可以隱式轉爲任何其他指針類型
return 0;
}
總結
NULL在C語言中是(void * )0,在C++中卻是0。這是因爲在C++中void * 類型是不允許隱式轉換成其他指針類型的,所以之前C++中用0來代表空指針。但是,在重載整型和指針的情況下,會出現匹配錯誤的情況。所以,C++11加入了nullptr,可以保證在任何情況下都代表空指針。