前言
本博客主要记录一些函数指针和回调函数的简单应用;
函数指针的简单应用
上一篇博客我记录了什么是函数指针?
那么函数指针有什么应用呢?
我们先来简单实现一个计算器;
void menu()
{
printf("****************************\n");
printf("*****1.Add 2.Sub *****\n");
printf("*****3.Mul 4.Div *****\n");
printf("***** 0.Exit *****\n");
printf("****************************\n");
}
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
int main()
{
int input = 0;
do {
menu();
printf("请输入你的选择:");
scanf("%d", &input);
switch (input)
{
int x = 0; int y = 0; int ret = 0;
case 1:
printf("请输入两个操作数:");
scanf("%d%d",&x,&y);
ret=Add(x, y);
printf("%d\n",ret);
break;
case 2:
printf("请输入两个操作数:");
scanf("%d%d", &x, &y);
ret=Sub(x, y);
printf("%d\n", ret);
break;
case 3:
printf("请输入两个操作数:");
scanf("%d%d", &x, &y);
ret=Mul(x, y);
printf("%d\n", ret);
break;
case 4:
printf("请输入两个操作数:");
scanf("%d%d", &x, &y);
ret=Div(x, y);
printf("%d\n", ret);
break;
case 0:printf("退出计算器\n");
break;
default:printf("选择错误\n");
}
} while (input);
return 0;
}
这个代码并不是最好的,同时给人的感觉总是那么的冗长,有很多重复的步骤;
而且我们以后如果想要加入其它计算器的话,我们就需要在加一个case语句,然后又是重复上面的步骤,如果像这样的话,随着我们需求的不断增多,我们需要加入的case语句更是越来越多;我们有没有什么办法来优化一下呢?
void menu()
{
printf("****************************\n");
printf("*****1.Add 2.Sub *****\n");
printf("*****3.Mul 4.Div *****\n");
printf("***** 0.Exit *****\n");
printf("****************************\n");
}
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
int main()
{
int input = 0;
do
{
menu();
printf("请输入两个数:");
scanf("%d",&input);
int(*(p[5]))(int, int) = {0,Add,Sub,Mul,Div};
int len = sizeof(p)/sizeof(p[0]);
if (!input)
{
printf("退出计算器\n");
}
else if (input >= 1 && input <= len - 1)
{
printf("请输入两个操作数:>");
int x = 0;
int y = 0;
scanf("%d%d",&x,&y);
int ret=p[input](x,y);
printf(">:%d\n",ret);
}
else
{
printf("选择错误\n");
}
} while (input);
return 0;
}
由于我们所写的函数类型都是一样的,自然的我们可以吧相同类型的函数装在一个数组里面,然后通过数组的访问方式去访问里面的元素,这大大的减少了代码量和重复量,对于后期代码的维护有着极大的方便、维护成本也是更少如果后期我们有了新的需求,我们只需在数组里面加入函数名就行了,其它不用动,相比于前面直接加case更加的简洁方便!!!同时这种借助数组的然后来实现某些功能的方式叫做跳转表!!!;
什么是回调函数?
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
利用冒泡排序实现qsort函数
我们知道冒泡排序(升序)的主要思想就是相邻元素两两比较,较大大的元素,往后挪;
每冒一趟,就可以右边冒出一个最大值:
当然我们每一趟都会在最右边冒出一个最大值,那么这个最右边的最大值就不用参与下一次的冒泡了,个实际参与冒泡的元素:N-1;后面依次这样进行:
冒泡排序主要代码:
void Bubble_Sort(int*a,int len)
{
for (int i = 0; i < len - 1; i++)
{
for (int j = 0; j < len - 1 - i; j++)
{
if (a[j] > a[j + 1])
{
int tmp = a[j+1];
a[j + 1] = a[j];
a[j] = tmp;
}
}
}
}
既然大概解了冒泡排序,那么我们既然要模拟qsort那么我们就应该来了解一下qsort函数:
参数解释:
既然介绍了qsort函数,我们来简单使用一下:
int cmp_int(const void* left, const void* right)
{
return *(int*)left - *(int*)right;//由于我们这个函数是我们设计的,我们肯定知道我们要排的数组是什么类型的元素啊,于是我们直接强转成对应元素指针就行了
}
int main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int len = sizeof(arr) / sizeof(arr[0]);
int (*cmp)(const void*, const void*) = &cmp_int;
qsort(arr,len,sizeof(int),cmp);
for (int i = 0; i < len; i++)
{
printf("%d ",arr[i]);
}
return 0;
}
当然我们还可以用它来比较结构体:
typedef struct student
{
char name[20];
char sex[10];
char ID[20];
int age;
}ST;
void printfArr(ST*p,int len)
{
for (int i=0;i<len;i++)
{
printf("%s\n",(p+i)->name);
}
}
int cmp_name(const void* left, const void* right)
{
return strcmp(((ST*)left)->name, ((ST*)right)->name);//这里该百年left和right的相减顺序,改变时降序还是升序
}
int main()
{
ST arr[] = { {"mahuateng","man","12107980831",46},
{"leijun","man","12107930841",45},
{"lihongyan","man","1210794250831",21},
{"dongmingzhu","woman","321079320831",36},
{"zero","man","1124832752",19} };
int len = sizeof(arr) / sizeof(arr[0]);
int (*cmp)(const void*, const void*) = cmp_name;
qsort(arr, len, sizeof(ST), cmp);
printfArr(arr, len);
return 0;
}
以名字为结构体排序:
以年龄为结构体排序:
typedef struct student
{
char name[20];
char sex[10];
char ID[20];
int age;
}ST;
int cmp_age(const void* left, const void* right)
{
return ((ST*)left)->age - ((ST*)right)->age;//这里就要改变比较方式,比较字符串和比较整数方式不一样
}
int main()
{
ST arr[] = { {"mahuateng","man","12107980831",46},
{"leijun","man","12107930841",45},
{"lihongyan","man","1210794250831",21},
{"dongmingzhu","woman","321079320831",36},
{"zero","man","1124832752",19} };
int len = sizeof(arr) / sizeof(arr[0]);
int (*cmp)(const void*, const void*) = cmp_age;
qsort(arr, len, sizeof(ST), cmp);
return 0;
}
实际上qsort函数,底层是利用快速排序的思想来实现的,但是现在我们不利用快排的思想来实现,我们利用冒泡排序的思想来实现:
既然是模拟实现,那我们就跟着正品库函数一样设计参数:
然后冒泡排序的框架写出来:
虽然我们不知道数据类型,但是我们知道这个数据所占空间地大小啊,因此,我们也能知道每个元素的首地址!!!,然后我们再把首地址强制转换为char*的指针,加上步长,我们就可以把相两个元素之间的元素进行交换了,一个字节一个字节的交换:
就这样在宽度的范围里,一个字节一个字节的交换内容:
因此主要代码实现:
void My_qsort(void*arr,size_t len,size_t width,int(*cmp)(const void*,const void*))
{
for (int i = 0; i < len - 1; i++)
{
for (int j = 0; j < len - 1 - i; j++)
{
char* left = (char*)arr + j * width;//
char* right = (char*)arr + (j + 1) * width;//left和right是相邻元素的首地址
if (cmp(left,right) > 0)//这里我们将首地址传过去,让使用者帮我们实现,两个元素之间的判断
{
for (int k = 0; k < width; k++)//交换宽度范围内的内容
{
char tmp = left[k];//由于不知道数据类型,只能一字节一字节的交换;
left[k] = right[k];
right[k] = tmp;
}
}
}
}
}
当然我们还能基于插入排序实现qsort函数:
My_qsort2(void* arr, size_t len, size_t width, int(*cmp)(const void*, const void*))//插入排序实现;
{
char key[100] = { 0 };//将待插入的数据先保存起来;
for (int i = 0; i < len - 1; i++)
{
int right = i;//记录待排序区间最后一个元素下标
char* end = (char*)arr + (right+1)* width;//先把待插入数据取出来先存起,然后表示空位置
for (int j = 0; j < width; j++)//保存起来
{
key[j] = end[j];
}
char* begin = NULL;//当前位置于key的元素进行比较
end;//空位//
while (right>=0)//开始进行比较
{
begin = (char*)arr + right * width;
end = (char*)arr + (right + 1) * width;
if (cmp(begin,key) > 0)//具体两个元素怎么比较,交给使用者实现
{
for (int k = 0; k < width; k++)
{
end[k] = begin[k];
}
right--;
}
else//满足条件直接退出
break;
}
end = (char*)arr + (right + 1) * width;//是break来到这,和循环完来到这
for (int t = 0; t < width; t++)//最后将待插入的数据插在空位置
{
end[t] = key[t];
}
}
}
当然还可以利用其它排序来完成。