C++98标准定义空指针是NULL,NULL是一个Macro常量,定义格式:
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
如此定义的原因是,C语言允许void*隐式转换为任意指针类型,而C++不允许这样 *** 作,但是C++允许任意指针赋值0。
nullptr是C++11新引入的特性,C++98标准NULL的升级版本。虽然nullptr是C++11引入的关键字,但是和98版本不同的是,nullptr存在类型,它的类型是nullptr_t。需要特殊说明的是nullptr_t 是C++11~C++20标准特性,但不是C99 ~C17标准的特性,C语言从C23才开始支持nullptr_t。
nullptrnullptr是一个标准的右值常量。C++11~C++20标准规定,nullptr具有下述特性:
- nullptr可以隐式的方式转换为任何指针类型和任何成员指针类型的空指针。例如:
int * p = nullptr; // nullptr可以赋值给int*
std::string * p2 = nullptr; // nullptr可以赋值给std::string*
std::nullptr_t&& n = nullptr; // nullptr可以赋值给std::nullptr_t右值引用
std::nullptr_t& nn = nullptr; // 编译报错,不允许nullptr赋值给左值引用
- 由于nullptr是一个右值,因此不能对nullptr做取地址运算,例如:
std::nullptr_t* p1 = &nullptr; // 编译报错,不允许对nullptr做取地址运算
- 对指针做空指针判断bool运算,nullptr运算结果为false,例如:
int * p = nullptr; // nullptr可以赋值给int*
if (!p)
{
std::cout << "p空指针运算结果为false..";
}
- nullptr内存大小等同于void*,也就是说sizeof(nullptr) === sizeof(void*),例如:
if (sizeof(nullptr) == sizeof(void*))
{
std::cout << "sizeof(nullptr) == sizeof(void*)\n";
}
- 为向后兼容,C++11依然允许使用NULL表示空指针,因此表达式nullptr == NULL为true,但是nullptr并不是0。也不能隐式转换为整数。
if (nullptr == NULL)
{
std::cout << "nullptr == NULL为true \n";
}
nullptr_t
nullptr_t数据类型存在于头文件
#ifdef __cplusplus
namespace std
{
typedef decltype(nullptr) nullptr_t;
}
using ::std::nullptr_t;
#endif
C++标准允许我们通过nullptr_t定义自己的nullptr,而且任何nullptr_t对象与nullptr,作==运算均为true,作!=运算均为false。例如:
std::nullptr_t n = std::nullptr_t();
if (n == nullptr)
{
std::cout << "n == nullptr is true\n";
}
if (!(n != nullptr))
{
std::cout << "n != nullptr is false\n";
}
(n == nullptr) is true
(n != nullptr) is false
nullptr_t的实现
基于上述nullptr和nullptr_t的讨论分析,笔者总结出nullptr_t的可能实现:
namespace ptr
{
struct nullptr_t
{
void* __lx;
inline constexpr nullptr_t() : __lx(0) {}
inline constexpr nullptr_t(int) : __lx(0) {}
template <class _Tp>
inline constexpr operator _Tp* () const { return 0; }
template <class _Tp, class _Up>
inline constexpr operator _Tp _Up::* () const { return 0; }
nullptr_t* operator&() const = delete;
friend inline constexpr bool operator==(nullptr_t, nullptr_t) { return true; }
friend inline constexpr bool operator!=(nullptr_t, nullptr_t) { return false; }
};
inline constexpr nullptr_t __get_nullptr_t() { return nullptr_t(0); }
#define nullptr __get_nullptr_t()
}
根据nullptr和nullptr_t的特性,可以逐个验证:
int * p = ptr::nullptr; // nullptr可以赋值给int*
ptr::nullptr_t&& n = ptr::nullptr; // nullptr可以赋值给std::nullptr_t右值引用
ptr::nullptr_t* pp = &ptr::nullptr; // 编译报错,“ptr::nullptr_t *ptr::nullptr_t::operator &(void) const”: 尝试引用已删除的函数
ptr::nullptr_t nn = ptr::nullptr_t();
if (nn == ptr::nullptr)
{
std::cout << "(n == nullptr) is true\n";
}
if (!(nn != ptr::nullptr))
{
std::cout << "(n != nullptr) is false\n";
}
if (ptr::nullptr == NULL)
{
std::cout << "nullptr == NULL为true \n";
}
nullptr的好处
解决整数0和空指针混淆
在C++11之前,NULL就是整数0,因此NULL既可表示整数0,也可表示空指针。这有时候会给我带来
一些混淆。例如:
void foo(int) {}
void foo(int*) {}
foo(0); // 调用foo(int)而不是foo(int*)
foo(NULL); // 如果NULL是0,则调用foo(int);
我们期望foo(NULL)调用函数是 foo(int*),但是实际上调用的是foo(int)。而nullptr可以很好的解决此问题。这也是nullptr产生的动机。
捕获空指针空指针nullptr的类型为nullptr_t,我们可以通过nullptr_t类型捕获空指针。例如:
try
{
...
throw nullptr;
}
catch (nullptr_t)
{
...
}
但是,如果throw NULL,那我们用什么类型来catch呢?用int类型来catch?是不是有点别扭。
总结nullptr是一个有类型的右值常量,可很好的解决了整数0和空指针的问题。所以建议大家在开发过程中优先使用nullptr替代NULL。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)