1.函数指针数组
我们学了整型数组,字符数组,指针数组,我们还学过整型指针,字符指针,函数指针,数组指针而今天我们要学函数指针数组属于是多重buff叠一起了。 那今天就来学习一下。
指针数组是这样的 int *arr[10],函数指针是这样的char *test(int n,char* s)那要是把函数地址写道数组中就是函数指针数组了。那怎么来定义呢?
这里有三个情况:
int (*parr1[3])();
int *parr2[3]();
int (*)() parr3[3];
我们可以来看看,结果呢就是parr1,parr1先和[ ]结合,说明parr1是数组,那数组的内容是什么呢?对啦就是int (*)()类型的函数指针。这个函数指针数组空括号里是函数指针的参数。
我们给一个函数指针数组的代码:char *(*pfArr[4])(int,char*),这里我们还可以拓展一下:
那p怎么去书写呢?
char*(*(*p)[4])(int,char*) = &pfArr p在括号里面放了一个 * 就说明p是指针,然后加上[4]就说明p指向的是数组,有4个元素。这里的p就是是指向函数指针数组的指针,那p指向的4个数组的元素他类型是什么呢?
char*(*)(int,char*) = &pfArr
2.转移表
那学会函数指针数组之后有什么用呢?接下来就是使用时刻。
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf(" 0:exit \n");
printf("*************************\n");
printf(" ");
scanf("%d", &input);
switch (input)
{
case 1:
printf(" ");
scanf("%d %d", &x, &y);
ret = add(x, y);
printf("ret = %d\n", ret);
break;
case 2:
printf(" ");
scanf("%d %d", &x, &y);
ret = sub(x, y);
printf("ret = %d\n", ret);
break;
case 3:
printf(" ");
scanf("%d %d", &x, &y);
ret = mul(x, y);
printf("ret = %d\n", ret);
break;
case 4:
printf(" ");
scanf("%d %d", &x, &y);
ret = div(x, y);
printf("ret = %d\n", ret);
break;
case 0:
printf(" \n");
break;
default:
printf(" \n");
break;
}
} while (input);
return 0;
}
我们可以看一下这串代码,在case1234中你不断地在调用前面的函数,而且很多都是重复的代码是不是感觉很麻烦,不简明,所以我们利用今天所学来解决一下。
下面的代码的转移表就是用函数指针数组来完成的。转移表顾名思义就是充当一个跳板。
#include <stdio.h>//首先呢这里还是正常的定义函数
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a*b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
//创建一个函数指针数组,(这里就用到了函数指针数组)这个数组中都是函数指针,都是地址。
int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
//这里的的数组为什么是五个元素呢?因为你要对应下面的菜单中的数字加上一个0之后,add下标就是1,在依次是234
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf(" 0:exit \n");
printf("*************************\n");
printf( "请选择 " );
scanf("%d", &input);
if ((input <= 4 && input >= 1))
{
printf( " 输入操作数:" );
scanf( "%d %d", &x, &y);
ret = (p[input])(x, y);//这里的input正好对应转移表的下标,x和y是计算的两个参数
printf( "ret = %d\n", ret);
}
else if(input == 0)
{
printf("退出计算机 \n");
}
else
{
printf( " 输入有误\n" );
}
}while (input);
return 0;
}
3.回调函数
在进行这节的讲解,还会用到上述的代码,但是呢,我们要对最原始的代码,使用函数指针进行简化。代码如下:
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a*b;
}
int div(int a, int b)
{
return a / b;
}
void Calc(int (*pf)(int,int))//这里便采用了函数指针,
{
int a = 0;
int b = 0;
int ret = 0;
printf("请输入2个操作数:");
scanf( "%d %d", &x, &y);
ret = pf(x, y);
printf( "ret = %d\n", ret);
}
int main()
{
int input = 0;
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf(" 0:exit \n");
printf("*************************\n");
printf( "请选择 " );
scanf("%d", &input);
swith(input)
{
case 1:
Calc(Add);//我们可以看到在add sub mul div这四个函数中,都不是直接通过函数名调用函数,而都是用calc实现的。我们把这四个函数中的地址传给calc,然后再回头使用函数指针调用,这就是函数调用.就是回调了。
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;
}
这里我们用到了函数指针,代码中我已经标明了。函数指针的作用就是下面的两点:
参数化函数调用:函数指针可以作为参数传递给其他函数,使得被调用的函数能够根据需要调用不同的函数。
回调函数:函数指针常用于实现回调机制,即一个函数在完成某些操作后调用另一个作为参数传递给它的函数。
看到了没有,这就是回调函数。
4.qsort使用举例
qsort 是一个广泛使用的排序函数,它在 C 语言标准库中实现,并被许多其他编程语言的库所借鉴。qsort 是快速排序算法的一种实现,它对数组或列表中的数据进行原地排序,即不使用额外的存储空间。
下面是qsort函数的原型如下:
void*base是指向要排序的数组的第一个元素的指针。
size_t num是数组中元素的数量。
size_t size是数组中的每个元素的大小*(以字节为单位)
int (*compar)(const void*,const void*))是一个指向比较函数的指针,这个函数用于比较两个元素并返回它们的顺序。比较函数需要接受两个指向元素的指针,并返回一个整数,表示第一个元素与第二个元素 的相对顺序。
下图就是他们的比较的返回值对应的意义:
那下面就用qsort练习一下:
4.1使用qsort函数排序整型数据
#include <stdio.h>
//qosrt函数的使用者得实现一个比较函数
int int_cmp(const void * p1, const void * p2)
{
return (*( int *)p1 - *(int *) p2);
}
int main()
{
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int i = 0;
qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
{
printf( "%d ", arr[i]);
}
printf("\n");
return 0;
}
4.2使用qsort排序结构数据
和个上面同样的道理
#include <stdio.h>
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;
}
//strcmp - 是库函数,是专门用来比较的两个字符串的大小的
//假设按照名字进行比较
int cmp_stu_by_name(const void* e1, const void* e2)
{
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
//按照年龄来排序
void test2()
{
struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
}
//按照名字来排序
void test3()
{
struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}
int main()
{
test2();
test3();
return 0;
}
在这串代码中使用了 ->操作符在C语言中,“->” 是一个运算符,它是成员访问运算符,用于指针访问结构体中的成员。当有一个结构体的指针时,你可以使用这个运算符来直接访问该结构体中的成员,而不需要先解引用指针。
5.qsort函数的模拟实现
使用回调函数,采用冒泡的方式,对qsort的模拟实现
注意:这里第一次使用void*的指针,讲解void*的作用(在代码内部进行讲解)
#include <stdio.h>
int int_cmp(const void* p1, const void* p2)
{
return (*(int*)p1 - *(int*)p2);//这里为什么要进行强制类型转换呢?因为void*类型的指针是无具体类型的指针,这种类型的指针不能直接进行解引用,也不能加减整数。
}
//这里的_swap汉书为什么给他传入width参数呢?因为想要交换只知道位置还不够,还要知道位置所在的宽度
void _swap(char*buf1,char*buf2,size_t width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf1= tmp;
buf1++;//这里为什么要++呢?因为你要进行两两的交换元素,这俩个交换完成之后,要进行下面两个的交换。直到完成交换的整体的宽度为止。
buf2++;
}
} //起始位置 元素个数 元素大小(宽度) 比较函数
void bubble(void* base, size_t sz,size_t width, int(*cmp)(const void*p1, const void*p2))
{
int i = 0;
int j = 0;
//趟数
for (i = 0; i < count - 1; i++)
{
//一趟内部的两两比较
for (j = 0; j < count - i - 1; j++)
{ //比较arr[j]和arr[j+1]
//要比较两个元素的大小就要找到两个元素的地址,base是起始地址,又是void*的指针,他没有办法加减整数去找到下一个元素,所以在这里进行强制类型转换,为什么要转换成char*因为加减整数就会加减一个字节,比较精确。
//这里的+j*width就是跳过的字节数,width就是每个元素的字节个数,就是宽度。而j就是跳过的元素个数
if (cmp((char*)base + j *width, (char*)base + (j + 1) * width) > 0)
{ //交换两个元素
_swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
}
}
}
}
int main()
{
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
bubble(arr, sz, sizeof(arr[0]), int_cmp);
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
为了使用回调函数模拟实现 qsort 函数对数组进行排序,我们将创建一个名为 bubble 的冒泡排序函数,该函数将接受一个回调函数作为参数。这个回调函数将用于比较数组中的两个元素,并根据比较结果决定是否交换它们的位置。
既然是模拟qsort函数,我们定义的bubble(气泡的意思)函数肯定要在参数上类似。
arr 是指向要排序的数组的第一个元素的指针。
sizeof(arr)/sizeof(arr[0]) 是数组中元素的数量。
sizeof(int) 是数组中的每个元素的大小*(以字节为单位)
int (*compar)(const void*,const void*)) 是一个指向比较函数的指针,这个函数用于比较两个元素并返回它们的顺序。比较函数需要接受两个指向元素的指针,并返回一个整数,表示第一个元素与第二个元素 的相对顺序。
在这个函数中,int_cmp是一个指向比较函数的指针,这个函数用于比较两个元素并返回它们的顺序。比较函数需要接受两个指向元素的指针,并返回一个整数,表示第一个元素与第二个元素 的相对顺序。
int_cmp的定义就是这样
讲完模拟qsort函数的参数,我们再来讲讲排序的趟数和每趟的次数。在之前没有讲qsort时候,我们看到的排序是这样
但是现在为什么换成了这样?因为 上面的方法只适合整型之间的排序,我们如果想把它改造成能够排序任意类型的函数,就是下面的样子。
把两个元素比较的方法,封装成函数然后把函数的地址传给排序函数。就是下图中的
上面的解释只是讲了个调用的大概,关于精确的函数比较和交换的解释,在上述的代码中就可以找到。