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 ---简称:cpp c++ 文件
#define NULL 0
#else
#define NULL ((void *)0)
#endif

C++的0

因为C++中不能将void *类型的指针隐式转换成其他指针类型,而又为了解决空指针的问题,所以C++中引入0来表示空指针(注: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, );

好的,这些代码都是正常完美的编译运行。但是突然在某个时候我们功能扩展,需要对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 *>());  --- 我们的游戏项目就遇到这个问题,这样用开起来别扭

我知道,如果我们一开始就有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 ; } template<class C, class T>
inline operator T C::*() const
{ return ; } private:
void operator&() const;
} nullptr = {};

虽然这个东西被大家讨论过很多次了,但是我觉得还是有必要再讨论一下,毕竟在C++ 11还没有普及之前,我们还是应该知道怎么去避免问题,怎么很快的找到问题。

------  结论:  如果使用 nullptr 初始化对象,就能避免 0 指针的二义性的问题。  cocos2d-x3.0 采用 c++11的新特性了... c++ 二春来了...

史上最明白的 NULL、0、nullptr 区别分析(老师讲N篇都没讲明白的东东),今天终于明白了,如果和我一样以前不明白的可以好好的看看...的更多相关文章

  1. 【Tips】史上最全H1B问题合辑——保持H1B身份终级篇

    [Tips]史上最全H1B问题合辑——保持H1B身份终级篇 2015-04-10留学小助手留学小助手 留学小助手 微信号 liuxue_xiaozhushou 功能介绍 提供最真实全面的留学干货,帮您 ...

  2. 聊一聊c++中指针为空的三种写法 ----->NULL, 0, nullptr

    看到同事用了一下nullptr.不是很了解这方面东东,找个帖子学习学习 http://www.cppblog.com/airtrack/archive/2012/09/16/190828.aspx N ...

  3. .Net魔法堂:史上最全的ActiveX开发教程——ActiveX与JS间交互篇

    一.前言 经过上几篇的学习,现在我们已经掌握了ActiveX的整个开发过程,但要发挥ActiveX的真正威力,必须依靠JS.下面一起来学习吧! 二.JS调用ActiveX方法 只需在UserContr ...

  4. 史上最简单的的HashTable源码分析

    HashTable源码分析 1.前言 Hashtable 一个元老级的集合类,早在 JDK 1.0 就诞生了 1.1.摘要 在集合系列的第一章,咱们了解到,Map 的实现类有 HashMap.Link ...

  5. 怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版,每个 Java 初学者都应该收藏

    这么说吧,在我眼里,Java 就是最流行的编程语言,没有之一(PHP 往一边站).不仅岗位多,容易找到工作,关键是薪资水平也到位,不学 Java 亏得慌,对吧? 那可能零基础学编程的小伙伴就会头疼了, ...

  6. linux驱动学习(八) i2c驱动架构(史上最全) davinc dm368 i2c驱动分析【转】

    转自:http://blog.csdn.net/ghostyu/article/details/8094049 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[-] 预备知识 lin ...

  7. 史上最难的一道Java面试题 (分析篇)

    博客园 匠心零度 转载请注明原创出处,谢谢! 无意中了解到如下题目,觉得蛮好. 题目如下: public class TestSync2 implements Runnable { int b = 1 ...

  8. 【UEFI】---史上最全的X86平台启动流程分析(软硬结合)

    最近研究了下X86处理器的启动流程分析,相比于常见的ARM来说,X86平台启动流程真的复杂了很多,本次基于项目实际的两个问题入手,研究了包括以下几个部分的内容: 1. 从硬件角度看启动流程 2. 从软 ...

  9. .Net魔法堂:史上最全的ActiveX开发教程——自动更新、卸载篇

    一.前言 B/S模式的特点之一,客户端版本升级相对简单.快捷,适合产品的快速迭代.而ActiveX组件的自动更新同样也继承了这一优点.下面我们一起来了解吧! 二.二话不说更新ActiveX 1. 设置 ...

随机推荐

  1. FM算法解析及Python实现

    1. 什么是FM? FM即Factor Machine,因子分解机. 2. 为什么需要FM? 1.特征组合是许多机器学习建模过程中遇到的问题,如果对特征直接建模,很有可能会忽略掉特征与特征之间的关联信 ...

  2. Python之使用转义序列 \n 和 \t 跟 expandtabs 函数输出表格

    示例: text = "username\temail\tpassword\nashdfh\tfiodfh@q.com\ty567\nsdfiuh\tadfhisoj@163.com\t42 ...

  3. docker(一) Centos7下安装docker

    docker(一) Centos7下安装dockerdocker(二) windows10下安装dockerdocker(三) 镜像和容器常用命令 docker(四) 使用Dockerfile构建镜像 ...

  4. Django rest framework(6)----序列化(2)

    为什么要序列化 django 查询数据库返回的类型是  queryset 类型 而我们和前端通信使用的大多数是json类型,这个时候我们需要把 queryset的数据类型转换成python的数据类型然 ...

  5. [模板] 笛卡尔树 && RMQ

    话说我noip之前为什么要学这种东西... 简介 笛卡尔树(Cartesian Tree) 是一种二叉树, 且同时具有以下两种性质: 父亲节点的值大于/小于子节点的值; 中序遍历的结果为原序列. 笛卡 ...

  6. 关于Qt的StyleSheet作用范围

    Qt的StyleSheet是很方便的一个设置各种控件风格形态的属性,但是默认的StyleSheet会作用于所有的子控件,容易带来麻烦,以下几种情况,可以限制作用范围 以QTextEdit为例,实体名为 ...

  7. stop()在animate中的用法

    stop 是jQuery中用于控制页面动画效果的方法.运行之后立刻结束当前页面上的动画效果.stop在新版jQuery中添加了2个参数:第一个参数的意思是是否清空动画序列,也就是stop的是当前元素的 ...

  8. 【CF1151F】Sonya and Informatics(动态规划,矩阵快速幂)

    [CF1151F]Sonya and Informatics(动态规划,矩阵快速幂) 题面 CF 题解 考虑一个暴力\(dp\).假设有\(m\)个\(0\),\(n-m\)个\(1\).设\(f[i ...

  9. 多版本python及多版本pip使用

    最近做一些网站的发布程序,要用到python3,所以又安装了python3.   www.qlrx.netwww.393662.comwww.qnpx.netwww.393225.com       ...

  10. CF809E Surprise me!(莫比乌斯反演+Dp(乱搞?))

    题目大意: 给你一棵树,树上的点编号为\(1-n\).选两个点\(i.j\),能得到的得分是\(\phi(a_i*a_j)*dis(i,j)\),其中\(dis(i,j)\)表示\(a\)到\(b\) ...