✨作者:@平凡的人1
✨专栏:《C语言从0到1》
✨一句话:凡是过往,皆为序章
✨说明: 过去无可挽回, 未来可以改变
🌹感谢您的点赞与关注,同时欢迎各位有空来访我的🍁平凡舍
🚀前言
回顾我们前面学习了指针数组、数组指针以及简单介绍了函数指针,传参问题等。下面我们将在这些学习过的内容之上继续延展下去,通过这一篇博客,你可以收获更多的知识与内容,同时夯实自己的基础。本篇内容可能比较多,请耐心仔细阅读!💖
🚀有趣的代码
开始之前,基于前面的基础,我们先来看看两个有趣的代码
//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);
这两个代码是什么意思呢?先想一想
代码1:
想看里面的部分void(*p)();p是函数指针,所以对于void(*)()是函数指针类型,0本身是个值,0之前放了个类型,强制类型转换,然后进行解引用。所以说代码1是一次函数调用,调用的0作为地址处的函数.
1.把0强制类型转换为:无参,返回类型是void的函数的地址
2.调用0地址处的这个函数
代码2:
signal是函数名,有两个参数,一个是整型,一个是函数指针类型,此时简单来说只剩下void(*)(int),这又是一个函数指针类型。所以说,
代码2是一次函数声明,声明的signal函数的第一个参数的类型是int,第二参数的类型是函数指针,该函数指针指向的函数参数是int,返回类型是void,signal函数的返回类型也是一个函数指针
其实这看起来是难以理解的,所以我们可以用typedf(起别名)来简化一下,更加容易认识代码:
所以,我们要学会去拆分一下代码,不会导致看不懂别人写的代码是什么意思。
我们前面学到了函数指针,但是却没有举例说到函数指针的用途,函数指针究竟能够去做些什么呢?下面,我们一起来看一看。👇
🚀简单计算器
我们将基于简单计算器这个例子来阐述函数指针的用处在于哪,或者说怎么去用上函数指针呢?我们先来简单模拟实现一下简单计算器(基于整型类型)
实现整型加减乘除的功能
#include <stdio.h>
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;
int x = 0;
int y = 0;
int ret = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
printf("请选择2个操作数:>");
scanf("%d %d", &x, &y);
switch (input)
{
case 1:
ret = Add(x, y);
printf("%d\n", ret);
break;
case 2:
ret = Sub(x, y);
printf("%d\n", ret);
break;
case 3:
ret = Mul(x, y);
printf("%d\n", ret);
break;
case 4:
ret = Div(x, y);
printf("%d\n", ret);
break;
case 0:
printf("退出计算器\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
简单测试运行一下:
你会发现,输入0退出居然要输入两个操作数,这是为什么呢?因为在操作之前我们把输入的数放在前面了,非常的奇怪,退出前居然还要输入两个数,这时候我们稍微改进一下:
#include <stdio.h>
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;
int x = 0;
int y = 0;
int ret = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请选择2个操作数:>");
scanf("%d %d", &x, &y);
ret = Add(x, y);
printf("%d\n", ret);
break;
case 2:
printf("请选择2个操作数:>");
scanf("%d %d", &x, &y);
ret = Sub(x, y);
printf("%d\n", ret);
break;
case 3:
printf("请选择2个操作数:>");
scanf("%d %d", &x, &y);
ret = Mul(x, y);
printf("%d\n", ret);
break;
case 4:
printf("请选择2个操作数:>");
scanf("%d %d", &x, &y);
ret = Div(x, y);
printf("%d\n", ret);
break;
case 0:
printf("退出计算器\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
稍微测试运行一下:
总算解决刚开始出现的问题了,但是这时候又有一个问题:
这一段代码太过冗余了,有点重复性,重复度太高了,我们想想办法解决?
能不能把相同的代码抽离出来,把相同的代码封装成一个函数,下面我们利用函数指针进行改进一下,避免代码的冗余
#include <stdio.h>
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;
}
void calc(int (*pf)(int,int))
{
int x = 0;
int y = 0;
int ret = 0;
printf("请选择2个操作数:>");
scanf("%d %d", &x, &y);
ret = pf(x, y);
printf("%d\n", ret);
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
calc(Add);
break;
case 2:
calc(Sub);
break;
case 3:
calc(Mul);
break;
case 4:
calc(Div);
break;
case 0:
printf("退出计算器\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
简单测试运行一下:
没有任何问题,解决了代码的冗余问题。这就是函数指针的作用。
通过函数地址传递给函数参数,进入函数内部,去调用函数,这就是回调函数。后面会讲到。
🚀函数指针数组
指向函数指针数组的指针是一个 指针
开始之前,我们先来理解函数指针数组:把函数和指针放在数组中,其实就是函数指针数组,怎么理解呢?看一段代码:
怎么去调用里面的函数呢?
所以函数指针数组有什么用?怎么去用?下面来进行演示
还是刚开始冗余代码版本的计算器:
#include <stdio.h>
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;
int x = 0;
int y = 0;
int ret = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请选择2个操作数:>");
scanf("%d %d", &x, &y);
ret = Add(x, y);
printf("%d\n", ret);
break;
case 2:
printf("请选择2个操作数:>");
scanf("%d %d", &x, &y);
ret = Sub(x, y);
printf("%d\n", ret);
break;
case 3:
printf("请选择2个操作数:>");
scanf("%d %d", &x, &y);
ret = Mul(x, y);
printf("%d\n", ret);
break;
case 4:
printf("请选择2个操作数:>");
scanf("%d %d", &x, &y);
ret = Div(x, y);
printf("%d\n", ret);
break;
case 0:
printf("退出计算器\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
我们想一下,如果要添加实现x&y,x^y,x>>y,x<<y的功能,此时就是添加4,5,6,7,8之类的选项,case的选项越来越多以此类推,代码会变得越来越长,这时候,把代码写得整洁一些:把switch语句去掉,创建一个函数指针数组存放函数,通过输入的选择作为下标去调用即可,下面我们来看看代码的修改:
#include <stdio.h> 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; int x = 0; int y = 0; int ret = 0; int (*pfArr[5])(int, int) = { 0,Add, Sub,Mul,Div}; //添加0进去,使之下标对应起来 do { menu(); printf("请选择:>"); scanf("%d", &input); //直接让input作为下标 if (input == 0) { printf("退出计算器"); } else if (input >= 1 && input <= 4) { printf("请选择2个操作数:>"); scanf("%d %d", &x, &y); ret = pfArr[input](x, y); printf("%d\n", ret); } else { printf("选择错误\n"); } } while (input); return 0; }
这样修改的好处在于,以后想添加新的功能,只需要把函数的地址放在数组里面即可,改变范围即可,稍微调整一下代码即可。通过函数指针数组便于以后修改代码。通过这个简单的例子,演示了函数指针数组的作用。下面我们来简单测试一下:
🚩指向函数指针数组的指针
在这里,顺便提一提指向函数指针数组的指针。前面我们写了函数指针数组,是数组,我们对它&,放到一个指针里面即可。
这里基于上述的函数指针数组来用代码简单表示一下:
当然,你会发现,可以套娃套下去…这里就不展开说明了
🚀回调函数
刚开始,实现计算机的时候有说到:
这就是用了回调函数的机制,什么是回调函数?
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
说到这里,太抽象了,难以理解,这时候,我们请出一个例子qsort函数的使用。
说到qsort函数,我们先来说一说冒泡排序
🚩冒泡排序优化版
void bubble_sort(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz - 1; i++)
{
int flag = 1;
//用来判断数组是否有序,提高效率
int j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
flag = 0;
}
}
if (flag == 1)
{
break;
}
}
}
#include <stdio.h>
int main()
{
int arr[] = { 10,9,8,7,6,5,4,3,2,1,0 };
//利用冒泡排序把数组排成升序
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
虽然优化了一下下,提高了效率,但是不管怎么优化,都只能排序整型数组。那怎么办呢?qsort
🚩qsort函数的使用
使用快速排序的思想实现的一个排序函数
下面,我们来简单理解一下qsort函数的参数的意思:
可以看到,比较函数有void*,所以我们很有必要来理解一下void*指针:
这时候,如果用void*来接收,就不会报警告了:
void*是无具体类型的指针,这种指针可以接收任意类型的地址,void*是无具体类型的指针,所以不能解引用操作,也不能±整数操作。这里的参数是void*的原因是因为不知道传过来的类型的指针是什么,所以定义为void*
下面我们还是通过上面的例子来对qsort函数进行简单应用:(记得引用头文件 #include <stdlib.h>)
通过qsort成功实现排序,那能不能实现降序?
我们只要通过改变e1和e2相减的位置即可实现。使之逻辑相反。这个就是回调函数来实现qsort的功能!
这里只是qsort的基本使用。
上面是利用qsort函数来排序整型的,下面我们利用qsort函数来排序结构体
通过结构体的名字进行排序:
通过结构体的年龄来进行排序:
好了,通过上面,已经对qsort有了一定的认识,并且会逐渐的运用,这时候,想想:怎么把冒泡排序改造一下
🚩冒泡排序通用版
把冒泡排序改造成类似qsort函数的实现
void Swap(char* a, char* b,int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *a;
*a = *b;
*b = tmp;
a++;
b++;
}
}
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e2 - *(int*)e1;
}
void bubble_sort(void *base,int sz,int width,int(*cmp)(const void*e1,const void *e2))
{
int i = 0;
for (i = 0; i < sz - 1; i++)
{
int flag = 1;
//用来判断数组是否有序,提高效率
int j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
//通过利用强转base为(char*)乘以宽度的多少来进行比较
if (cmp((char*)base+j*width,(char*)base+(j+1)*width)>0)
{
Swap((char*)base + j * width, ((char*)base + (j + 1) * width),width);
flag = 0;
}
}
if (flag == 1)
{
break;
}
}
}
void test()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
int main()
{
test();
}
运行成功!在这里,我们稍微改造了冒泡排序,现在,来通过冒泡排序,排序结构体:
struct Stu
{
char name[20];
int age;
};
int cmp_stu_by_age(const void* e1, const void* e2)
{
return ((struct Stu*)e1) ->age - ((struct Stu*)e2)->age;
}
void Swap(char* a, char* b,int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *a;
*a = *b;
*b = tmp;
a++;
b++;
}
}
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e2 - *(int*)e1;
}
void bubble_sort(void *base,int sz,int width,int(*cmp)(const void*e1,const void *e2))
{
int i = 0;
for (i = 0; i < sz - 1; i++)
{
int flag = 1;
//用来判断数组是否有序,提高效率
int j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
//通过利用强转base为(char*)乘以宽度的多少来进行比较
if (cmp((char*)base+j*width,(char*)base+(j+1)*width)>0)
{
Swap((char*)base + j * width, ((char*)base + (j + 1) * width),width);
flag = 0;
}
}
if (flag == 1)
{
break;
}
}
}
void test()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
void test1()
{
struct Stu s[] = { {"zhangsan",1} ,{"lisi",2},{"wangwu",3} };
int sz = sizeof(s) / sizeof(s[0]);
//qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_age);
for (int i = 0; i < sz; i++)
{
printf("%d\n", s[i].age);
}
}
int main()
{
//test();
test1();
}
简单测试运行:
好了,关于其他排序的话这里就先不展开了,就先到这里结束了!文章来源:https://www.toymoban.com/news/detail-435320.html
🚀结语
好了,通过本篇博客我们刚开始看了两个有趣的代码,以及模拟实现简单计算器,认识到了什么是函数指针数组,以及使用的例子,还略微提及了指向函数指针数组的指针,以及最后的回调函数,以及后续通过回调函数而展开冒泡排序与qsort函数的实现!如果觉得还不错的话,多多支持哦!这次博客也到此结束了!>🌹文章来源地址https://www.toymoban.com/news/detail-435320.html
到了这里,关于【C语言】指针进阶知识终章的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!