nullptr

作者: ColdWave | 来源:发表于2018-08-02 23:27 被阅读0次

    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 <stdio.h>
    
    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 <stdio.h>
    
    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 类型数据可以用于关系运算表达式,但仅能与 nullptr_t 类型数据或指针类型数据做比较,当且仅当关系运算符为 ==, <=, >=时返回 true.
    #include <iostream>
    #include <typeinfo>
    using namespace std;
    int main()
    {
        // nullptr 可以隐式转换为 char*
        char *cp = nullptr;
    
        // 不可转换为整型,而其他类型也不能转换为 nullptr_t
        // int n1 = nullptr;
        // int n2 = reinterpret_cast<int>(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<typename T> void g(T* t) {}
    template<typename T> 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 <cstdio>
    #include <cstddef>
    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

          本文链接:https://www.haomeiwen.com/subject/lgeevftx.html