NULL、0、nullptr (C++)

本文探讨了C语言中的NULL、C++中的0及C++11中的nullptr如何表示空指针,并讨论了它们之间的差异及优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

点击打开参考


C的NULL

在C语言中,我们使用NULL表示空指针,也就是我们可以写如下代码: 

int *i = NULL;
foo_t *f = NULL;

 实际上在C语言中,NULL通常被定义为如下: 

#define NULL ((void *)0)

 也就是说NULL实际上是一个void *的指针,然后吧void *指针赋值给int *和foo_t *的指针的时候,隐式转换成相应的类型。而如果换做一个C++编译器来编译的话是要出错的,因为C++是强类型的,void *是不能隐式转换成其他指针类型的,所以通常情况下,编译器提供的头文件会这样定义NULL: 

#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif

 

C++的0

因为C++中不能将void *类型的指针隐式转换成其他指针类型,而又为了解决空指针的问题,所以C++中引入0来表示空指针,这样就有了类似上面的代码来定义NULL。实际上C++的书都会推荐说C++中更习惯使用0来表示空指针而不是NULL,尽管NULL在C++编译器下就是0。为什么C++的书都推荐使用0而不是NULL来表示空指针呢?我们看一个例子:

在foo.h文件中声明了一个函数: 

void bar(sometype1 a, sometype2 *b);

 这个函数在a.cpp、b.cpp中调用了,分别是:

a.cpp: 


bar(a, b);

 

b.cpp: 


bar(a, 0);

 

好的,这些代码都是正常完美的编译运行。但是突然在某个时候我们功能扩展,需要对bar函数进行扩展,我们使用了重载,现在foo.h的声明如下: 

void bar(sometype1 a, sometype2 *b);
void bar(sometype1 a,  int i);

 这个时候危险了,a.cpp和b.cpp中的调用代码这个时候就不能按照期望的运行了。但是我们很快就会发现b.cpp中的0是整数,也就是在overload resolution的时候,我们知道它调用的是void bar(sometype1 a, int i)这个重载函数,于是我们可以做出如下修改让代码按照期望运行: 

bar(a, static_cast<sometype2 *>(0));

 我知道,如果我们一开始就有bar的这两个重载函数的话,我们会在一开始就想办法避免这个问题(不使用重载)或者我们写出正确的调用代码,然而后面的这个重载函数或许是我们几个月或者很长一段时间后加上的话,那我们出错的可能性就会加大了不少。貌似我们现在说道的这些跟C++通常使用0来表示空指针没什么关系,好吧,假设我们的调用代码是这样的:

foo.h 

void bar(sometype1 a, sometype2 *b);

 a.cpp 


bar(a, b);

 b.cpp 


bar(a, NULL);

 当bar的重载函数在后面加上来了之后,我们会发现出错了,但是出错的时候,我们找到b.cpp中的调用代码也很快可能忽略过去了,因为我们用的是NULL空指针啊,应该是调用的void bar(sometype1 a, sometype2 *b)这个重载函数啊。实际上NULL在C++中就是0,写NULL这个反而会让你没那么警觉,因为NULL不够“明显”,而这里如果是使用0来表示空指针,那就会够“明显”,因为0是空指针,它更是一个整形常量。

在C++中,使用0来做为空指针会比使用NULL来做空指针会让你更加警觉。


C++ 11的nullptr

虽然上面我们说明了0比NULL可以让我们更加警觉,但是我们并没有避免这个问题。这个时候C++ 11的nullptr就很好的解决了这个问题,我们在C++ 11中使用nullptr来表示空指针,这样最早的代码是这样的,

foo.h 

void bar(sometype1 a, sometype2 *b);

 a.cpp 


bar(a, b);

 b.cpp 


bar(a, nullptr);

 在我们后来把bar的重载加上了之后,代码是这样:

foo.h 

void bar(sometype1 a, sometype2 *b);
void bar(sometype1 a,  int i);

 a.cpp 


bar(a, b);

 b.cpp 


bar(a, nullptr);

 这时候,我们的代码还是能够如预期的一样正确运行。

在没有C++ 11的nullptr的时候,我们怎么解决避免这个问题呢?我们可以自己实现一个(《Imperfect C++》上面有一个实现): 

const
class nullptr_t
{
public:
    template< class T>
    inline  operator T*()  const
        {  return 0; }

