1

上一篇文章中,通过一个简单的swap函数引入了C语言中泛型编程的实现方式。下面我们会看一个复杂点的例子,下面是代码:

void* lSearch(void* key,void* base,int n,int elemSize,int(*cmpFn)(void*,void*))
{
    for(int i=0;i<n;i++)
    {
        void* elemAddr = (char*)base + i*elemSize;
        if (cmpFn(key,elemAddr)==0)
            return elemAddr;
    }
    return NULL;
}

这是一个实现线性查找功能的函数,参数key是我们要查找的元素,base是查找数组的基地址,n为数组的长度,elemSize是元素类型的大小,cmpFn是一个函数指针,这个函数由我们自己提供。其中要注意的是下面这一句:

 void* elemAddr = (char*)base + i*elemSize;

每次循环,elemeAddr都指向数组中下一个元素的起始地址。为什么要对base指针进行强制类型转换,转换成char呢?这跟上一篇文章中的讲到的原因一样,对于int类型的指针来说,我们知道每次进行++操作,指针会自增4字节,然而对于void类型指针进行加法操作,编译器不知道每次要加多少。所以我们将base指针强制转换成char,这样编译器就知道指针加法的基数是1.

下面我们来看cmpFn的实现,假如我们要查找的类型是int,则:

int intCmp(void* vp1,void* vp2)
{
    int* ip1 = vp1;
    int* ip2 = vp2;
    return *ip1 == *ip2;
}

OK,假如现在我们有一个字符串数组:

char* words[] = {"so","sally","can","wait"};

利用上面的lSearch代码,我们要查找“can”,则调用形式应该是:

char* key = "can";
lSearch(&key,words,4,sizeof(char*),strCmp);

其中strCmp是我们要自己提供的。注意lSearch的第一个参数是一个void类型,而key是char类型,这里我们传递给它的则是key的取地址,即一个(char**)类型的变量,并且elemSize取的是char类型指针的大小。我们先实现strCmp,然后再来说为什么。

int strCmp(void* vp1,void* vp2)
{
    char* cp1 = *(char**)vp1;
    char* cp2 = *(char**)vp2;
    return strcmp(cp1,cp2);//调用字符串比较的库函数
}

这里我们将两个void指针强制转换成char**类型,然后再进行解引用。这样做的意义在于,针对字符串数组来说,字符串是存储在常量区域的,字符串类型的变量是一个指针,而字符串数组存储的就是这些指针。对于words[1]来说,它存储的是一个地址,这个地址指向的内存空间存储的是“sally”。假如我们直接写成:

char* cp1 = (char*) vp1;

那么,strcmp比较的则是存储字符串的地址,而不是字符串本身。这也是为什么lSearch的第一个参数,我们传递的是key的取地址,而不是直接的key。


Dr_Noooo
50 声望5 粉丝