• Effective Modern C++[实践]->优先使用nullptr,而非0或NULL


    1. 优先使用 nullptr,而不是 0 和 NULL
    2. 避免在整数和指针类型上重载

    回看旧识

    空指针

    空指针(null pointer)不指向任何对象,在试图使用一个指针之前代码可以首先检查它是否为空,以下是几个生成空指针的方法。

    int *p1 = nullptr;//等价于int *p1=0;
    int *p2 = 0; //直接将p2初始化为字面常量0
    //需要首先#include<cstdlib>
    int *p3 = NULL;//等价于int *p3 = 0;
    
    • 1
    • 2
    • 3
    • 4

    void*

    1. void *也称为泛型指针,是一种特殊的指针类型,可用于存放任意对象的地址即可以指向任何数据类型的对象。
    2. void *是没有关联数据类型的指针。 void *可以保存任何类型的地址,并且可以转换为任何类型。
    3. malloc()calloc() 返回void * 类型,并且这允许使用这些函数来分配任何数据类型的内存(仅仅因为void*)。
    4. void指针像普通指针一样声明,使用void关键字作为指针的类型:void* ptr;

    示例

    int nValue;
    float fValue;
    struct Something{
        int n;
        float f;
    };
    Something sValue;
    void* ptr;
    ptr = &nValue; // valid
    ptr = &fValue; // valid
    ptr = &sValue; // valid
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    c++中必须显式地将malloc的返回值类型转换为(int *)

    C允许将void*指针赋值给任何指针类型,而不需要强制转换,而在c++中则不可以。在c++中,必须显式地对void*指针进行类型转换

    例如,以下内容在C中是有效的,但在c++中可以:

    void* ptr;
    int *i = ptr; // 从void*到int*的隐式转换
    
    • 1
    • 2

    类似地,

    int *j = malloc(sizeof(int) * 5);  // 从void*到int*的隐式转换
    
    • 1

    为了使上述代码在c++中也能编译,我们必须使用显式类型转换,如下所示,

    void* ptr;
    int *i = (int *) ptr;
    int *j = (int *) malloc(sizeof(int) * 5);
    
    • 1
    • 2
    • 3

    不知void *所指,如何强转

    #include <iostream>
    #include <cassert>
    
    enum class Type{
        tInt, // 注意:我们不能在这里使用“int”,因为它是一个关键字,所以我们将使用“tInt”
        tFloat,
        tCString
    };
    
    void printValue(void* ptr, Type type){
        switch (type){
        case Type::tInt:
            std::cout << *static_cast<int*>(ptr) << '\n'; 
            break;
        case Type::tFloat:
            std::cout << *static_cast<float*>(ptr) << '\n'; 
            break;
        case Type::tCString:
            std::cout << static_cast<char*>(ptr) << '\n'; 
            break;
        default:
            assert(false && "type not found");
            break;
        }
    }
    
    int main(){
        int nValue{ 5 };
        float fValue{ 7.5f };
        char szValue[]{ "Mollie" };
    
        printValue(&nValue, Type::tInt);
        printValue(&fValue, Type::tFloat);
        printValue(szValue, Type::tCString);
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    运行结果

    5
    7.5
    Mollie
    
    • 1
    • 2
    • 3

    void* 不能直接解引用

    因为void指针不知道它所指向的对象类型,所以解除对void指针的引用是非法的。相反,在执行解引用之前,必须首先将void指针转换为另一种指针类型。

    下述代码无法通过编译

    #include <iostream>
    using namespace std;
     
    int main(){
        int a = 10;
        void* ptr = &a;
        cout << *ptr;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    编译报错如下:

    source>: In function 'int main()':
    <source>:8:13: error: 'void*' is not a pointer-to-object type
        8 |     cout << *ptr;
          |             ^~~~
    
    • 1
    • 2
    • 3
    • 4

    如欲取址修改如下,此项必须确保转换后所得的类型就是指针所指的类型,类型一旦不符,将产生未定义的后果。

    #include <iostream>
    using namespace std;
     
    int main(){
        int a = 10;
        void* ptr = &a;
        int *ptr1 = static_cast<int*>(ptr);
        cout << *ptr1;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    运行结果如下:

    10
    
    • 1

    C标准不允许使用空指针进行指针算术运算。

    不可能对void指针进行指针运算。 这是因为指针运算需要指针知道它所指向的对象大小,因此它可以适当地递增或递减指针。

    在这里插入图片描述
    GNU C中,考虑到void的大小为1是允许的。

    • 在GNU C中,对指向void的指针和指向函数的指针支持加减操作。这是通过将void或函数的大小视为1来实现的。
    • 这样做的结果是,在void和函数类型上也允许sizeof,并返回1。
    • 选项-Wpointer-arith会在使用这些扩展时发出警告。

    void *与空指针(null pointer)区别?

    void指针可以指向任何类型的对象,但不知道它指向的是什么类型的对象。void指针必须显式转换为另一种类型的指针才能执行间接转换。空指针是指不指向地址的指针。void指针可以是空指针。因此,void指针指的是指针的类型,而null pointer指的是指针的值(地址)。

    NULL

    C中说明

    NULL 是实现定义的空指针常量,可为

    1. 值为 ​0​ 的整数常量表达式
    2. 转型为 void* 的值为 ​0​ 的整数常量表达式

    空指针常量能转换为任何类型;转换结果是该类型的空指针值。
    可能的实现

    // 兼容 C++ :
    #define NULL 0
    // 不兼容 C++ :
    #define NULL (10*2 - 20)
    #define NULL ((void*)0)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    c++中说明

    宏 NULL 是实现定义的空指针常量,可为

    1. 求值为零的整数类型字面常量表达式右值 (C++11 前)
    2. 零值整数字面量,或为 std::nullptr_t 类型纯右值(C++11 起)

    空指针常量可以隐式转换为任何指针类型;这种转换结果是该类型的空指针值。若空指针常量拥有整数类型,它亦可转换为 std::nullptr_t 类型纯右值。
    c++中可能的实现

    #define NULL 0
    // C++11 起
    #define NULL nullptr
    
    • 1
    • 2
    • 3

    C 中,宏 NULL 可以拥有类型 void* ,但这在 C++ 中不允许。

    nullptr

    考虑下面这两个foo函数:

    void foo(char*);
    void foo(int);
    
    • 1
    • 2

    那么 foo(NULL); 这个语句将会去调用 foo(int),从而导致代码违反直觉。为了解决这个问题,C++11引入了nullptr关键字,专门用来区分空指针、 0。

    关键词 nullptr 代表指针字面量。它是 std::nullptr_t 类型的纯右值。存在从nullptr到任何指针类型及任何成员指针类型的隐式转换。同样的转换对于任何空指针常量也存在,空指针常量包括 std::nullptr_t 的值,以及宏 NULL

    #include <cstddef>
    #include <iostream>
     
    template<class T>
    constexpr T clone(const T& t){
        return t;
    }
     
    void g(int*){
        std::cout << "函数 g 已调用\n";
    }
     
    int main(){
        g(nullptr);        // 良好
        g(NULL);           // 良好
        g(0);              // 良好
     
        g(clone(nullptr)); // 良好
    //  g(clone(NULL));    // 错误:非字面量的零不能为空指针常量
    //  g(clone(0));       // 错误:非字面量的零不能为空指针常量
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    std::nullptr_t

    typedef decltype(nullptr) nullptr_t;(C++11 起)
    std::nullptr_t 是空指针字面量 nullptr 的类型。它是既非指针类型亦非指向成员指针类型的独立类型。

    #include <cstddef>
    #include <iostream>
     
    void f(int*){
       std::cout << "Pointer to integer overload\n";
    }
     
    void f(double*){
       std::cout << "Pointer to double overload\n";
    }
     
    void f(std::nullptr_t){
       std::cout << "null pointer overload\n";
    }
     
    int main(){
        int* pi {}; double* pd {};
     
        f(pi);
        f(pd);
        f(nullptr); // 无 void f(nullptr_t) 可能有歧义
        // f(0);    // 歧义调用:三个函数全部为候选
        // f(NULL); // 若 NULL 是整数空指针常量则为歧义
                    // (如在大部分实现中的情况)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    拥抱nullptr

    避免重载决议

    文中开头第2条,不要在指针和整形之间做重载,对c++那个版本都是适用的准则。这样可以规避重载决议,但nullptr会是更好的选择。
    示例见第一章的nullptr小节

    让模板更清晰

    示例如下:

    #include <iostream>
    
    int func(void *ptr){
        return 0;
    }
    template<typename FuncType,
             typename PtrType>
    decltype(auto) Call(FuncType func,PtrType ptr) {
        return func(ptr);
    }
    int main(){
        // auto r1 = Call(func,0);// error: invalid conversion from 'int' to 'void*'
        // auto r2 = Call(func,NULL);//error: invalid conversion from 'long int' to 'void*'
        auto r3 = Call(func,nullptr);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    auto r3 = Call(func,nullptr);实例后的函数如下

    template<>
    int Call<int (*)(void *), nullptr_t>(int (*func)(void *), nullptr_t ptr)
    {
      return func(ptr);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    参考文献

    [1] C 语言中 void* 详解及应用
    [2] How does ‘void*’ differ in C and C++?
    [3] Should the compiler warn on pointer arithmetic with a void pointer?
    [4] 11.14 — Void pointers

  • 相关阅读:
    基于 JMeter API 开发性能测试平台
    私藏!资深数据专家SQL效率优化技巧 ⛵
    行车记录仪
    LeetCode241. 为运算表达式设计优先级 - 分治法
    计算机网络:网络层 - IP数据报的转发
    忘记 iPhone 密码:如果忘记密码,如何解锁 iPhone
    AI绘图之Midjourney初体验
    [工业互联-4]:工业有线互联总线之IO-Link
    Java并发 JUC工具类:Semaphore详解
    交换机和路由器技术-28-OSPF的NSSA区域
  • 原文地址:https://blog.csdn.net/MMTS_yang/article/details/125528660