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);
}
网友评论