一、void 指针存储任意类型指针的基础
指针是一个特殊的变量,它的特殊之处在于这个变量存放的内容为地址,无论是int型指针、float型指针等等所有的指针类型,只要在同一个系统下,系统都为它分配了同样内存大小的空间(如32位机子中任何一个指针的长度都是4个字节,因为逻辑地址就占用4字节),这也为使用void指针可以存储任意类型的指针打下了基础。
二、void的定义
void体现了一种抽象,这个世界上的变量都是“有类型”的,void的出现只是为了一种抽象的需要,void的字面意思是“无类型”,void *则为“无类型指针”,void *可以指向任何类型的数据,任何类型指针都可以赋值给void指针,但反之则不行。这个类似于抽象类的概念,我们不能够用抽象类来去定义对象,同样的我们可以将void类比为“抽象数据类型,因此也不能定义一个void变量,我们试着来定义一个void变量:
void a;//这行语句编译时会出错,提示“illegal use of type 'void'”。不过,即使void a的编译不会出错,它也没有任何实际意义。
void真正发挥的作用在于:
(1)对函数返回的限定;
(2)对函数参数的限定。
三、void使用规则
1、c和c++对于指针类型转换的要求
(1).C中对指针类型的转换要求很低。但是这将留下一个很大的漏洞
int* a=Null;
void* b=Null;
char* c=Null;
c=a; //warning C4133: “=”: 从“int *”到“char *”的类型不兼容,但是编译能通过
//将void*和其他类型的指针相互赋值不会出现警告,这在C语言中是允许的
b=a;
c=b;
(2).C++中对类型要求很高,不允许不同类型指针之间的相互赋值,void指针例外(任何类型指针都可以赋值给void指针,但反之则不行)
int* a=Null;
void* b=Null;
char* c=Null;
c=a;//出错
b=a;//通过
c=b;//出错
//如果要赋值必须显示转换
int* a=Null;
void* b=Null;
char* c=Null;
c=(char*)a;//通过
b=a; //通过
c=(char*)b;//通过
2、具体使用规则
- 如果函数没有返回值,那么应声明为void类型;
- 如果函数无参数,那么应声明其参数为void;
- 不同标准下对void指针进行的算法操作
- 如果函数的参数可以是任意类型指针,那么应声明其参数为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
林锐博士《高质量C/C++编程》中提到:“C++语言有很严格的类型安全检查,不允许上述情况(指函数不加类型声明)发生”。可是编译器并不一定这么认定,譬如在Visual C++6.0中上述add函数的编译无错也无警告且运行正确,所以不能寄希望于编译器会做严格的类型检查。
因此,为了避免混乱,我们在编写C/C++程序时,对于任何函数都必须一个不漏地指定其类型。如果函数没有返回值,一定要声明为void类型
规则二 如果函数无参数,那么应声明其参数为void
(1)在C++语言中,函数参数为void,则这个函数不接受任何参数,如声明一个这样的函数:
int func(void)
{
return 1;
}
则进行下面的调用是不合法的:
func(2);
(2)在C语言中,可以给无参数的函数传送任意类型的参数
#include <stdio.h>
fun()
{
return 1;
}
int main()
{
printf("%d",fun(2));
}
//编译正确且输出1
在C++编译器中编译同样的代码则会出错。在C++中,不能向无参数的函数传送任何参数,出错提示“‘fun’ : function does not take 1 parameters”。因此无论在C还是C++中,若函数不接受任何参数,一定要指明参数为void,这是写程序的好习惯。
规则三 不同标准下对void指针进行的算法操作
(1)ANSI标准认定:进行算法操作的指针必须是确定知道其指向数据类型大小的,不能对void指针进行算法操作。
//该程序错误
void * pvoid;
pvoid++; //ANSI:错误
pvoid += 1; //ANSI:错误
//该程序正确
int *pint;
pint++; //ANSI:正确
pint++的结果是使其增大sizeof(int)。
(2)GNU认定:void *的算法操作与char *一致。因此下列语句在GNU编译器中皆正确:
pvoid++; //GNU:正确
pvoid += 1; //GNU:正确
//pvoid++的执行结果是其增大了1。
在实际的程序设计中,为迎合ANSI标准,并提高程序的可移植性,我们可以这样编写实现同样功能的代码:
void * pvoid;
(char *)pvoid++; //ANSI:正确;GNU:正确
(char *)pvoid += 1; //ANSI:错误;GNU:正确
在真实设计时,还是应该尽可能地迎合ANSI标准。
规则四 如果函数的参数可以是任意类型指针,那么应声明其参数为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明显不是一个“纯粹的,脱离低级趣味的”函数!
下面的代码执行正确:
//示例: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); //错误
四、使用技巧
(1)将不同指针类型的变量转换成同一类型后作比较
Derived d(1, 2, 3);
BaseA* pa = &d;//定义BaseA*指针,指向子类定义的对象
BaseB* pb = &d;//定义BaseB*指针,指向子类定义的对象
void* paa = pa;//进行类型转换
void* pbb = pb;//进行类型转换,为接下来的地址比较作准备
if( paa == pbb )//按道理,两者应该相等,因为是指向同一个对象
{
cout << "Pointer to the same object!" << endl;
}
else//不相等就输出error
{
cout << "Error" << endl;
}