怠惰是贫穷的制造厂。
目录
函数指针
一个函数总是占用一段连续的内存区域,函数名在表达式中可以被转换成所在区域的首地址,我们可以把函数的这个首地址赋予一个指针变量,使其指向函数所在的内存区域,然后通过指针变量就可以找到并调用这个函数,这种指针就是函数指针。
1.函数指针的定义形式:
returnType (*pointerName) (param list);
返回类型(*指针名称)(函数参数)
eg:void (*p) (int)
注:()优先级高于* ,第一个括号不可省略
&函数名和 函数名,都是函数地址,这点与数组不同。
2.函数的调用
3.解析两段有趣的代码:
1.
(*(void (*)() ) 0)( ) ;
2.
void( *signal ( int , void(*)(int) ) ) (int);
解析:首先,在一个变量的定义形式中,我们去掉它的变量名,剩下的就是它的类型。例如:
int a; ----- a 是int型
char ch;----ch是char型
int* b;-----b是int*型,即b是个指针变量;
int *arr[10]; -----arr是int* [10]型,即arr是个指针数组
int (*p)[10];------p是int (*)[10]型,即p是一个数组指针
void (*p)();----------p是void(*)()型,即p是一个函数指针
1.
2.
代码二实在是太复杂,我们可以简化一下
我们前边说过,signal的函数返回类型为void(*)(int),这个太长读起来有些难懂,我们就把它类型重定义一下;
你可能觉得它应该是长这个样子的,
typedef void(*)(int) pfun_t;
很遗憾,不是噢,这个新的函数类型名字需要紧跟这星号*,是这个样子滴
typedef void(*pfun_t)(int);
然后,这段冗长的代码就可以改成:
pfun_t signal(int, pfun_t);
函数指针数组
1.定义形式
数组是一个存放相同类型数据的存储空间,我们已经学习过了指针数组:
int *arr[10];数组的每个元素是int*,是个指针
那我们如果要把几个函数的地址存在一个数组里,那这个数组就叫做函数指针数组。定义形式如下:
int (* arr[7]) ( int );
arr先和[ ]结合,表明是一个数组,数组有7个元素,每个元素都是int(*)()即函数指针类型。
2.函数指针数组的用途
那他到底有什么用呢?
我们先用一段代码来实现一下计算器:
#include<stdio.h>
void menu()
{
printf("*********0.exit*************\n");
printf("***1.add*******2.sub********\n");
printf("***3.mul*******4.div********\n");
printf("****************************\n");
printf("****************************\n");
}
int add(int a, int b)
{
int z = a + b;
return z;
}
int sub(int a, int b)
{
int z = a - b;
return z;
}
int mul(int a, int b)
{
int z = a * b;
return z;
}
int div(int a, int b)
{
int z = a / b;
return z;
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
do
{
menu();
printf("请选择->");
scanf("%d", &input);
switch (input)
{
case 0:
printf("退出");
break;
case 1:
printf("请输入你的操作数");
scanf("%d%d", &x, &y);
printf("%d\n",add(x,y));
break;
case 2:
printf("请输入你的操作数");
scanf("%d%d", &x, &y);
printf("%d\n", sub(x, y));
break;
case 3:
printf("请输入你的操作数");
scanf("%d%d", &x, &y);
printf("%d\n", mul(x, y));
break;
case 4:
printf("请输入你的操作数");
scanf("%d%d", &x, &y);
printf("%d\n", div(x, y));
break;
default:
printf("输入错误");
}
} while (input);
return 0;
}
利用回调函数(后边会讲),做一下优化
void menu()
{
printf("*********************************\n");
printf("***1.add********2.sub************\n");
printf("***3.mul********4.div************\n");
printf("*********0.exit******************\n");
printf("*********************************\n");
printf("*********************************\n");
}
int add(int x, int y)
{
int z = 0;
return z = x + y;
}
int sub(int x, int y)
{
int z = 0;
return z = x - y;
}
int mul(int x, int y)
{
int z = 0;
return z = x * y;
}
int div(int x, int y)
{
int z = 0;
return z = x / y;
}
void caluc(int(*p)(int,int))
{
int x = 0;
int y = 0;
printf("请输入您的数字");
scanf("%d%d", &x, &y);
printf("%d\n", p(x, y));
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 0:printf("退出\n");
break;
case 1:
caluc(add);
break;
case 2:
caluc(sub);
break;
case 3:
caluc(mul);
break;
case 4:
caluc(div);
break;
default:
printf("输入错误\n");
break;
}
} while (input);
return 0;
}
我们也可以运用函数指针数组来实现:
void menu()
{
printf("*********0.exit*************\n");
printf("***1.add*******2.sub********\n");
printf("***3.mul*******4.div********\n");
printf("****************************\n");
printf("****************************\n");
}
int add(int a, int b)
{
int z = a + b;
return z;
}
int sub(int a, int b)
{
int z = a - b;
return z;
}
int mul(int a, int b)
{
int z = a * b;
return z;
}
int div(int a, int b)
{
int z = a / b;
return z;
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
int(*parr[5])(int, int) = {0,add,sub,mul,div};
do
{
menu();
printf("请选择->");
scanf("%d", &input);
if (input >= 1 && input <= 4)
{
printf("请输入操作数:");
scanf("%d%d", &x, &y);
printf("%d\n", parr[input](x, y));
}
else if (0 == input)
{
printf("退出\n");
}
else
{
printf("录入有误\n");
}
} while (input);
return 0;
}
函数指针数组的用途:转移表。
指向函数指针数组的指针
指向函数指针数组的指针是一个指针,指针指向一个数组,数组的元素都是函数指针。那是如何定义的呢?
void(*p)(int ,int);-----------这是一个函数指针
void(*parr[4])(int ,int);---------这是一个函数指针数组;
void(*(*pparr)[4])(int ,int);---------这是一个指向函数指针数组的指针;
回调函数
回调函数是一个通过函数指针调用的函数,如果你把函数的地址作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
最著名的回调函数调用有C/C++标准库stdlib.h/cstdlib中的快速排序函数qsort和二分查找函数bsearch中都会要求的一个与strcmp类似的参数,用于设置数据的比较方法。
1.qsort函数
首先,我们先了解一下qsort函数:
qsort 是qsort函数C语言编译器函数库自带的排序函数。
函数声明: void qsort (void* base, size_t num, size_t size, int (*compar)(const void*,const void*));
base:要进行排序数组的首元素地址
num:数组元素个数
size:数组每个元素的大小,以字节为单位
compar:函数指针,指向一个比较两个元素的函数,这个函数使用者自己实现
compar参数
int compar(const void* p1,const void*p2)
Compare 函数的返回值
描述
< 0
p1将被排在p2前面
0
p1 等于 p2
> 0
p1 将被排在p2后面
补充:关于void:
1.void* 类型指针能接受任何类型的地址
2.void*类型指针不能进行解引用
3.void*类型指针不能进行+-加减整数操作
2.qsort函数的应用:
1.对浮点型数据进行排序
int float_cmp(const void*p1, const void* p2)
{
return( int )(*(float*)p1 - *(float*)p2);
}
int main()
{
float arr[] = { 9.0, 8.0, 7.0, 6.0, 5.0, 4.0 };
int sz =sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz,sizeof(float), float_cmp);
for (int i = 0; i < sz; i++)
{
printf("%lf ", arr[i]);
}
return 0;
}
2,对整型进行排序
int int_cmp(const void*p1, const void*p2)
{
return (*(int*)p1 - *(int*)p2);
}
int main()
{
int i = 0;
int arr[] = { 9, 8, 7, 6, 5, 4, 3, 2, 10 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr,sz,sizeof(int),int_cmp);
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
3.对结构体进行排序
struct stu
{
char name[20];
int age;
};
int struct_name_cmp(const void*p1, const void*p2)
{
return strcmp( ( (struct stu*)p1 )->name,( (struct stu* )p2) ->name);
}
int struct_age_cmp(const void*p1, const void*p2)
{
return ((struct stu*)p1)->age - ((struct stu*)p2)->age;
}
int main()
{
struct stu class1[3] = { { "zhangsan", 20 }, { "lisi", 33 }, { "wangwu", 67 } };
int sz = sizeof(class1) / sizeof(class1[0]);
qsort(class1, sz, sizeof(class1[0]), struct_name_cmp);
qsort(class1, sz, sizeof(class1[0]), struct_age_cmp);
return 0;
}
1).原序列
2)按名字排序
3)按年龄排序
总结;
我们把cmp这个回调函数的地址传给了qsort函数,实现了数列排序中两个元素大小的比较。使用qsort函数进行排序,要比我们之前所采用的冒泡排序适用范围更广。我们之前采用的只能对整型数组进行排序,具有局限性。
冒泡排序(只能对整型进行排序)
void bubble_sort(int arr[], int sz)
{
int i = 0;//趟数
for (i = 0; i < sz - 1; i++)
{
int j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
int tmp = 0;
if (arr[j]>arr[j + 1])
{
tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
int main()
{
int arr[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1 };
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;
}
3.模拟实现qsort(采用冒泡排序的方式)
int int_cmp(const void*p1, const void*p2)
{
return (*(int*)p1 - *(int*)p2);
}
int float_cmp(const void*p1, const void* p2)
{
return( int )(*(float*)p1 - *(float*)p2);
}
struct stu
{
char name[20];
int age;
};
int struct_name_cmp(const void*p1, const void*p2)
{
return strcmp( ( (struct stu*)p1 )->name,( (struct stu* )p2) ->name);
}
int struct_age_cmp(const void*p1, const void*p2)
{
return ((struct stu*)p1)->age - ((struct stu*)p2)->age;
}
swap(char* p1,char* p2,int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *p1;
*p1 = *p2;
*p2 = tmp;
p1++;
p2++;
}
}
void bubble(void* base, int count,int width, void*(*cmp)(void*,void*))
{
int i = 0;
//趟数
for (i = 0; i < count - 1; i++)
{
int j = 0;
//每一趟需要比较的对数
for (j = 0; j < count - 1 - i; j++)
{
//调用函数,比较两个元素大小
if (cmp( (char*)base+j*width, (char*)base + (j+1)*width )>0)
{
//交换两个元素
swap((char*)base + j*width, (char*)base + (j + 1)*width,width );
}
}
}
}
void test2()
{
struct stu s[3] = { {"zhangsan",20},{"lisi",19},{"wangwu",77} };
int sz = sizeof(s) / sizeof(s[0]);
bubble(s, sz, sizeof(s[0]), struct_name_cmp);
}
void test1()
{
int arr[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble(arr, sz, sizeof(int), int_cmp);
for (int i = 0; i < sz; i++)
{
printf("%d ",arr[i]);
}
}
int main()
{
//test1();
test2();
return 0;
}