回想一下,在C中指针变量拥有与其它变量一样的类型。之所以指针变量会有类型是因为当我们想获取指针变量的值时,编译器已经知道指针所指向的数据的类型,从而可以访问相应的数据。但是,有些时候我们并不关心指针所指向的变量的类型。在这种情况下,我们就是可以使用泛型指针,泛型指针并不变量指定具体的数据类型。
通常情况下,C只允许相同类型的指针之间进行转换。例如:一个字符型指针sptr和一个整型指针iptr,我们不允许把sptr转换为iptr或把iptr转换为sptr。但是,泛型指针能够被转换为任何类型,反之亦然。因此,如果有一个泛型指针gptr,我可以把sptr转换为gptr或者把gptr转换为sptr。在C语言中,我们通常声明一个void指针来表示泛型指针。
很多情况下void指针都是非常有用的。例如:C标准函数库中的memcpy函数,它将一段数据从内存中的一个地方复制到另一个地方。由于memcpy可能用来复制任何类型的数据,所以将它的指针参数设定为void指针是非常合理的。void指针同样可以用到其它普通的函数中去。例如:之前提到过的交换函数swap2,我们可以把函数参数改为void指针,这样swap2就变成一个可以交换任何类型数据通用交换函数,代码如下:
#include <stdlib.h> #include <string.h> int swap2(void *x, void *y, int size) { void *tmp; if ((tmp = malloc(size)) == NULL) return -1; memcpy(tmp, x, size); memcpy(x, y, size); memcpy(y, tmp, size); free(tmp); return 0; }
void指针在用来实现数据结构时是非常有用的,因为我们可以通过void指针存储和检索任何类型的数据。我们再来看一下之前提到过的链表结构ListElmt,回想一下,这个结构包含两个成员:data和next。如果data被声明为一个void指针,那么data就可以指向任何类型的数据。从而,我们就可以使用ListElmt结构来建立各种不同数据类型的链表。
在第5章里,定义了一个链表的操作函数list_ins_next,它的功能是将一个指向data的指针元素插入到链表中:
int list_ins_next(List *list, ListElmt *element, void *data);
要将指针iptr引用的数据插入到名为list的整型链表中,C语言允许我们将整型指针iptr赋值给参数data,因为data是一个void指针。
retval = list_ins_next(&list, element, iptr);
当然,当从一个链表中删除数据时,必须使用正确的数据类型来检索要被删除的数据。这样做是为了保证当我们想对数据进行操作时数据的类型是正确的。正如之前所述,从一个链表中删除元素的函数list_rem_next,它的第三个参数是一个指向void指针的指针:
int list_rem_next(List *list, ListElmt *element, void **data);
想要从list中删除一个整型变量,并且element是要删除元素的引用,我们用如下调用方式。当函数返回时,iptr指向了被删除的数据。这是由于此操作改变了指针本身,将地址传递给了指针iptr,,使其指向了被删除的数据。
retval = list_rem_next(&list, element, (void **)&iptr);
同时,此函数调用包含一个将iptr临时转换为一个指向void指针的指针的过程。正如我们将在下一节所看到的,类型转换是C语言中的一种特殊的转换机制,它允许我们临时把一种类型的变量转换为另一种类型的变量。在这里,类型转换是必要的,因为C语言中虽然一个void指针与其它类型的指针相兼容,但一个指向void指针的指针却并不一定与其它类型的指针兼容。
要将类型为T的变量t转换成S类型,只需要在t前加上用括号括上的S。例如:要将一个整型指针iptr转换为一个浮点型指针fptr,我们在整型指针加上一个用括号括起来的浮点指针即可,如下所示:
fptr = (float *)iptr;
(通常来说将一个整型指针转换成一个浮点指针是一种危险的做法,但在这里我们仅仅用这个例子做一个类型转换的示例而已。)在类型转换之后,iptr与fptr都指向了同一块内存地址。但是,从这个地址取到什么类型的值是由我们用什么类型的指针访问它所决定的。
对于泛型指针来说类型转换非常重要,因为只有告诉泛型指针是通过何种类型来访问地址时,泛型指针才能正确取到值。这是由于泛型指针不会告诉编译器它指向的是何种类型数据,所以编译器既不知道哪个地址要被访问,也不知道多少字节的数据会被访问。当将泛型指针赋值给其他类型的指针时,使用类型转换也是一种很好的代码自注释方法。尽管这里的转换并不是必须的,但这样做能大大提高程序的可读性。
当转换指针时,我们对内存中的对齐方式必须特别注意。具体来说,我们需要知道,指针的类型转换会暗地里改变计算机本身的对齐方式。很多计算机对对齐的方式有要求,因为某些硬件的优化可以使访问内存更有效率。所以,如果有一个非按字对齐的void指针,当我们将它转换为一个整型指针并试图获取它的值时,程序可能在运行时出现异常。
PS:
1、此书(Mastering Algorithms with C)译稿版权归本人(Love_Lei)及好友(bigship)共同所有,未经本人同意谢绝一切转载,并不得抄袭,模仿,盗版!更请大家监督盗版之人!
2、由于本人水平有限,如对译文有任何建议和异议,欢迎大家留言指正,我们共同讨论学习!谢谢!