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;

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

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

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

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

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

类似地,

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

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

void* ptr;
int *i = (int *) ptr;
int *j = (int *) malloc(sizeof(int) * 5);

不知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;
}

运行结果

5
7.5
Mollie

void* 不能直接解引用

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

下述代码无法通过编译

#include <iostream>
using namespace std;
 
int main(){
    int a = 10;
    void* ptr = &a;
    cout << *ptr;
    return 0;
}

编译报错如下:

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

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

#include <iostream>
using namespace std;
 
int main(){
    int a = 10;
    void* ptr = &a;
    int *ptr1 = static_cast<int*>(ptr);
    cout << *ptr1;
    return 0;
}

运行结果如下:

10

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)

c++中说明

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

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

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

#define NULL 0
// C++11 起
#define NULL nullptr

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

nullptr

考虑下面这两个foo函数:

void foo(char*);
void foo(int);

那么 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));       // 错误:非字面量的零不能为空指针常量
}

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 是整数空指针常量则为歧义
                // (如在大部分实现中的情况)
}

拥抱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;
}

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

template<>
int Call<int (*)(void *), nullptr_t>(int (*func)(void *), nullptr_t ptr)
{
  return func(ptr);
}

参考文献

[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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

-西门吹雪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值