void、void*以及NULL
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++中的一些细节,就越是觉得自己学得很粗浅。。。但近来能够静下心来慢慢看看这些细微的东西,感觉还是挺受益的,无论以后使用什么语言进行编程,都能够静下心来,多多思考,坚持下去,总是好的。
void、void*以及NULL的更多相关文章
- void指针和NULL指针
Void指针和NULL指针 Void指针: Void指针我们称之为通用指针,就是可以指向任意类型的数据.也就是说,任何类型的指针都可以赋值给Void指针. 举例: #include<stdio. ...
- invalid conversion from 'void* (*)()' to 'void* (*)(void*)'
void *thread1() ], NULL, thread1, NULL)) != ) 提示:invalid conversion from 'void* (*)()' to 'void* (*) ...
- 深刻理解void,void*和sizeof关键字
void的字面值是“无类型”,void*则是"无类型指针".void*可以指向任何类型的数据.void几乎只有"注释"和限制程序的作用,因为从来没有人会定义一个 ...
- 通过qsort(void * lineptr[], int left, int rifht, int (*comp)(void *, void *))解读指针函数和void指针
原函数是<The C programint language >5.11文本行排序的程序,如下: void qsort(void *v[], int left, int right, i ...
- 简述static关键字、void与void *(void指针)、函数指针
static关键字1.修饰局部变量,延长局部变量的生命周期.使变量成为静态局部变量,在编译时就为变量分配内存,直到程序退出才释放存储单元.2.修饰全局变量,限制全局变量的使用范围为本文件中.全局变量默 ...
- js & void() & void(0)
js & void() & void(0) https://www.runoob.com/js/js-void.html void() <a href="javascr ...
- void指针、NULL指针和未初始化指针
一个指针可以被声明为void类型,比如void *x.一个指针可以被赋值为NULL.一个指针变量声明之后但没有被赋值,叫做未初始化指针. 1 2 3 4 5 6 7 8 9 10 11 12 13 1 ...
- void void*
void类型及void指针 1.概述 许多初学者对C/C 语言中的void及void指针类型不甚理解,因此在使用上出现了一些错误.本文将对void关键字的深刻含义进行解说,并 详述void及void指 ...
- void与NULL详解
void 是 “空”类型(无值型),意思是这种类型的大小无法确定. 并不存在void类型的对象,所以也就不能声明void类型的对象或者将sizeof()运算符用于void类型,C++/C语言不能对一个 ...
随机推荐
- avi 格式详解
http://blog.csdn.net/becomly/article/details/6283004 http://blog.csdn.net/easecom/article/details/45 ...
- java io流缓冲理解
bufferedinputstream和bufferedoutputstream:这两个类是在inputstream和outputstream的基础上增加了一个buffer的缓冲区,从而使数据不直接写 ...
- spring的两种属性注入方式setter注入和构造器注入或者自动注入
1.这里的属性自动注入,与注解配置bean是两回事.这里的自动注入,指的是bean属性的自动注入. bean属性自动注入,包括byNAme和ByType两码事. 2.所有的applicationCon ...
- [Unity菜鸟] FBX模型动画提取
角色已经人形化(Humanoid)了,那它的动画可以用在其它的模型上了也就是可以共用一套模型动画了,但是你有没有发现那动画是和fbx模型绑在一起的,没关系你可以选中这几个动画文件按Contrl+D就可 ...
- java获取当前操作系统的信息
java获取当前操作系统的信息 JavaOS虚拟机UnixEXT 从网上收集的一些关于java获取操作系统信息的方法,现在总结一下: 1获取本机的IP地址: private static Strin ...
- swift:高级运算符(位运算符、溢出运算符、优先级和结合性、运算符重载函数)
swift:高级运算符 http://www.cocoachina.com/ios/20140612/8794.html 除了基本操作符中所讲的运算符,Swift还有许多复杂的高级运算符,包括了C语和 ...
- 写出优秀论文How To Write A Great Essay About Anything
There is an assumption in the world that an essay is something literary you write for school about a ...
- c# 使用 静态类+xml序列化 保存配置文件
namespace TVCorrectionDataProcess{ [XmlRoot(ElementName = "Config")] public class Co ...
- 数组 寻找最大的第k个数
int Partion(int asy[],int begin,int end) { int k=begin; int key=asy[end]; for(int i=begin;i<=end; ...
- 【HDOJ】4801 Pocket Cube 的几种解法和优化
1. 题目描述给定一个$2 \times 2 \times 2$的魔方,当某个面上的4个小块颜色均相同时,称这个面为complete.求对这个魔方进行$n \in [1,7]$次旋转(沿某个面顺时针或 ...