【C++】NULL和nullptr的關聯與差別

在寫代碼的過程中,有時候需要將指針賦值爲空指針,以防止野指針。在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;
  1. nullptr_t是一種數據類型,而nullptr是該類型的一個實例。通常情況下,也可以通過nullptr_t類型創建另一個新的實例;
  2. 所有定義爲nullptr_t類型的數據都是等價的,行爲也是完全一致的;
  3. std::nullptr_t類型,並不是指針類型,但可以隱式轉換成任意一個指針類型(注意不能轉換爲非指針類型,強轉也不行)
  4. nullptr_t類型的數據不適用於算術運算表達式。但可以用於關係運算表達式(僅能與nullptr_t類型數據或指針類型數據進行比較,當且僅當關係運算符爲==、<=、>=等時)。

nullptr與NULL的區別

  1. NULL是一個宏定義,C++中通常將其定義爲0,編譯器一般優先把它當作一個整型常量(C標準下定義爲(void*)0);
  2. nullptr是一個編譯期常量,其類型爲nullptr_t。它既不是整型類型,也不是指針類型;
  3. 在模板推導中,nullptr被推導爲nullptr_t類型,仍可隱式轉爲指針。但0或NULL則會被推導爲整型類型
  4. 要避免在整型和指針間進行函數重載。因爲NULL會被匹配到整型形參版本的函數,而不是預期的指針版本

nullptr與(void*)0的區別

  1. nullptr到任意類型指針的轉換是隱式的(儘管nullptr不是指針類型,但仍可當指針使用);
  2. (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,可以保證在任何情況下都代表空指針


相關閱讀

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章