void关键字
我们先从void谈起。
在C++中,void字面意思为无类型,即为不确定类型——类型不确定从而所占内存不确定。跟auto又不一样,像void a= 10;
的定义是错误的,即void类型不能用来定义值。这行语句编译时会出错,提示“illegal use of type 'void'”。不过,即使void a的编译不会出错,它也没有任何实际意义。
void真正的作用在于:
- 对函数参数的限定。
函数的参数填void,表示函数不需要参数——当然现在没什么人会这么写了(直接空着就等于void):int func(void);
。 - 对函数返回的限定——不能有任何返回值——这就是我们常写的void作返回值的函数。
void func(int a,int b) { //函数体代码 return; }
另外void*可以表示接受任意类型的指针;
注意:
- 如果函数没有返回值,那么应声明为void类型;
在C语言中,凡不加返回值类型限定的函数,就会被编译器作为返回整型值处理。但是许多程序员却误以为是void类型。因此为了避免混乱,我们在编写C/C++时,任何函数都应该指定其类型。如果函数没有返回值,也一定要声明为void关键字。
- 如果函数无参数,那么应声明其参数为void;
- 如果函数的参数可以是任意类型指针,那么应声明其参数为void * ;
- void不能代表一个真实的变量;
void*关键词
void*
则不同,编译器会允许你做类似于int someInt = 10; void* par = &someInt;
之类的操作,因为无论指向什么类型的指针,指针所存的地址字节大小是一样的,本身所占空间是一定的。可以认为void*
就是一个可以指向任意类型的指针。在一些时候我们不需要知道void*具体类型,我们直接对内存进行操作。在这点上有点像C#中的泛型,但又不太一样,在需要操作内存时候,void*相比泛型是更为轻量化的实现方法。当然,void*
在使用时也会不可避免的带来许多限制。
如果函数的参数可以是任意类型指针,那么应声明其参数为void *
如典型的内存操作函数memcpy和memset的函数原型分别为:
void * memcpy(void *dest, const void *src, size_t len);
void * memset ( void * buffer, int c, size_t num );
这样,任何类型的指针都可以传入memcpy和memset中,这也真实地体现了内存操作函数的意义,因为它操作的对象仅仅是一片内存,而不论这片内存是什么类型。如果memcpy和memset的参数类型不是void *,而是一个具体的指针类型,比如char *,那明显memcpy和memset就仅仅能对char*类型的指针进行操作。这依赖了具体的类型,就意味着我们函数只用来操作唯一且确定的类型。在上面的内存操作函数中,显然是不可取的,这只会增加冗余的信息。
//示例:memset接受任意类型指针
int intarray[100];
memset ( intarray, 0, 100*sizeof(int) ); //将intarray清0
//示例:memcpy接受任意类型指针
int intarray1[100], intarray2[100];
memcpy ( intarray1, intarray2, 100*sizeof(int) ); //将intarray2拷贝给intarray1
注意:
- 不能对void*指针直接解引用(*p),需要显式转换成其他类型的指针
比如,下面对aa解引用,报错:表达式必须是指向完整对象类型的指针。
所以解引用之前,我们需要声明aa的具体类型,即强转为具体类型的指针,让编译器知道。
cout<<"void*变量 解引用:"<<*(char*)aa<<endl;
- 把void*指针赋值给其他类型的指针需要显式转换对应具体类型的指针
其实这个意思跟第一条并无差别,就是说我们要对一个指针进行具体操作,需要一个完整对象类型的指针。
char *wholePin =(char*)aa;//正确写法
内存危险
众所周知,C++的指针很灵活,但同时它也是一把双刃剑,极考验程序员的经验与精力,如果用的不规范,则容易出现各种问题,严重时甚至会让项目崩盘。在使用指针时,最容易出现的问题就是内存泄漏,造成泄漏的原因主要是程序要在使用完指针之后没有手动释放。或因为一些异常原因,释放没有得到执行!这是危险的!然而在void*中存在一个隐性的概念,若想正确删除掉void*指向的动态类型变量,需要进行强制类型转换,否则不会释放原类型的内存空间!导致内存泄漏。
为什么会出现内存泄漏呢?在delete
的过程中,编译器并不知道该void*
指向的什么类型的变量,自然无法正确调用原类型的析构函数,因而只是调用void*内存空间下的析构函数,简单地清空了void*指针。
delete voidPointer; //只是清空了一个指针
delete (FrameInfo*) voidPointer; //正确析构voidPointer指向的变量。
本章部分内容引用了以下文章:
同时在这里要特别感谢这些作者的见解分享,笔者收获颇多。
C++之void是什么? - keep_smile-resonate - 博客园