NULL:
NULL是c语言的东西,定义处: #define NULL ((void *)0)
我们可以写 int* i = NULL, foo_t* pObj = NULL.
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来表示空指针呢?我们看一个例子:
1 //foo.h
2 void bar(type1 a, type2* b);
3
4 //foo.h中的bar函数在a.cpp和b.cpp中都被调用了.
5
6 //a.cpp
7 .....
8 bar(a,b);
9 .....
10
11 //b.cpp
12 .....
13 bar(a, 0);
14 .....
15
16 //现在,上面的代码都能编译运行.但是突然某天我们要功能扩展,需要对bar函数扩展,我们使用了重载 foo.h变成如下:
17 void bar(sometype1 a, sometype2 *b);
18 void bar(sometype1 a, int i);
19
20 //这个时候就危险了.因为a.cpp和b.cpp中的调用代码这个时候就不能按照期望的运行了。但是我们很快就会发现b.cpp中的0是整数,也就是在overload resolution的时候,我们知道它调用的是void bar(sometype1 a, int i)这个重载函数,于是我们可以做出如下修改让代码按照期望运行:
21
22 bar(a, static_cast<type2 *>(0));
23
24 //我知道,如果我们一开始就有bar的这两个重载函数的话,我们会在一开始就想办法避免这个问题(不使用重载)或者我们写出正确的调用代码,然而后面的这个重载函数或许是我们几个月或者很长一段时间后加上的话,那我们出错的可能性就会加大了不少。貌似我们现在说道的这些跟C++通常使用0来表示空指针没什么关系,好吧,假设我们的调用代码是这样的:
25 //foo.h
26 void bar(type1 a, type2 *b);
27
28 //a.cpp
29 ......
30 bar(a, b);
31 ......
32 //b.cpp
33 .....
34 bar(a,NULL);
35 .....
36
37 //当bar的重载函数在后面加上来了之后,我们会发现出错了,但是出错的时候,我们找到b.cpp中的调用代码也很快可能忽略过去了,因为我们用的是NULL空指针啊,应该是调用的void bar(type1 a, type2 *b)这个重载函数啊。实际上NULL在C++中就是0,写NULL这个反而会让你没那么警觉,因为NULL不够“明显”,而这里如果是使用0来表示空指针,那就会够“明显”,因为0是空指针,它更是一个整形常量。
38
39 在C++中,使用0来做为空指针会比使用NULL来做空指针会让你更加警觉。
C++ 11的nullptr
1 //虽然上面我们说明了0比NULL可以让我们更加警觉,但是我们并没有避免这个问题。这个时候C++ 11的nullptr就很好的解决了这个问题,我们在C++ 11中使用nullptr来表示空指针,这样最早的代码是这样的
2
3 //foo.h
4
5 void bar(type1 a, type2* b);
6 -------------------------------------------
7 | //a.cpp | //b.cpp |
8 | .... | ....... |
9 | bar(a,b); | bar(a, nullptr);|
10 | ....... | ........ |
11 ---------------------------------------------
12 在我们后来把bar的重载加上了之后,代码是这样:
13
14 //foo.h
15 void bar(type1 a, type2 *b);
16 void bar(type1 a, int i);
17
18 //a.cpp //b.cpp
19 .... .....
20 bar(a,b); bar(a,nullptr);
21 .... ......
22
23 //这时候,我们的代码还是能够如期的正确运行.
24
25 //在没有C++11的nullptr的时候,我们应该怎么解决避免这个问题呢?我们可以自己实现一个nullptr
26
27 const
28 class nullptr_t
29 {
30 public:
31 template<class T>
32 inline operator T*() const
33 { return 0; }
34
35 template<class C, class T>
36 inline operator T C::*() const
37 { return 0; }
38
39 private:
40 void operator&() const;
41 } nullptr = {};
和小伙伴都惊了个呆了啊...上面自己实现的nullptr_t类完全看不懂的样子,得一句一句的分析分析啊...
1 const //参见下文解释1
2 class nullptr_t
3 {
4 public:
5 template<class T>
6 inline operator T* () const //参见下文解释2
7 {
8 return 0;
9 }
10
11 template<class C, class T>
12 inline operator T C::*() const //参见下文解释3
13 {
14 return 0;
15 }
16
17 private:
18 void operator& () const; //参见下文解释4
19
20 }nullptr = {}; //参见下文解释5
解释1 : 在类前面的const是什么!!
1 //类前面的const 是修饰 类后面定义的对象的.
2
3 const
4 class A
5 {
6 public:
7 int i;
8 }a,b;
9
10 //等价于
11 class A
12 {
13 public:
14 int i;
15 };
16
17 const A a;
18 const A b;
解释2 : 这个模板函数是什么?
1 template <class T>
2 inline operator T* () const
3 {
4 return 0;
5 }
6
7 //上面的模板函数是在重载 类型转换运算符 (跟 重载 operator = 一样都是在重载运算符).
8 //我们知道重载运算符是要带返回值的,例如下面的类重载等号 是要返回A&的
9 class A
10 {
11 public:
12 A& operater = (const A& Obj)
13 {
14 i = Obj.i;
15 j = Obj.j;
16 }
17 private:
18 int i;
19 int j;
20 };
21 //但是呢.重载 类型转换运算符比较特殊. (c++ primer,可以查“用户自定义类型转换符”找到讲解) 规定为:转换函数不能写返回类型(规定的),返回的类型就是 operator 后面跟的类型
22 所以:
23 inline operator T* () const
24 ---------------------------------------------
25 这是一个类型转换函数,把A类型(这里是return 0 的 0的类型)转换成 T*的类型
26 inline 表示内联函数,写不写无所谓。
27 operator 代表重载某种操作 operator T* () 就是重载类型转换
28 const 表示 成员变量是只读,不能改。
29
30 return _data; // 有返回值,返回值类型是operator后面的T*类型。
解释3 : 这个模板函数又是什么???
解释4: 只写函数声明,不写函数实现真的可以么??
事实证明是可以的,只有没人调用这个函数是可以编译运行成功的..但是如果有人调用的话编译就会报错了.
解释5: 类对象 = {} 中的 ={}是什么??
= {} 是c++11的语法,代表给这个变量初始化