上一篇我们介绍了字符指针,指针数组和数组指针;今天我们先来简单回顾一下再开始新的内容
一、简单回顾
字符指针:字符类型的指针;可接收单个字符的地址,也可接收字符串的首地址
指针数组:是数组,是存放指针的数组;
数组指针:是指针,是指向数组的指针;可作为二维数组的地址的形参
二、函数指针
1.函数的地址
我们对函数指针和数组指针对比进行讲解:
数组指针:指向数组的指针,接收数组的地址
函数指针:指向函数的指针,接收函数的地址
数组的地址由&数组名来表示,那函数的地址怎么表示呢?答案是&函数名就是函数的地址,看以下代码:
#include<stdio.h>
int Add(int a, int b)
{
return a + b;
}
int main()
{
printf("%p", &Add);
return 0;
}
打印结果为:
可能有人会好奇,数组名是数组首元素的地址,那函数名是什么,是函数第一个参数的地址吗?当然不是,其实函数名也是函数的地址,我们加上&是为了方便理解,来看:
#include<stdio.h>
int Add(int a, int b)
{
return a + b;
}
int main()
{
printf("%p", &Add);
printf("%p", Add);
return 0;
}
打印结果为:
2.函数指针的表示
数组指针表示为:int (*p)[10],其实函数指针与数组指针的表示相似:int (*p)(int, int),最左边的int代表指针所指函数的返回类型,()里面的int代表函数的参数的类型
int Add(int a, int b)
{
return a + b;
}
int main()
{
int (*p)(int, int) = &Add;
int ret = (*p)(3, 5);
printf("%d\n", ret);
return 0;
}
打印结果为:
在以前写代码时:int ret = Add(3, 5),我们刚刚说过函数名也是函数的地址,指针也是地址,所以代码还可以写成:int ret = p(3, 5),不用加*,加上*同样也是方便我们对指针的理解。
3. 函数指针数组
根据指针数组可以对比知道:函数指针数组是数组,是用来保存函数指针的数组,那么函数指针数组该如何表示;其实很简单,这是函数指针:int (*p)(int, int),那么函数指针数组的表示就是 int (*p[数组长度])(int, int) 这样p就会先和[ ]结合成为数组,而去掉p[ ]之后int (* )(int, int) 就是这个数组所保存的函数指针的类型。
int (*p[数组长度])(int, int) 中第一个int代表函数指针所指函数的返回类型,括号中的连个int代表函数指针所指函数的参数类型,保存在函数指针数组中的函数指针所指向的函数的参数个数和类型必须相同
函数指针数组的用法:
先举一个简易加减计算器的例子:
#include<stdio.h>
//简易加减计算器
void menu()
{
printf("**************************\n");
printf("**** 1. 加法 ****\n");
printf("**** 2. 减法 ****\n");
printf("**** 0. 退出 ****\n");
printf("**************************\n");
}
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
int ret = 0;
int (*parr[3])(int, int) = { NULL, Add, Sub }; //1
do
{
menu(); //菜单函数
printf("请输入:>");
scanf("%d", &input);
if (input >= 1 && input <= 2)
{
printf("请输入两个整数\n");
scanf("%d %d", &x, &y);
ret = (*parr[input])(x, y); //2
printf("%d\n", ret);
}
else if (input == 0)
{
printf("已退出计算器\n");
}
else
{
printf("输入错误,请重新选择\n");
}
} while (input);
return 0;
}
在没学函数指针数组之前,我们会通过switch语句来实现这个功能,但是那样的话会使得代码冗长和出现大部分的代码重复,为了优化那种形式,我们采用函数指针数组来实现这个功能
代码中注释1处创建一个函数指针数组将两个函数的地址保存到该数组中,数组中的第一个元素是NULL是为了配合menu菜单函数使得用户输入相应的数字可以完成相应的功能
代码中注释2处(*parr[input])(x, y)中(x, y)是想函数传过去的两个参数,其中*parr中的*同样是为了方便对指针的理解,可以加也可以不加
除了这种方法优化以外,还可以使用回调函数的方法来优化:
三、 回调函数
概念:将函数A的地址传给函数B,函数B用一个函数指针来接收函数A的地址,之后在函数B中通过函数指针来调用这个函数A,此时函数A就叫回调函数
例1:加深理解回调函数
接下来,接着已简易加减计算器为例:
#include<stdio.h>
//简易加减计算器
void menu()
{
printf("**************************\n");
printf("**** 1. 加法 ****\n");
printf("**** 2. 减法 ****\n");
printf("**** 0. 退出 ****\n");
printf("**************************\n");
}
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
void calc(int (*pf)(int, int))
{
int x = 0;
int y = 0;
int ret = 0;
printf("请输入两个数:\n");
scanf("%d %d", &x, &y);
ret = pf(x, y);
printf("%d\n", ret);
}
int main()
{
int input = 0;
do
{
menu();
printf("请输入:>\n");
scanf("%d", &input);
switch (input)
{
case 1:
calc(Add);
break;
case 2:
calc(Sub);
break;
case 0:
printf("已退出计算器\n");
break;
default:
printf("输入错误,请重新选择\n");
}
} while (input);
return 0;
}
在代码中采用了switch语句,假如input我们输入的是1,那么进入第一个case关键字后,将Add函数的地址传给了calc函数中,calc函数用函数指针pf来接收Add函数的地址,之后再在函数中通过函数指针来调用Add函数,此时Add函数就是回调函数
例2:qsort函数讲解
1>冒泡排序
在qsort之前先来一个冒泡排序的代码:
#include<stdio.h>
void bubble_sort(int* pa, int sz)
{
int i = 0;
int j = 0;
int tmp = 0;
for (i = sz - 1; i > 0; i--)
{
for (j = 0; j < i; j++)
{
if (pa[j] > pa[j+1])
{
tmp = pa[j];
pa[j] = pa[j + 1];
pa[j + 1] = tmp;
}
}
}
}
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);
return 0;
}
代码缺陷:这段代码只适用于整形数组,如果改为浮点型数组,字符型数组甚至是结构体数组,这段代码就会失效,那么为了能够对任何类型的数组进行排序,我们使用qsort函数
2>qsort函数:排序整形数据
qsort函数是什么:qsort是一个库函数,其所需的头文件为 #include<stdlib.h>,它可以用来对任意类型的数据进行排序
qsort函数原型解释:
从图中可以看出qsort函数一共有4个参数:
void* base :是待排序数组的首元素地址
size_t num:是待排序数组的元素个数,也就是数组长度
size_t size:是待排序数组中一个元素所占内存的大小,直接用sizeof(arr[0])即可
最后一个参数等下单独讲解,先来说一下void*是什么:
void*类型的指针用来接收任意类型数据的地址,它不能进行解引用和 + - 操作
最后一个参数:
int (*compar) (const void*, const void*):是一个函数指针,函数指针compar指向一个函数,这个函数需要自己写,其功能就是比较待排序数组的两个元素
int (*compar) (const void* a, const void* b):
当a>b时,返回一个大于0的数;
当a=b时,返回0;
当a<b时,返回一个小于0的数;
具体实例如下:
#include<stdio.h>
#include<stdlib.h>
int cmp_int(const void* a, const void* b)
{
return *(int*)a - *(int*)b;
}
int main()
{
int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
return 0;
}
数组的首元素地址:arr,数组的元素个数:sz,数组中一个元素所占内存大小:sizeof(arr)
函数指针(地址):cmp_int,cmp_int赋值给int (*compar) (const void*, const void*),也就是compar这个函数指针指向cmp_int这个函数的地址
由于void*类型的指针不能进行解引用操作,所以先将其强制类型转换为int*,再进行解引用操作,这样就完成了和整形数据冒泡排序的升序一样的功能
3>qsort函数:排序结构体数据
当要对一个结构体数组进行排序时,不能使用简单的加减来比较结构体数据,如图中的学生结构体,我们可以根据名字或年龄这其中的一项进行比较,具体操作如下:
struct Stu
{
char name[20];
int age;
};
int cmp_struct(const void* a, const void* b)
{
return ((struct Stu*)a)->age - ((struct Stu*)b)->age;
}
int main()
{
struct Stu arr[] = { {"Frisk", 8}, {"Papyrus", 20}, {"Sans", 16}};
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_struct);
return 0;
}
这是根据年龄进行排序的,将指针变量a和b强制类型转换为struct Stu*类型,通过->访问age,将两者的差值作为返回值返回即可
若要根据名字进行排序,将age改为name,要注意的是name是字符串,字符串的比较要使用strcmp函数:
struct Stu
{
char name[20];
int age;
};
int cmp_struct(const void* a, const void* b)
{
return strcmp(((struct Stu*)a)->name, ((struct Stu*)b)->name);
}
int main()
{
struct Stu arr[] = { {"Frisk", 8}, {"Papyrus", 20}, {"Sans", 16}};
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_struct);
return 0;
}