nullptr

nullptr

空指针:从 0 到 NULL,再到 nullptr

NULL 是一个宏定义:

#undef NULL
#if defined(__cplusplus)
#define NULL 0
#else
#define NULL ((void *)0)
#endif
int *my_ptr = 0;
int *my_ptr = NULL;
// NULL 的问题
#include 

void f(char *c) {
    printf("invoke f(char *)\n);
}
void f(int i) {
    printf("invoke f(int)\n");
}
int main() {
    f(0);
    f(NULL);     // 注意:如果gcc编译,NULL会转换为内部标识符 __null,该语句会编译失败
    f((char*)0);
}
/*
 * 使用 XLC 编译器会得到如下结果:
 * invoke f(int)
 * invoke f(int)
 * invoke f(char*)
 * XLC 将 NULL 定义为了 0
*/

引起该问题的原因是 0 的二义性。0 既可以表示整型,也可以表示一个 空指针(void *)。存在二义性时,需要使用强制类型转换:((void *)0).

虽然 g++ 编译器将 NULL 转换为编译器内部标识符(__null),并在编译时期进行了分析,在一定程度上可以缓解问题,但是会带来代码移植的限制。

nullptr

// 头文件: cstddef
typedef decltype(nullptr) nullptr_t;
/*
 使用 nullptr_t 必须包含头文件: cstddef。
 使用 nullptr 则不需要
*/

nullptr 最大的优势是 有类型,且可以被隐式转换为指针类型。

#include 

void f(char *c) {
    printf("invoke f(char *)\n);
}
void f(int i) {
    printf("invoke f(int)\n");
}
int main() {
    f(nullptr); // invoke f(char *)
    f(0);       // invoke f(int)
}

nullptr 和 nullptr_t

C++11 不仅定义了空指针常量 nullptr,也定义了 空指针类型 nullptr_t。那么也就是说 nullptr_t 可以用来定义变量。通常,可以使用 nullptr_t 声明一个 空指针变量。

  • C++11规定:
    • 所有定义为 nullptr_t 类型的数据都是等价的,行为也是完全一致。
    • nullptr_t 类型数据可以隐式转换为任意一个指针类型。
    • nullptr_t 类型数据不能转化为 非指针类型,即使使用 reinterpret_cast()。
    • nullptr_t 类型数据不适用于算术运算表达式。
    • nullptr_t 类型数据可以用于关系运算表达式,但仅能与 nullptr_t 类型数据或指针类型数据做比较,当且仅当关系运算符为 ==, <=, >=时返回 true.
#include 
#include 
using namespace std;
int main()
{
    // nullptr 可以隐式转换为 char*
    char *cp = nullptr;

    // 不可转换为整型,而其他类型也不能转换为 nullptr_t
    // int n1 = nullptr;
    // int n2 = reinterpret_cast(nullptr);
    // nullptr 与 nullptr_t 类型变量可以比较
    // 当使用 ==, <=, >= 符号比较时返回 true。
    nullptr_t nptr;
    if (nptr == nullptr) {
        cout << "nullptr_t nptr == nullptr" << endl;
    } else {
        cout << "nullptr_t nptr != nullptr" << endl;
    }
    if (nptr < nullptr) {
        cout << "nullptr nptr < nullptr" << endl;
    } else {
        cout << "nullptr_t nptr !< nullptr" << endl; 
    }

    // 不能转换为整型或 bool 类型
    // if (0 == nullptr);
    // if (nullptr);

    // 不可以进行算术运算
    // nullptr += 1;
    // nullptr * 5;

    // 以下操作均可以正常运行
    sizeof(nullptr);
    typeid(nullptr);
    throw(nullptr);

    return 0;
}

虽然 nullptr_t 看起来像是一个 指针类型,但是在把 nullptr_t 应用于模板时,我们发现模板却只能把他作为一个普通的类型来推导。(并不会将其视为 T* 指针)

using namespace std;
template void g(T* t) {}
template void h(T t) {}

int main()
{
    g(nullptr);          // error,nullptr 时 nullptr_t 类型,而不是指针
    g((float*)nullptr);  // T = float

    h(0);                // T = int
    h(nullptr);          // T = nullptr_t
    h((float*)nullptr);  // T = float *

}

一些关于 nullptr 规则的讨论

  • sizeof(nullptr_t) == sizeof(void *)

  • nullptr 是一个编译时期的常量,它的名字是一个编译时期的关键字,能够为编译器识别。

  • (void *)0 只是一个强制转换表达式,其返回的也是一个 void * 指针类型

  • nullptr 到任何指针的转换都是隐式的。

  • nullptr 对象的地址可以被用户使用

#include 
#include 
using namespace std;
int main()
{
    nullptr_t my_null;
    printf("%x\n", &my_null);
    
    // printf("%x", &nullptr);  // 根据 C++11 的规定,nullptr 是右值常量,无法取地址
    
    printf("%d\n", my_null == nullptr);

    const nullptr_t && default_nullptr = nullptr;
    // default_nullptr 是 nullptr 的一个右值引用
    printf("%x\n", &default_nullptr);
}

你可能感兴趣的:(nullptr)