C语言:指针(4)
1. 回调函数
回调函数就是指通过函数指针调用的函数。
如果将函数指针作为参数传递给另一个函数,另一个函数根据指针来调这个函数,那么被调用的函数就是回调函数。回调函数不是由这个函数的实现方直接调用,而是在特定的条件下由另一方调用的。
例如在指针(3)中,我们将多个函数的地址放在函数指针数组中,根据不同的选择来对输入的x和y执行相应的计算,这里就是采用的就是回调函数的功能。
2. qsort使用举例
2.1 qsort函数
在 C 语言中,qsort是标准库<stdlib.h>提供的快速排序函数,用于对数组进行排序。其函数原型如下:
void qsort(void *base, size_t nmemb, size_t size,int (*compar)(const void *, const void *));
其中:
base是要排序的数组首元素的地址
nmemb是数组中元素的个数
size是每个元素的大小,单位为字节
compar是指比较函数的指针,用来定义排序规则
2.2 利用qsort函数排序整型数据
qsort会根据传递的函数指针所指向的那个函数的返回值来排序,例如两个值a,b进行比较:
当返回值为负时,a就会排在b前面;返回值为正时,a排在b后面;返回值为0时,qsort会将这两个值视为相等,但并不会保证相等元素排序与原始排序相同,例如:原始排序是 a b,a在前,而qsort排序后可能会是b a,即相等的元素排序后的相对位置不一定与原始位置相同。
知道这些我们可以利用qsort函数做一个简单的排序,让一个数组中的元素按升序排序:
#include <stdio.h>
#include <stdlib.h>
int i;int cmp(const void* p1,const void* p2)
{return (*(int *)p1 - *(int *)p2);
}
int main()
{int arr[] = {1,4,3,6,8,9,7,2,5,0};int num = sizeof(arr)/ sizeof(arr[0]);int sz = sizeof(arr[0]);qsort(arr,num,sz,cmp);for(i = 0;i < num;i++){printf("%d ",arr[i]);}return 0;
}
0 1 2 3 4 5 6 7 8 9
我们可以看到输出结果与预期一致,qsort正确得进行了排序。
另外,我们可以看到我们定义的cmp函数中,在进行运算前前面先将指针p1与p2的类型先强制转换为了int *,这是因为void *虽然可以接受各种类型的指针,但是却不能直接使用,必须先进行强制类型转换。这里因为我们希望是升序,所以返回p1减去p2的值。若p1 < p2,返回值为负,p1排在p2前;若p1 > p2,返回值为正,p1排在p2后。如果我们想要实现降序,只需要将cmp函数体内p1与p2的位置互换或者是在计算表达式前加上 - ,改变正负即可。
2.3 使用qsort排序结构数据
现在我们尝试利用qsort函数来对结构数据进行排序,例如:
#include <stdio.h>
#include <stdlib.h>
int i;
struct Stu
{char name[10];int age;
};
int cmp_by_name(const void *p1,const void *p2)
{return strcmp(((struct Stu*)p1)->name,((struct Stu*)p2)->name);
}int main()
{struct Stu s[] = {{"zhangsan",20},{"lisi",23},{"wangwu",19}};int len = sizeof(s)/ sizeof(s[0]);int sz = sizeof(s[0]);qsort(s,len,sz,cmp_by_name);for(i = 0;i < len;i++){printf("%s\n",s[i].name);}return 0;
}
lisi
wangwu
zhangsan
根据打印结果,我们发现,qsort成功按照字母顺序进行了排序。同样的,我们也可以写出cmp_by_age函数,让qsort按照年龄来进行排序。
另外,我在定义函数cmp_by_name时,在函数体内部用 -> 来访问结构中的name,而在打印时使用的是 . 。这是因为,我在定义函数的返回值时,这里的数据是指针类型,只能用->来访问,而在打印时则是直接访问结构,所以要使用 . 来访问结构中数据。因此,如果我们不想使用->,也可以先对指针进行解引用操作然后再使用 . 来访问数据。
3. qsort函数的模拟实现
学习了qsort函数的使用方法后,我们现在尝试自己写代码来模拟实现qsort函数。
例如,我们用冒泡排序的方法法来模拟qsort函数。
那么。根据上面的代码的经验,我们可以得到下面的代码:
#include <stdio.h>int cmp(const void *p1,const void *p2)
{return (*(int*)p1 - *(int*)p2);
}
void swap(void *p1,void *p2,int sz)
{int i;char temp;for(i = 0;i < sz;i++){temp = *((char *)p1 + i);*((char *)p1 + i) = *((char *)p2 + i);*((char *)p2 + i) = temp;}
}void bubble(void* base,int num,int sz,int (*cmp)(const void *p1,const void *p2))
{int i,j;for(i = 0;i < num - 1;i++){for(j = 0;j < num - 1 - i;j++){if(cmp(base + j * sz,base + (j + 1) * sz) < 0){swap(base + j * sz,base + (j + 1) * sz,sz);}}}
}
int main()
{int i;int arr[] = {1,5,3,7,9,4,0,2,6,8};int num = sizeof(arr)/ sizeof(arr[0]);int sz = sizeof(arr[0]);bubble(arr,num,sz,cmp);for(i = 0;i < num;i++){printf("%d ",arr[i]);}return 0;
}
9 8 7 6 5 4 3 2 1 0
这里主体的逻辑和上面的代码一样,值得一提的是swap这个函数。
在定义这个函数的时候我们是将指针类型强制转换为char * 类型,这样处理的好处是:我们为了模拟qsort函数实现通用设计,在不知道接受的数据类型的前提下,将指针类型强制转换为char *类型,而char *类型指针和 i 相加时只会跳过 i 个字节,这样能够充分交换每一个字节中的数据,防止遗漏,保证了数据的准确性。