void、void*以及NULL
写在前面
在使用C++的过程中,void和NULL用到的频率挺高的,但是从来没有去探索过这两个关键字的联系和区别,也没有对它们做更多的探索。对于void*,说实话,实际应用中貌似没有用到过这个东西。那这三者到底是什么呢?应该怎么用呢?
void
void是指无类型。我们可以把它理解为“不存在”
我们在写代码的时候,用到void的地方无非两个:
1、函数没有返回值的时候,将函数的返回类型声明为void
如:void f(int a);
在C语言中,如果一个函数没有写返回类型,编译器默认这种函数的返回类型是整型,而不是void.
2、函数没有参数的时候,在参数列表中注明void
如:int getSum(void),当然也可以写成int getSum()。对编译器而言,这两种形式都没有区别,如果在程序中同时声明这两种形式的话,编译器不会视为函数重载,而是会报重复声明的错误。尽管对于上述两种参数类型为空的声明,编译器的处理都相同,但是为了让程序具有良好的可读性,同时也为了满足编程规范的要求,还是加上void为好。
接下来我们试着定义一个void类型的变量:
void a;
编译器会报错:error C2182: “a”: 非法使用“void”类型
也就是说,void是没有类型的,如果定义这样的变量,编译器并不知道应该给这个变量开辟多大的空间,因此只能报错。
void*
说实话,目前为止,除了在写一些内存拷贝、移动相关的样例函数时用过void*,其他时候一次都没用过。(事实上void*的就是在内存复制、内存拷贝这些场景中用到的)void*看起来看起来比较神秘而高冷啊。它到底是个啥呢?
既然void是无类型,那么很自然的推理出,void*就是无类型指针。
在程序中定义一个void*的变量:
void *p;
程序可以通过编译。我们可以输出sizeof(p),其结果为4.这很容易理解,因为p是一个指针,只是它指向的对象的类型是未知的,在win32中,指针占4个字节,因此sizeof(p) = 4.我们可以打印出p的地址,我们甚至可以直接打印出p指向的对象的地址,尽管此时并不知道p指向的地址里面放的是什么:
cout<<"sizeof p = "<<sizeof(p)<<endl;
cout<<"&p = "<<&p<<endl;
cout<<"p = "<<p<<endl;//这句可能会在执行时出现异常,这是可以使用Release模式
void*是无类型的,也就是说,它可以是任意类型的,我们可以把任意类型的指针赋给void类型的指针。
下面这段代码声明了一个double类型的指针pb,然后将pb赋给p。
void *p;
double b = 0.2;
double *pb = &b;
cout<<"Befor initialization"<<endl;
cout<<"p = "<<p<<endl;
cout<<"pb = "<<pb<<endl;
cout<<"After initialization"<<endl;
p = pb;
cout<<"p = "<<p<<endl;
反过来,如果把无类型指针p赋给double型指针pb,
pb = p;
则无法通过编译,报错:无法从“void *”转换为“double *”,如果需要进行这类的转换,必须进行强制类型转换:
pb = (double *)p;
此外,对于有明确类型的指针,比如int *,我们可以直接操作指针,对指针做++、+=操作,如下:
int a = 2;
int *pa = &a;
cout<<"pa = "<<pa<<endl;
pa++;
cout<<"After pa++"<<endl;
cout<<"pa = "<<pa<<endl;
可以看到,指针移动了4个字节。如果是double类型的指针,则做++操作后移动的是8个字节。字符型指针比较奇特,直接看代码和结果:
char *pa = "abc";
cout<<"pa = "<<pa<<endl;
pa++;
cout<<"After pa++"<<endl;
cout<<"pa = "<<pa<<endl;
输出的是指向的内容。(这里不再继续扩展了,感觉有要跑偏了。。),我想说的是,对于void * ,不能进行++ 操作。很明显,对int*, dobule* 做++操作时,指针移动的大小是其指向对象的大小,而我们已经知道void*指向的对象是未知的,因此无法进行指针的下移,如果对void*做++操作,会报错: error C2036: “void *”: 未知的大小
NULL
NULL字面意思是“空”,也就是啥都没有,它通常表示空值,无结果,或是空集合,其ASCII码是0(十进制),我们可以在程序中输出NULL的值,如下:
cout<<"NULL = "<<NULL<<endl;
我们可以直接转到NULL的声明,然后stdio.h就会被打开,同时鼠标将被聚焦到下图中的代码上。
由此可以看到,NULL实际上是一个宏定义,在C++中,它被替换成0,而在C中,它被替换成一个无类型指针,且值为0.
因此我们把NULL赋给任意类型的指针,如下:
int t = 9;
int *a = &t;
cout<<"Before a = NULL"<<endl;
cout<<"a = "<<a<<endl;
a = NULL;
cout<<"After a = NULL"<<endl;
cout<<"a = "<<a<<endl;
在这段代码中,我们把NULL赋给了int型的指针a,这样a指向的对象就变成了0,这中做法称为指针的悬空。这时候a已经不指向向任何有效存储区,在指针初始化和指针delete之后,都会这么做。
总结
越是探索C++中的一些细节,就越是觉得自己学得很粗浅。。。但近来能够静下心来慢慢看看这些细微的东西,感觉还是挺受益的,无论以后使用什么语言进行编程,都能够静下心来,多多思考,坚持下去,总是好的。