C/C++语言void及void指针深层摸索
副标题#e#
1.概述
很多初学者对C/C++语言中的void及void指针范例不甚领略,因此在利用上呈现了一些错误。本文将对void要害字的深刻寄义举办讲解,并详述void及void指针范例的利用要领与能力。
2.void的寄义
void的字面意思是“无范例”,void *则为“无范例指针”,void *可以指向任何范例的数据。
void险些只有“注释”和限制措施的浸染,因为从来没有人会界说一个void变量,让我们试着来界说:
void a;
这行语句编译时会堕落,提示“illegal use of type ‘void‘”。不外,纵然void a的编译不会堕落,它也没有任何实际意义。
void真正发挥的浸染在于:
(1) 对函数返回的限定;
(2) 对函数参数的限定。
我们将在第三节对以上二点举办详细说明。
众所周知,假如指针p1和p2的范例沟通,那么我们可以直接在p1和p2间相互赋值;假如p1和p2指向差异的数据范例,则必需利用强制范例转换运算符把赋值运算符右边的指针范例转换为左边指针的范例。
譬喻:
float *p1;
int *p2;
p1 = p2;
个中p1 = p2语句会编译堕落,提示“‘=‘ : cannot convert from ‘int *‘ to ‘float *‘”,必需改为:
p1 = (float *)p2;
而void *则差异,任何范例的指针都可以直接赋值给它,无需举办强制范例转换:
void *p1;
int *p2;
p1 = p2;
但这并不料味着,void *也可以无需强制范例转换地赋给其它范例的指针。因为“无范例”可以海涵“有范例”,而“有范例”则不能海涵“无范例”。原理很简朴,我们可以说“汉子和姑娘都是人”,但不能说“人是汉子”可能“人是姑娘”。下面的语句编译堕落:
void *p1;
int *p2;
p2 = p1;
提示“‘=‘ : cannot convert from ‘void *‘ to ‘int *‘”。
#p#副标题#e#
3.void的利用
下面给出void要害字的利用法则:
法则一 假如函数没有返回值,那么应声明为void范例
在C语言中,凡不加返回值范例限定的函数,就会被编译器作为返回整型值处理惩罚。可是很多措施员却误觉得其为void范例。譬喻:
add ( int a, int b )
{
return a + b;
}
int main(int argc, char* argv[])
{
printf ( "2 + 3 = %d", add ( 2, 3) );
}
措施运行的功效为输出:
2 + 3 = 5
这说明不加返回值说明的函数简直为int函数。
林锐博士《高质量C/C++编程》中提到:“C++语言有很严格的范例安详查抄,不答允上述环境(指函数不加范例声明)产生”。但是编译器并不必然这么认定,譬如在Visual C++6.0中上述add函数的编译无错也无告诫且运行正确,所以不能寄但愿于编译器会做严格的范例查抄。
因此,为了制止杂乱,我们在编写C/C++措施时,对付任何函数都必需一个不漏地指定其范例。假如函数没有返回值,必然要声明为void范例。这既是措施精采可读性的需要,也是编程类型性的要求。别的,加上void范例声明后,也可以发挥代码的“自注释”浸染。代码的“自注释”即代码能本身注释本身。
法则二 假如函数无参数,那么应声明其参数为void
在C++语言中声明一个这样的函数:
int function(void)
{
return 1;
}
则举办下面的挪用是不正当的:
function(2);
因为在C++中,函数参数为void的意思是这个函数不接管任何参数。
我们在Turbo C 2.0中编译:
#include "stdio.h"
fun()
{
return 1;
}
main()
{
printf("%d",fun(2));
getchar();
}
编译正确且输出1,这说明,在C语言中,可以给无参数的函数传送任意范例的参数,可是在C++编译器中编译同样的代码则会堕落。在C++中,不能向无参数的函数传送任何参数,堕落提示“‘fun‘ : function does not take 1 parameters”。
所以,无论在C照旧C++中,若函数不接管任何参数,必然要指明参数为void。
法则三 小心利用void指针范例
凭据ANSI(American National Standards Institute)尺度,不能对void指针举办算法操纵,即下列操纵都是不正当的:
void * pvoid;
pvoid++; //ANSI:错误
pvoid += 1; //ANSI:错误
//ANSI尺度之所以这样认定,是因为它僵持:举办算法操纵的指针必需是确定知道其指向数据范例巨细的。
//譬喻:
int *pint;
pint++; //ANSI:正确
pint++的功效是使其增大sizeof(int)。
可是台甫鼎鼎的GNU(GNU‘s Not Unix的缩写)则不这么认定,它指定void *的算法操纵与char *一致。
因此下列语句在GNU编译器中皆正确:
pvoid++; //GNU:正确
pvoid += 1; //GNU:正确
pvoid++的执行功效是其增大了1。
在实际的措施设计中,为迎合ANSI尺度,并提高措施的可移植性,我们可以这样编写实现同样成果的代码:
void * pvoid;
(char *)pvoid++; //ANSI:正确;GNU:正确
(char *)pvoid += 1; //ANSI:错误;GNU:正确
GNU和ANSI尚有一些区别,总体而言,GNU较ANSI更“开放”,提供了对更多语法的支持。可是我们在真实设计时,照旧应该尽大概地迎合ANSI尺度。
法则四 假如函数的参数可以是任意范例指针,那么应声明其参数为void *
典范的如内存操纵函数memcpy和memset的函数原型别离为:
void * memcpy(void *dest, const void *src, size_t len);
void * memset ( void * buffer, int c, size_t num );
#p#分页标题#e#
这样,任何范例的指针都可以传入memcpy和memset中,这也真实地浮现了内存操纵函数的意义,因为它操纵的工具仅仅是一片内存,而岂论这片内存是什么范例。假如memcpy和memset的参数范例不是void *,而是char *,那才叫真的奇怪了!这样的memcpy和memset明明不是一个“纯粹的,离开初级趣味的”函数!
下面的代码执行正确:
//示例: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
有趣的是,memcpy和memset函数返回的也是void *范例,尺度库函数的编写者是何等地富有学问啊!
法则五 void不能代表一个真实的变量
下面代码都诡计让void代表一个真实的变量,因此都是错误的代码:
void a; //错误
function(void a); //错误
void浮现了一种抽象,这个世界上的变量都是“有范例”的,譬如一小我私家不是汉子就是姑娘(尚有人妖?)。
void的呈现只是为了一种抽象的需要,假如你正确地领略了面向工具中“抽象基类”的观念,也很容易领略void数据范例。正如不能给抽象基类界说一个实例,我们也不能界说一个void(让我们类比的称void为“抽象数据范例”)变量。
4.总结
小小的void储藏着很富厚的设计哲学,作为一名措施设计人员,对问题举办深一个条理的思考一定使我们受益匪浅。