- 回看旧识
- 空指针
- `void*`
- 在`c++`中必须显式地将`malloc`的返回值类型转换为`(int *)`。
- 不知`void *`所指,如何强转
- `void*` 不能直接解引用
- ` C`标准不允许使用空指针进行指针算术运算。
- void *与空指针(null pointer)区别?
- `NULL`
- `C`中说明
- `c++`中说明
- `nullptr`
- `std::nullptr_t`
- 拥抱`nullptr`
- 避免重载决议
- 让模板更清晰
- 参考文献
回看旧识 空指针
- 优先使用 nullptr,而不是 0 和 NULL
- 避免在整数和指针类型上重载
空指针(null pointer)不指向任何对象,在试图使用一个指针之前代码可以首先检查它是否为空,以下是几个生成空指针的方法。
int *p1 = nullptr;//等价于int *p1=0; int *p2 = 0; //直接将p2初始化为字面常量0 //需要首先#includevoid*int *p3 = NULL;//等价于int *p3 = 0;
- void *也称为泛型指针,是一种特殊的指针类型,可用于存放任意对象的地址即可以指向任何数据类型的对象。
- void *是没有关联数据类型的指针。 void *可以保存任何类型的地址,并且可以转换为任何类型。
- malloc() 和calloc() 返回void * 类型,并且这允许使用这些函数来分配任何数据类型的内存(仅仅因为void*)。
- 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#include enum class Type{ tInt, // 注意:我们不能在这里使用“int”,因为它是一个关键字,所以我们将使用“tInt” tFloat, tCString }; void printValue(void* ptr, Type type){ switch (type){ case Type::tInt: std::cout << *static_cast (ptr) << 'n'; break; case Type::tFloat: std::cout << *static_cast (ptr) << 'n'; break; case Type::tCString: std::cout << static_cast (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 Mollievoid* 不能直接解引用
因为void指针不知道它所指向的对象类型,所以解除对void指针的引用是非法的。相反,在执行解引用之前,必须首先将void指针转换为另一种指针类型。
下述代码无法通过编译
#includeusing namespace std; int main(){ int a = 10; void* ptr = &a; cout << *ptr; return 0; }
编译报错如下:
source>: In function 'int main()'::8:13: error: 'void*' is not a pointer-to-object type 8 | cout << *ptr; | ^~~~
如欲取址修改如下,此项必须确保转换后所得的类型就是指针所指的类型,类型一旦不符,将产生未定义的后果。
#includeusing namespace std; int main(){ int a = 10; void* ptr = &a; int *ptr1 = static_cast (ptr); cout << *ptr1; return 0; }
运行结果如下:
10C标准不允许使用空指针进行指针算术运算。
不可能对void指针进行指针运算。 这是因为指针运算需要指针知道它所指向的对象大小,因此它可以适当地递增或递减指针。
在GNU C中,考虑到void的大小为1是允许的。
void *与空指针(null pointer)区别?
- 在GNU C中,对指向void的指针和指向函数的指针支持加减操作。这是通过将void或函数的大小视为1来实现的。
- 这样做的结果是,在void和函数类型上也允许sizeof,并返回1。
- 选项-Wpointer-arith会在使用这些扩展时发出警告。
NULL C中说明void指针可以指向任何类型的对象,但不知道它指向的是什么类型的对象。void指针必须显式转换为另一种类型的指针才能执行间接转换。空指针是指不指向地址的指针。void指针可以是空指针。因此,void指针指的是指针的类型,而null pointer指的是指针的值(地址)。
宏 NULL 是实现定义的空指针常量,可为
- 值为 0 的整数常量表达式
- 转型为 void* 的值为 0 的整数常量表达式
空指针常量能转换为任何类型;转换结果是该类型的空指针值。
可能的实现
// 兼容 C++ : #define NULL 0 // 不兼容 C++ : #define NULL (10*2 - 20) #define NULL ((void*)0)c++中说明
宏 NULL 是实现定义的空指针常量,可为
- 求值为零的整数类型字面常量表达式右值 (C++11 前)
- 零值整数字面量,或为 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。
#includestd::nullptr_t#include template 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)); // 错误:非字面量的零不能为空指针常量 }
typedef decltype(nullptr) nullptr_t;(C++11 起)
std::nullptr_t 是空指针字面量 nullptr 的类型。它是既非指针类型亦非指向成员指针类型的独立类型。
#include拥抱nullptr 避免重载决议#include void f(int*){ std::cout << "Pointer to integer overloadn"; } void f(double*){ std::cout << "Pointer to double overloadn"; } void f(std::nullptr_t){ std::cout << "null pointer overloadn"; } int main(){ int* pi {}; double* pd {}; f(pi); f(pd); f(nullptr); // 无 void f(nullptr_t) 可能有歧义 // f(0); // 歧义调用:三个函数全部为候选 // f(NULL); // 若 NULL 是整数空指针常量则为歧义 // (如在大部分实现中的情况) }
文中开头第2条,不要在指针和整形之间做重载,对c++那个版本都是适用的准则。这样可以规避重载决议,但nullptr会是更好的选择。
示例见第一章的nullptr小节
示例如下:
#includeint func(void *ptr){ return 0; } template 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 (*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


![Effective Modern C++[实践]->优先使用nullptr,而非0或NULL Effective Modern C++[实践]->优先使用nullptr,而非0或NULL](http://www.mshxw.com/aiimages/31/1001994.png)