    template< class C,  class T>
    inline  operator T C::*()  const
        {  return 0; }
 
private:
     void  operator&()  const;
} nullptr = {};
<think>好的,我现在需要回答用户关于C++中NULLnullptr区别的问题。首先,我得回忆一下自己之前学过的相关知识,确保信息的准确性。 首先,我记得在C++中,NULL通常被定义为0或者((void*)0),这可能是从C语言继承过来的。而nullptr是C++11引入的新关键字,专门用来表示空指针。那这两个的区别主要在于类型和重载函数处理上吧? 接下来,我应该详细分析它们的差异。类型方面,NULL作为宏定义,如果编译器将其视为整数0,那么在需要指针类型的上下文中可能会导致类型不匹配的问题。而nullptr的类型是std::nullptr_t,可以隐式转换为任何指针类型,这样在类型安全上更好。 然后想到重载函数的例子,比如有一个函数重载接受int和指针参数。当传入NULL时,可能会调用接受int的版本,而传入nullptr则会正确调用指针版本,这应该是关键区别之一。用户可能需要这样的例子来理解应用场景。 另外,模板推导也是一个点。当使用NULL时,模板参数可能被推导为int,而nullptr会正确推导为指针类型,这会影响模板函数的实例化,可能导致错误或非预期行为。 还需要注意兼容性,C++11之后推荐使用nullptr,而NULL在旧代码中常见。同时,在C语言中,nullptr不存在,所以跨语言项目需要注意这一点。 然后要组织这些点,用清晰的结构表达出来。可能分成几个部分:定义与历史背景、类型差异、重载函数影响、模板推导、代码示例、总结建议。这样用户能逐步理解。 需要确保例子正确,比如写一个函数重载的例子,展示NULLnullptr的不同行为。同时,在模板例子中,使用decltype或者类型推导的结果来展示两者的差异。 还要注意术语的准确性,比如NULL是宏,而nullptr是关键字,类型是std::nullptr_t,这些都要正确无误地表达。 最后总结时,强调nullptr在类型安全和现代C++中的优势,建议在新项目中使用nullptr替代NULL,同时注意兼容性情况。 可能还需要检查是否有其他区别,比如在异常抛出或类型转换时的表现,但暂时想到的主要是类型和重载相关的问题。确保覆盖用户可能关心的主要方面,不遗漏重要点。</think>C++中`NULL`和`nullptr`的区别可以从以下方面逐步理解: --- ### 一、定义与历史背景 1. **`NULL`** - 传统C/C++宏定义,通常被实现为整数`0`或指针形式的`((void*)0)` - 例如:`#define NULL 0` 或 `#define NULL ((void*)0)` - 因历史原因,**在指针和整型间存在二义性** 2. **`nullptr`** - C++11引入的关键字,**明确表示空指针常量** - 类型为`std::nullptr_t`,可隐式转换为任意指针类型 --- ### 二、类型差异(关键区别) - **`NULL`的类型** ```cpp int* p = NULL; // 可能被推导为 int* p = 0; void func(int); // 可能意外匹配整型重载版本 ``` - **`nullptr`的类型** ```cpp int* p = nullptr; // 明确空指针,类型为 std::nullptr_t void func(int*); // 精确匹配指针重载版本 ``` --- ### 三、重载函数的歧义问题 **示例代码**: ```cpp void func(int) { cout << "int版本" << endl; } void func(char*) { cout << "指针版本" << endl; } func(NULL); // 输出 "int版本"(若NULL=0) func(nullptr); // 输出 "指针版本" ``` - **`NULL`可能错误匹配整型重载** - **`nullptr`始终匹配指针重载** --- ### 四、模板推导差异 **示例代码**: ```cpp template<typename T> void check_type(T t) {} check_type(NULL); // T被推导为 int(若NULL=0) check_type(nullptr); // T被推导为 std::nullptr_t ``` - **`NULL`可能导致模板参数推导错误** - **`nullptr`保留指针类型信息** --- ### 五、代码安全性与现代C++实践 1. **避免隐式类型转换** ```cpp char* ch = NULL; // 可能编译警告(整型转指针) char* ch = nullptr; // 安全,无警告 ``` 2. **与`auto`配合更清晰** ```cpp auto p1 = NULL; // p1类型为 int(若NULL=0) auto p2 = nullptr; // p2类型为 std::nullptr_t ``` --- ### 六、总结与建议 | 特性 | `NULL` | `nullptr` | |--------------|-------------------------|-------------------------| | **类型** | 整型或`void*` | `std::nullptr_t` | | **重载匹配** | 可能误匹配整型 | 精确匹配指针 | | **模板推导** | 可能推导为整型 | 保留指针类型信息 | | **C++版本** | C++03及之前 | C++11起 | **建议**: - 在新代码中**始终使用`nullptr`** - 旧代码迁移时逐步替换`NULL` - 需注意:C语言不支持`nullptr`(C++特有)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值