前言
本期内容是指针的最后一期,主要讲的是函数指针数组,总体不难,感谢各位支持。
目录
1.函数指针数组的使用
在上一期内容中,我们使用了函数指针完成了代码的简化,我们可以把函数指针作为一个数据类型构造出相应的数组,通过遍历数组,我们也能对函数进行调用。
需要注意的是声明函数指针数组的时候只需要在变量名后面增加【】,【】里面必须标注数组的大小。
// 函数指针数组
int (*arr[])(int,int) = {add,sub,mul,div};// []数字可加可不加
提出问题:当我们的功能越加越多,那么switch case语句就会越来越长,代码就会非常冗余,
我们可以使用函数指针数组,将所有方法存起来,这里第一个元素可以设置为0,因为数组的下标要和主菜单的下标一一对应(主菜单输入1就是add方法)。
如此以来,可以完全把switch case全部删除,如果想要增加功能,除了单独写函数以外,剩下的只需要在函数指针数组里面添加相应功能,以及控制输入的范围即可。
2. 函数指针数组指针
顾名思义,指向函数指针数组的指针,我们只需要在函数指针数组的基础上修改。
// 函数指针数组
int (*arr[5])(int,int) = {};
// 函数指针数组指针
int (*(*ptr)[5])(int,int) = &arr;
这个指针指向的是一个五个元素的数组,这个数组的类型是int (*)(int, int) ,即函数指针。如果你掌握了这些知识,其实还能再套一层,把这个指针放到指针数组里面......无限套娃。
3.回调函数
通过一个函数指针调用的函数。如果你把函数指针作为参数传递给另外一个函数,这个指针用来调用所指向的函数时,我们就说这是回调函数。
3.1 qsort的举例
我们之前写的冒泡排序只能排序某一类(int)的数组,不能做到通用,我们如何来解决这个问题呢?
首先来看一下c语言的库函数qsort,能够做到排序不同种类的数组元素,这是如何做到的呢,其实真正的玄机是在第四个形参,这是一个函数指针,需要用户自己提供一个方法来诠释如何比较这类元素的大小,比如传入一个比较结构体大小的函数指针,就能比较两个结构体的大小。
为了理解这个,需要认识一下void*指针,万能指针,具体作用如下:
对于qsort的最后一个形参而言,这是一个函数指针,这个函数的形参有两个元素,返回的值是正数说明第一个元素大于第二个元素,返回值为负数说明,第一个元素小于第二个元素,直接让两个元素相减,得到的返回值就可以完成升序,如果需要降序那么就把这两个元素的名称交换即可,但是这仅限于整型,对于其他类型来说,以下情况是一种普遍的写法:
int compare_generic(const void *a, const void *b) {
// 假设比较整数
int num1 = *(int*)a;
int num2 = *(int*)b;
if (num1 < num2) return -1; // a 更小,排在前面
if (num1 > num2) return 1; // a 更大,排在后面
return 0; // 相等
}
所以我们需要自行定义一个比较函数用来比较指定类型的元素大小,这里需要注意的一点是,由于void*不能直接解引用,所以需要进行强制类型转换再进行解引用。
如果还是不过瘾,我们可以来比较一下结构体的大小,创建一个结构体,按照结构体内的字符串进行比较大小。具体逻辑基本类似,需要注意的一点是,这里字符串比较返回值刚好就是正数0或负数,所以直接返回即可。
比较结构体的age也是一样,只需要增加一个比较方法即可。
3.2 分析qsort
① 第一个参数:起始指针的值为什么是void*,这是因为让方法能够通用,无论是什么类型的数据都能使用这个方法。
②第二个参数:确定整体数组的元素的个数。
③第三个参数:确定单个元素的字节数。这样一来就能找到一个范围内的所有元素了。
④第四个参数:比较方法,根据不同的类型可以自行定义比较方法。
void qsort(void *base, size_t nmemb, size_t size,
int (*compar)(const void *, const void *));
3.3 通用类型的冒泡排序
根据qsort的排序方法所传入的形参,我们可以尝试模仿一下完成bubble_sort的通用方法。
原版的关键代码进行解读:
这是冒泡排序的内部算法,我们需要注意以下几点:
①如何判断两个元素谁大谁小:使用比较函数。
②如何确定两个相邻元素的首地址:
1)先将void*类型强制转换为char*,将粒度最细分。
2)通过传入的参数width(即一个元素的字节数)、base(首元素地址)计算得出:
3)最终得到:(char*)base +(j * width)就是j下标元素的首地址。
③知道两个元素的首地址之后,可以传入cmp对比函数以及Swap交换函数。
for (int i = 0; i < sz - 1; i++)
{
int flag = 1; // 表示已经是有序数组
for (int j = 0; j < sz - i - 1; j++)
{
// 将void*强转成char*,使其粒度最细,j的地址就是:base + (j * width)
if (cmp((char*)base + (j * width), (char*)base + (j + 1) * width) > 0)
{
// 如果返回的是1,那么就说明第一个元素大于第二个元素,需要交换
Swap((char*)base + (j * width), (char*)base + (j + 1) * width, width);
flag = 0;
}
}
if (flag)
{
break; // 如果一轮过后数组没有发生调换说明数组有序,直接退出循环
}
}
④关于cmp函数,可以根据不同的数据类型来判断,上面已经赘述过一遍,这里不再演示。
// 比较函数
// void*万能指针
// 前者大于后者返回大于0的整数,0 ,小于....
int cmp_int(const void* e1, const void* e2)
{
return (*(int*)e1 - *(int*)e2); // void*不能直接解引用,需要强转为具体类型才能解引用
}
⑤关于Swap函数如何根据两个元素的首地址交换两个首元素:这里还需要给Swap函数传入一个参数width,也就是一个元素的字节数,根据这个元素的字节数进行交换,具体的交换如下所示,传入两个char*指针,根据width循环,一个字节一个字节进行顺序交换即可。
// 仅仅知道这两个元素首地址还不够,还需要知道宽度
void Swap(char* e1, char* e2, int width)
{
int i = 0;
// 每个元素对应的字节进行交换
for (int i = 0; i < width; i++)
{
char tmp = *e1;
*e1 = *e2;
*e2 = tmp;
e1++;
e2++;
}
}
完整代码:
#include<stdio.h>
// 仅仅知道这两个元素首地址还不够,还需要知道宽度
void Swap(char* e1, char* e2, int width)
{
int i = 0;
// 每个元素对应的字节进行交换
for (int i = 0; i < width; i++)
{
char tmp = *e1;
*e1 = *e2;
*e2 = tmp;
e1++;
e2++;
}
}
// 比较函数
// void*万能指针
// 前者大于后者返回大于0的整数,0 ,小于....
int cmp_int(const void* e1, const void* e2)
{
return (*(int*)e1 - *(int*)e2); // void*不能直接解引用,需要强转为具体类型才能解引用
}
// 首元素的地址,数组元素个数,单个元素字节大小,比较方法
void bubble_sort(const void* base, int sz, int width, int (*cmp)(const void* e1, const void* e2))
{
for (int i = 0; i < sz - 1; i++)
{
int flag = 1; // 表示已经是有序数组
for (int j = 0; j < sz - i - 1; j++)
{
// 将void*强转成char*,使其粒度最细,j的地址就是:base + (j * width)
if (cmp((char*)base + (j * width), (char*)base + (j + 1) * width) > 0)
{
// 如果返回的是1,那么就说明第一个元素大于第二个元素,需要交换
Swap((char*)base + (j * width), (char*)base + (j + 1) * width, width);
flag = 0;
}
}
if (flag)
{
break; // 如果一轮过后数组没有发生调换说明数组有序,直接退出循环
}
}
}
// test1冒泡
void test1()
{
int arr[] = { 9,7,12,1,6,5,3,8,2 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz,sizeof(arr[0]),cmp_int);
//qsort(arr, sz, sizeof(arr[0]), cmp_int);
// 打印
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
int main()
{
test1();
return 0;
}
关于指针的内容就全部结束了,如果这个系列对你有帮助,欢迎点赞评论收藏,你的支持是我最大的动力,后期会更新一个面试题系列,有需要的可以关注我博主~