4、void和void指针分析

一、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、具体使用规则

  1. 如果函数没有返回值,那么应声明为void类型;
  2. 如果函数无参数,那么应声明其参数为void;
  3. 不同标准下对void指针进行的算法操作
  4. 如果函数的参数可以是任意类型指针,那么应声明其参数为void * ;
  5. 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值