在C语言程序开发中,一些比较成熟的库函数常常会被使用。毕竟,如果手边就有不错的“轮子”可以用,没有程序员愿意再花费精力凭空造一个轮子出来。
没有程序员愿意凭空造轮子奇怪的void*指针
事实上,C语言标准库提供了非常丰富的成熟函数供程序员使用,不过不知道读者注意到没,有些库函数的参数是void*类型的,例如:
void*memcpy(void*dest,constvoid*src,size_tn);void*memmove(void*dest,constvoid*src,size_tn);前面的章节在讨论C语言指针时,提到指针从某种程度上来说,无非就是一个地址,它的类型只是用于说明数据结构的。例如int型指针告诉编译器该地址处紧接着的4字节按照int型数据解释,double型指针高速编译器接下来的8字节按照double型数据解释。
这里假定int型变量占用4字节内存空间,double型变量占用8字节内存空间。
对于void型指针,之前的分析似乎就不再适用了。因为void类型是一个特殊的类型,常被称作“空类型”,C语言中没有void类型的变量,所以在遇到void指针时,编译器根本不知道如何解释接下来的内存,甚至编译器都不知道接下来多少内存属于它。
编译器都不知道接下来多少内存属于它正因为如此,在C语言程序开发中,遇到void*指针时,如果需要访问它指向的内存,需要重新指定类型,否则就会报错。例如下面这段C语言代码:
#includestdio.hvoidmyprint(void*p){charc=p[0];printf(c=%c\n,c);}intmain(){charbuf[]=helloworld;myprint(buf);return0;}voidmyprint(void*p){charc=((char*)p)[0];printf(c=%c\n,c);}有时候为了简便,常常使用中间变量:
voidmyprint(void*p){char*cp=(char*)p;charc=cp[0];printf(c=%c\n,c);}这时再编译执行就一切正常了。
为什么使用void*指针
仅从上面的实例来看,使用void指针似乎比较麻烦:至少强制类型转换操作是少不了的。那为什么还要使用void指针呢?
为什么要是用void*指针仔细分析上面的实例,读者应注意“在使用void型指针时,要将其先转换为char型”,这其实要求程序员事先了解void指针指向的数据结构(本例是char型),否则就没法使用void*指针。
利用这一点,程序员可以使用void*将不公开的数据隐藏起来,避免外界调用者访问和修改它。例如,在实际的C语言项目开发中,操作某个对象时,常常先构建结构体structS描述该对象,然后使用init()函数获取相应信息,因为接下来的操作函数handle()需知道要操作哪个对象,所以要使用init()函数返回的信息,C语言代码似乎可以这么写:
structS*p=init();handle(p);从上面两行C语言代码可以看出,其实外界调用可以不用关心p的具体数据结构。不过,如果这么写了,外界又的确可以随意访问p指向的数据结构,这样就很危险了。事实上,我们完全可以将p转换为void*指针:
void*p=init();handle(p);这样一来,只有init()库的开发者才能访问p指向的内容,避免外界调用者“意外”或者“恶意”的修改,整个C语言程序就会稳定和安全很多了。
另外,void指针在一些教科书里被称作“万能指针”,这主要是因为任意指针都可以使用void指针传递,并且编译器不会报出“类型不匹配”相关的警告。例如,要是将myprint()函数的参数类型修改为int*型,相关C语言代码如下:
voidmyprint(int*p){char*cp=(char*)p;charc=cp[0];printf(c=%c\n,c);}intpthread_create(pthread_t*thread,constpthread_attr_t*attr,void*(*start_routine)(void*),void*arg);小结
本节抛砖引玉,主要讨论了void指针在实际C语言项目开发中的特性和用途。void指针可以将不希望被公开的数据结构隐藏,避免外界调用“意外”或者“恶意”的修改。另外,void*指针作为“万能指针”,可以传递任意类型的指针,而不引起“类型不匹配”相关的编译警告。
点个赞再走吧欢迎在评论区一起讨论,质疑。文章都是手打原创,每天最浅显的介绍C语言、linux等嵌入式开发,喜欢我的文章就