- void
swap(int* i1,int* i2){ -
int temp; -
temp=*i1; -
*i1=*i2; -
*i2=temp; - }
当你想交换两个char类型时,你还得重写一个参数类型为char的函数,你会多么想念C++中使用模板。等一下,是不是能用无类型的指针来作为参数呢?看如下改动:
- void
swap(void *vp1,void *vp2){ -
void temp=*vp1; -
*vp1=*vp2; -
*vp2=temp; - }
这段代码是错误的,是通不过编译的。首先,变量是不能声明为void无类型的。而你不知道调用此函数传进的参数是什么类型的,无法确定一种类型的声明。同时,不能将*用在无类型指针上,因为系统没有此地址指向对象大小的信息。在编译阶段,编译器无法得知传入此函数参数的类型的。这里要想实现泛型的函数,需要在调用的地方传入相关要交换的对象的地址空间大小size,同时利用在头文件string.h中定义的memcpy()函数来实现。改动如下:
- void
swap(void *vp1,void *vp2,int size){ -
char buffer[size]; -
memcpy(buffer,vp1,size); -
memcpy(vp1,vp2,size); -
memcpy(vp2,buffer,size); - }
函数memcpy()
原型:void* memcpy(void *dest,void *src,unsigned int n)
作用:由src所指内存区域复制n个字节到dest所指内存区域。
说明:src和dest所指内存区域不能重叠,函数返回指向dest的指针。
之所以将buffer声明为char类型,只是因为char类型的大小是1字节,这样声明可以为buffer指向的空间分配size字节大小的空间。
在调用这个函数时,可以像如下这样调用(同样适用于其它类型的x、y):
- int
x=27,y=2; - swap(&x,&y,sizeof(int));
其实,你会发现,这样的调用也是没有报错的:
- int
i=44; - short
s=5; - swap(&i,&s,sizeof(short));
两个不同类型的变量同样也可以实现交换。而根据系统的不同,即是小端法还是大端法保存数据,显示的结果是不同的。以Windows或linux下更常见的小端法为例,函数swap将整型i的低16位和短整型的16位进行了交换。
这样的调用是可行的,也提醒我们C语言下这样实现泛型编程是不安全的,编译器只进行有限的检查,更多的需要程序员的细心。如下的调用是可以的,但却毫无意义:
- int
i=22; - short
s=11; - swap(&i,&s,8);
调用此函数,将交换i的地址向后8字节的内容与s的地址后8字节的内容的交换,而对于大小为2字节的short类型的s,大小为4字节的int类型的i这样的操作一般情况下是没有意义的。在编程时也要注意这个。C语言为我们提供了许多接近机器底层的操作,但也是一把双刃剑。
下面看这样的调用:
- char
*husband=strdup("Fred"); - char
*wife=strdup("Linda"); - swap(&husband,&wife,sizeof(char*));
函数strdup():原型:char* strdup(char* s)。作用是复制字符串。
husband与wife是两个字符串,在交换这样的类型时,不能直接将两个字符串的地址传入swap函数,因为两个字符串的大小是不一定的,进行复制肯定会出现错误。这里是将保存两个字符串首地址的指针的地址进行交换,使两个指针指向对方的字符串,实现字符串的交换。sizeof()中写成 char**也是可以的,写成double*也是可以的,因为所有指针的大小都是4字节。但写成char*,更加明确,在别人读代码会引起更少的误解,因为husband与wife是char类型的一级指针。
下面看另一种功能的函数:
- int
lsearch(int key,int array[],int size){ -
for(int i=0;i<size;++i) -
if(array[i]==key) -
return i; -
return -1; - }
此函数在数组array中查找key元素,找到后返回它的索引,找不到返回-1.如上,也可以实现泛型的函数:
- void*
lsearch(void* key,void *base,int n,int elemSize){ -
for(int i=0;i<n;++i){ -
void *elemAddr=(char *)base+i*elemSize; -
if(memcmp(key,elemAddr,elemSize)==0) -
return elemAddr; -
return NULL; -
} - }
代码第三行:将数组的首地址强制转换为指向char类型的指针,是利用char类型大小为1字节的特性,使elemAddr指向此”泛型“数组的第 i-1个元素的首地址。因为之前已经说过,此时你并不知道你传入的是什么类型的数据,系统无法确定此数组一个元素有多长,跳向下个元素需要多少字节,所以强制转换为指向char的指针,再加上参数传入的元素大小信息和累加数i的乘积,即偏移地址,即可得此数组第i-1个元素的首地址。这样使无论传入的参数是指向什么类型的指针,都可以得到指向正确元素的指针,实现泛型编程。
函数memcmp(),原型:int memcmp(void *dest,const void *src,int n),比较两段长度为n首地址分别为dest、src的地址空间中的内容。
此函数在数组base中查找key元素,找到则返回它的地址信息,找不到则返回NULL。
如果使用函数指针,则可以实现其行为的泛型:
- void
*lsearch(void *key,void *base,int n,int elemSize,int(*cmpfn)(void*,void*,int)){ -
for(int i=0;i<n;++i){ -
void *elemAddr=(char *)base+i*elemSize; -
if(cmpfn(key,elemAddr,elemSize)==0) -
return elemAddr; -
return NULL; -
} - }
- }
在调用时,可以将指向不同行为的形式相同的函数的指针传入此函数,以实现lsearch函数行为的不同。
函数指针:
返回类型 (*函数指针名)(形参表)