文章目录
前言一、void *指针是什么?void*指针简单使用 二、指针内存大小指针所占大小void *类型指针与其他类型指针区别基本区别取值操作与地址增长操作 三、void *指针作为函数传参实现memset实现memcpy 总结前言
这篇将介绍C语言令人头疼的知识点–指针。介绍void *这个特别的指针,介绍指针内存分配、指针作为函数传参改怎么使用以及为什么不能直接对void *指针做一些操作。
一、void *指针是什么?
C语言中,一共有几种指针类型,其中最特别的就是void *指针。void *指针就是指向任何类型的指针,在编译的时候不会确定其指向的类型,是在程序中进行指向的。
void*指针简单使用
上面说了,void *指针是可以指向任何类型的指针,那让我们看看具体改怎么使用叭。
int main() {int i_t[2]={0,1};void *v_t=i_t;/*void *可以获得指向任何地址,但不知道指向地址的类型大小*//*普通指针使用,获得void *指针指向的地址并强制转换为int *类型,使之有知道指向类型的大小*/int * p_t=(int *)v_t;printf("Get int * i_t[0] = %d i_t[1] = %d \n",*p_t,*(p_t+1));/*void指针使用,可以直接给void *指针赋值,让它指向的类型改为int*的地址*/ v_t=p_t;printf("Get void * i_t[0] = %d i_t[1] = %d \n",*(int *)v_t,* (int *)(v_t+4));/*printf("Get void * i_t[0] = %d i_t[1] = %d \n",*(int *)v_t,* (char *)(v_t+4));*/ //这两条printf输出效果一样,/*第二条printf输出警告,对void * 进行 +操作的警告,在GNU标准,对void *取加操作是仅仅类似char *的取加操作*/system("pause");return 0; }
运行结果
Get int * i_t[0] = 0 i_t[1] = 1Get void * i_t[0] = 0 i_t[1] = 1
注意,void *在使用的时候,对void *进行地址的+或者-操作,一定要进行操作需要进行强制类型转换来进行操作,不是所有编译器都支持GNU标准的。
二、指针内存大小
指针所占大小
指针大小其实是由编译器和操作系统共同决定的,一般规律如下。
32位操作系统,32位编译器,指针大小是32位64位操作系统,32位编译器,指针大小是32位64位操作系统,64位编译器,指针大小是64位
32位指针大小,能够寻址的空间是4G,62位指针大小能够寻址1800T的地址,这个仅作为了解即可。
void *类型指针与其他类型指针区别
基本区别
区别可以看下图,指针所在的内存都是一样的,但是void *指针与其他指针不一样的就是,它包含所指向类型的信息。这个特性就意味着,它可以指向任何类型的变量,void *类型指针只是指向了这个变量的地址而已。
取值操作与地址增长操作
在ANSI的标准下,在void *类型如果不强制类型转换是不能够进行取值与地址的增长的操作。
为什么呢?我们先看一下int *类型的指针是怎么进行取值的操作的。
取值操作
int *是取得值的范围是(当前首地址+int类型偏移大小)。double *则是当前首地址+double类型偏移大小。这里的偏移大小就是所占的字节数,也就是sizeof(int)或者sizeof(double)。
void *指针是没有包含其指向类型的信息,也就是在不使用强制类型转换下,编译器不知道void *的取多少偏移地址的范围才是所指向的值所在地址范围。也就是void *仅仅具有当前指向目标的首地址,并不知道取首地址之后多少字节合适,这样来取直,究竟是4字节还是8字节,编译器它不知道。它不够智能,得需要告诉这个蠢货,使用强制类型告诉它。
地址增长
地址增长和取值是类似的,void *不知道++改移动多少字节合适,就不具体论述了,聪明的你心中一定有答案了。
三、void *指针作为函数传参
这里我们将使用void *指针作为传参,使得程序的泛用性更强。这里我们就实现两个个C库中的memset和memcpy,边实现边回顾上面的知识点,就知道为什么传入参数是void *类型以及为什么需要指定长度。
实现memset
函数原型:返回首地址,传入需要指定需要指向初始化的地址以及想要初始化的值和长度。
void * memset(void *s,int c,size_t n)
函数功能:初始化函数,将某一块内存中的全部设置为指定的值。
请看下面的代码和测试函数:
void * my_memset(void *s,int c,size_t n){char *c_s=(char *)s;while(n--) *c_s++=c;return s;}int main() {int i_t[2]={1,1};my_memset(i_t,0,sizeof(i_t));for(int i=0;i<(sizeof(i_t)/sizeof(int));i++)printf(" %d ",i_t[i]);system("pause");return 0; }
运行结果:
0 0
实现memcpy
函数原型:
void *memcpy(void *dest, void *src, unsigned int count);
函数功能:由src所指内存区域复制count个字节到dest所指内存区域
代码以及测试函数:
void * my_memcpy(void *dest,void *src,unsigned int size){char *b_dest=(char *)dest,*b_src=(char *)src;unsigned int len;for(len=size;len>0;len--){*b_dest++=*b_src++;}return dest;}int main() {int i_t[2]={1,1};int i_f[2]={0,0};my_memcpy(i_f,i_t,sizeof(i_t));for(int i=0;i<(sizeof(i_t)/sizeof(int));i++)printf(" %d ",i_f[i]);system("pause");return 0; }
代码思路简单分析:
复制和清零的思路都是对传入的地址,进行一位一位的操作。
上面说了,void *不包含类型,所以可以作为任意类型进行传入,这样就达到可以操作任意类型。传入的仅仅是没有类型的地址信息而已。在C中由于指针本身并不包含需要操作长度的信息,因此必须传入需要操作的长度信息。不然仅仅传入指针编译器并不会知道究竟操作的范围。
总结
使用void *指针能够加强函数的适用性,但是操作void *指针需要非常的注意,不然容易导致指针操作越界,导致一些其他不可知的程序错误。