目录
一.回调函数是什么?
函数指针到底有什么用呢?-- 把函数地址取出来, 又通过地址调用这个函数,为啥不直接函数名调用?函数指针可以用来实现回调函数。
代码举例:
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 menu()
{
printf("******************************\n");
printf("********* 1.Add 2.Sub ********\n");
printf("********* 3.Mul 4.Div ********\n");
printf("********** 0.exit ********\n");
printf("******************************\n");
}
int main()
{
int input = 0;
int ret = 0;
int x = 0;
int y = 0;
do
{
menu();
printf("请选择\n");
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;
}
相同(相似)的代码出现了多份显得冗余。可以用回调函数的方式优化!
要是把case1 2 3 4,分别分装成4个函数代码实现功能,函数里面的代码还是重复冗余的。
回调函数的实现(优化)
代码举例
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 menu()
{
printf("******************************\n");
printf("********* 1.Add 2.Sub ********\n");
printf("********* 3.Mul 4.Div ********\n");
printf("********** 0.exit ********\n");
printf("******************************\n");
}
//回调函数实现,核心部分
//calc功能变强大了。
//Add,Sub,Mul,Div只能实现1个功能。
//calc通过传递的参数不一样实现的的功能也不一样,可以实现他们所有功能
void calc(int(*pf)(int, int))
{
int ret = 0;
int x = 0;
int y = 0;
printf("请输入2个操作数:");
scanf("%d %d", &x, &y);
ret = pf(x, y);//函数调用
printf("%d\n", ret);
}
//1.分装一个函数calc,传递你要实现功能的函数地址。
//2.函数地址要用函数指针变量接收。
// 还要写变量指向函数的形参。
//3.再通过函数指针变量pf,调用它所指向的函数参数。
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 3:
calc(Mul);
break;
case 4:
calc(Div);
case 0:
printf("退出计算器\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
实现方式:
进入main函数,再选择进入case1,调用calc函数,里面参数是Add的地址,通过pf调用的时候,就是调用Add的函数。
回调函数的理解:
1.回调函数就是⼀个通过函数指针调⽤的函数。
2.如果你把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被⽤来调⽤其所指向的函数时,被调⽤的函数就是回调函数。回调函数不是由该函数的实现⽅直接调⽤,⽽是通过另⼀个函数,函数指针调用实现的。
总结:
1.实现功能的4个函数参数的类型相同,返回类型也相同,就可以设置一个类型相同的函数指针。
就可以以回调函数方式实现,以减少重复冗余代码。
2.这些实现功能的函数不是直接调用的,而是通过指函数针变量调用的。通过pf调用Add的时候,就被称为回调函数。也可也理解为,被函数指针调用的函数是回调函数。
3.就是把函数地址传递给其他函数,用函数指针变量调用,实现功能。
4.Add,Sub,Mul,Div只能实现1个功能。
calc通过传递的参数不一样实现的的功能也不一样,可以实现他们所有功能
5.得慢慢感悟。
二.qsort使⽤举例
函数指针的使用:qsort
qsort是库函数,这个函数可以完成任意类型数据的排序,#include <stdlib.h>
代码举例:先用冒泡排序
//冒泡排序:这个函数只能排序整形数据
void fufu1(int* arr, int sz)
{
int flag = 1;
int i = 0;
for (i = 0; i < sz - 1; i++)
{
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;
}
}
}
void fufu2(int* arr, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int main()
{
int arr[] = { 5, 1, 2, 3, 8, 9, 7, 6, 2, 4 };
int sz = sizeof(arr) / sizeof(arr[0]);
fufu2(arr, sz);
fufu1(arr, sz);
fufu2(arr, sz);
return 0;
}
1.认识qsort函数4个参数
认识qsort函数的返回类型和4个参数
void qsort(
void* base,//base,指向了要排序的数组的第一个元素,viod*接收任何类型元素
size_t num,//base,指向的数组在的元素个数,(待排序的数组的元素个数)
size_t size,//base,指向的数组中元素的大小(单位是字节)
int(*compar)(const void* p1, const void* p2)//函数指针 - 指针指向的函数是用来比较数组的2个元素的
compar - 比较的p1指向的元素和p2指向的元素进行比较,返回的值是int类型。
p1 < p2; 返回 <0 的数;
p1 = p2; 返回 0 的数;
p1 > p2; 返回 >0 的数;
//两个整形元素比较大小直接可以是> < =
//但是两个结构体的数据不能直接用> < =
compar - 这个函数指针,需要我们程序员自己提供个函数实现判断2个数比较。
自己提供的函数参数和返回类型得跟函数指针保持一致!
);
2. 1qsort 的使用
代码举例:整形数据
void fufu2(int* arr, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
//cmp_int 这个函数是用来比较p1和p2指向的元素的大小
int cmp_int(const void* p1, const void* p2)
{
//1.我们要用p1和p2比较大小,要解引用
//2.void*类型的指针不能直接解引用
//3.p1和p3指针变量要强制类型转换,再解引用
//4.用做差比较:
// p1 - p2 > 0;p1>p2
// p1 - p2 = 0;p1=p2
// p1 - p2 < 0;p1<p2
return *(int*)p1 - *(int*)p2;
}
void test1()
{
int arr[] = { 5, 1, 2, 3, 8, 9, 7, 6, 2, 4 };
int sz = sizeof(arr) / sizeof(arr[0]);
fufu2(arr, sz);
//qsort的4个传参 - 第4个参数得自己提供函数,是专门比较整形大小的。
qsort(arr, sz, sizeof(arr[0]), cmp_int);
//提供的函数返回类型和参数类型一致的时候,就能把地址传给指针了,指针就指向这个函数。
fufu2(arr, sz);
}
int main()
{
test1();
return 0;
}
1.首先我们要知道如何写qsort的4个参数,只需要传递相应的参数qsort函数就能自动帮我排序。
2.第4个参数是个函数指针,需要我们程序自己提供,并实现2个元素的比较大小。 我们自己提供的函数的返回类型和参数类型,要跟函数指针保持一致。提供的函数返回类型和参数类型一致的时候,就能把地址传给指针了,指针就指向这个函数。
3.自己提供的函数里面比较元素大小的实现:
//两个整形元素比较大小直接可以是> < =
//但是两个结构体的数据不能直接用> < =
//参数是void*类型指针,要强制类型转换,解引用之后再比较大小。
4.cmp_int这个函数是使用者提供,我使用,我提供,降序只要逻辑相反就可以了
5.冒泡排序 和 qsort 排序不一样,qsort低层采用的是快速排序。
6.只需要会用就行。
三.使⽤qsort排序结构数据
1.按照年龄比较
代码举例:测试qsort排序结构体数据
struct Stu
{
char name[20];//名字
int age;
};
//怎么比较2个结果体数据? - 不能直接使用> < == 符号比较
//比较方法:
//1.可以按照名字比较
//2.可以按照年龄比较
//按照年龄比较
int cmp_stu_by_age(const void* p1, const void* p2)
{
//p1和p2分别指向结构体数据,遗憾的是类型是void*,得转换成结构体指针(struct stu*)。
return((struct Stu*)p1)->age - ((struct Stu*)p2)->age;//默认为升序,前面一个>后面一个
//return ((struct stu*)p2)->age - ((struct stu*)p1)->age; 降序,前面一个<后面一个
//强制转换之后的类型再->age,才会起作用,这里优先级低,所以要用括号。
//结构体指针访问它所指向对象的成员,不需要解引用,直接用箭头就行。仅限结构体指针
//(*(struct stu*)p1).age 通过点的方式找到成员
}
void test2()
{
//数组里面有3个元素,里面有名字,有年龄
struct Stu arr[] = { {"zhangsan",20},{"lisi",38},{"wangwu",18} };//数组初始化
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
}
int main()
{
test2();
return 0;
}
2.按照名字来比较
1.两个字符串不能用> < ==比较
2.而是使用库函数strcmp,#include <string.h>
专门用来2个字符串的。不是比较长度,而是比较内容。其实是按照字符ASCII码值的大小比较的。
abcdef < abq; 因为q的ASCLL码值大于c。
4.strcmp:int strcmp(const char* str1,const char* str2);
返回值是int,参数类型const char*
p1 - p2 > 0;p1>p2;返回值
p1 - p2 = 0;p1=p2
p1 - p2 < 0;p1<p2
代码举例:
struct Stu
{
char name[20];//名字
int age;
};
int cmp_stu_by_name(const void* p1, const void* p2)
{
return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p1)->name);//默认为升序
}
void test2()
{
struct Stu arr[] = { {"zhangsan",20},{"lisi",38},{"wangwu",18} };//数组初始化
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%s %d\n", arr[i].name, arr[i].age);
}
}
int main()
{
test2();
return 0;
}
总结:
1.qsort确实可以排序任意类型的数据
2.学会qsort怎样使用
3.qsort在使用的时候,需要使用者传递一个函数的地址
这个函数用来比较待排序的数组中的2个元素
按照参数和返回值的要求实现就行
4.冒泡函数只能比较2个整数,只能用大于符号比
对于不同类型数据来比,比较方法是有所差异的
而是通过参数形式传进来,函数指针来比较。
四. qsort函数的模拟实现
1.qosrt函数参数深入理解
改造冒泡排序排序任意数据前提:还是使用冒泡排序
1.待排序的趟数不会变。元素个数-1
2.每一趟冒泡排序的对数也不会发生变化,不管什么类型数据 都是两两比较。元素个数 -1-i
3.比较的地方要改造,参数和2个元素值的交换
再次理解qsort返回值
{
qsort 是一个库函数,可以直接使用。
qsort 的实现是使用快速排序算法来排序的。
}
{
qsort参数深入理解(1)
void qsort(
void* base,//待排序数组的起始位置 - 你要排序的数组告诉我起始位置
size_t num,//待排序数组的元素个数 - 你要排序的数组几个元素
size_t size,//待排序的数组的元素大小
int(*compar)(const void* p1, const void* p2)//函数指针
//该函数指针指向的是个函数。
//指针指向的函数是用来比较待排序数组中的两个元素的。
//p1指向一个元素,p2也指向一个元素 -- {*compar)这个指针就是比较p1和p2指向的函数
}
{
qsort参数深入理解(2)(参数解析)
void qsort(
void* base,//待排序数组的起始位置 - 因为qsort可能排序任意类型的数据,为了能够接收任意的指针类型,设计成void*类型
size_t num,//待排序数组的元素个数 - 无符号整数,因为元素个数不可能是负数,所以设计成size_t类型
size_t size,//待排序的数组的元素大小 - 无符号整数,因为我不知道类型,但我们传递的大小计算。
int(*compar)(const void* p1, const void* p2)//提供比较函数,用指针调用,不知道比较什么类型数据,类型写成const void*
}
代码举例:(代码笔记,看起有点乱,从main函数推)
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void bubble_sprt1(int arr[], int sz)//只能排序整形,因为参数是整形要是传递浮点型,字符型不行。
//3.函数参数也要重新设计,因为这是2个整形,只能接收整形。
{
int i = 0;
//趟数
for (i = 0; i < sz - 1; i++)//不变
{
//每一趟冒泡排序的过程
int j = 0;
for (j = 0; j < sz - 1 - i; j++)//不变
{
if (arr[j] > arr[j + 1])//1.比较的地方要改造,因为字符类型或者结构体类型数据不能用> < =,把比较的方法写个
//函数,作为参数,写成函数指针形式,专门比较不同类型的数据。- 这就是回调函数方式
{
int tmp = arr[j];
arr[j] = arr[j + 1];//2.交换的代码也得改,因为这次交换的2个整形,下次可能2个交换结构体。
arr[j + 1] = tmp;
}
}
}
}
void print_arr(const int* arr, const int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
void test1()
{
//想排序一组整形数据 - 升序
int arr[] = { 3,1,5,7,9,2,4,0,8,6 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sprt1(arr, sz);
print_arr(arr, sz);
}
int cmp_int(const void* p1, const void* p2)
{
return *(int*)p1 - *(int*)p2;
}
//void test2()
//{
// //想排序一组整形数据 - 升序
// int arr[] = { 3,1,5,7,9,2,4,0,8,6 };
// int sz = sizeof(arr) / sizeof(arr[0]);
// qsort(arr, sz, sizeof(arr[0]), cmp_int)
// /*{
// qsort 是一个库函数,可以直接使用。
// qsort 的实现是使用快速排序算法来排序的。
// }*/
// //{
// // qsort参数深入理解(1)
// // void qsort(
// // void* base,//待排序数组的起始位置 - 你要排序的数组告诉我起始位置
// // size_t num,//待排序数组的元素个数 - 你要排序的数组几个元素
// // size_t size,//待排序的数组的元素大小
// // int(*compar)(const void* p1, const void* p2)//函数指针
// // //该函数指针指向的是个函数。
// // //指针指向的函数是用来比较待排序数组中的两个元素的。
// // //p1指向一个元素,p2也指向一个元素 -- {*compar)这个指针就是比较p1和p2指向的函数
// //}
// print_arr(arr,sz);
//}
//用自己写的冒泡排序,排序任意类型数据
//{
// qsort参数深入理解(2)(参数解析)
// void qsort(
// void* base,//待排序数组的起始位置 - 因为qsort可能排序任意类型的数据,为了能够接收任意的指针类型,设计成void*类型
// size_t num,//待排序数组的元素个数 - 无符号整数,因为元素个数不可能是负数,所以设计成size_t类型
// size_t size,//待排序的数组的元素大小 - 无符号整数,因为我不知道类型,但我们传递的大小计算。
// int(*compar)(const void* p1, const void* p2)//提供比较函数,用指针调用,不知道比较什么类型数据,类型写成const void*
//}
void Swap(char* buf1, char* buf2, size_t width)//一个字节一个字节交换,字节大小是几,就交换几对
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void bubble_sprt2(void* base, size_t sz, size_t width, int (*cmp)(const void* p1, const void* p2))//1.先改造参数,参考qsort函数参数,思考它里面的参数。
// 起始位置 元素个数 元素大小 函数指针参数 不知道具体什么类型
{
int i = 0;
//趟数
for (i = 0; i < sz - 1; i++)//不变
{
//每一趟冒泡排序的过程
int j = 0;
for (j = 0; j < sz - 1 - i; j++)//不变
{
//if (arr[j] > arr[j + 1])2.改造比较地方,拿到下标为j和j+1的地址,传个cmp
//1.把base转换成char*类型,(char*)+j*width - 相当拿到起始位置 - 就是j的地址
//2.(char*)+(j+1)*width - 相当拿到起始位置 - 就是j+1的地址
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)//调用cmp函数,cmp的返回值>0就交换
{
/*int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;*/
//3.改造交换 - p1 和 p2指向的元素进行交换,所以要知道2个元素起始位置 - 还要知道元素大小
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);//Swap这个函数进行交换
}
}
}
}
void test3()
{
//设计和实现bubble_sort2(),这个函数能够排序任意类型的数据
int arr[] = { 3,1,5,7,9,2,4,0,8,6 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sprt2(arr, sz, sizeof(arr[0]), cmp_int);//bubble_sort2这个函数排序任意内省的数据
print_arr(arr, sz);
}
int main()
{
//test1();
//test2();
test3();//探究qsort的底层原理,怎么做到排序任意类型数据的。
return 0;
}
梳理:
1. 一个数组,里面有10个元素,传给冒泡函数2(元素起始位置,元素个数,元素大小,实现比较2个元素比较大小的函数)
2. 调用冒泡函数2,形参(元素个数,元素大小,函数指针-指向实现2个元素的大小的函数)
3.然后进行排序,趟数不变,每一趟的过程不变
4.变的是判断部分,因为只直到数组的大小和首元素的位置,所以把数组void*指针强制转换成char*指针,
再首元素地址+j * 元素大小
再首元素地址 +( j+1) * 元素大小
才能找到2个元素的地址 - 传给函数cmp - (*cmp)函数指针指向实现2个元素比较大小的函数
5.返回值>0就交换
6.实现一个交换函数,实参是2个元素的地址,和元素的大小。元素大小是几,就交换几次。
总结:
1.主要就是函数指针的调用。
2.通过一个函数,再用另一个函数作为参数,实现比较的功能,再用指针变量接收地址,当调用这个函数指针,就调用实现功能的函数。
3.当我们直到元素首地址,也知道元素大小,我们可以把元素指针强制类型转换成char*类型计算,或者char类型计算,就可以拿到想要元素的地址。
4.第4个函数,我们自己实现元素比较的函数被当成参数,用指针接收,只要调用指针就相当于调用了这个函数。
2.使用bubble_sort2函数来排序结构体
代码举例:
struct Stu
{
char name[20];
int age;
};
//字符排序方法:用到strcmp库函数
int cmp_stu_by_name(const void*p1, const void* p2)
{
return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
//正序排序方法2个元素相减
int cmp_stu_by_age(const void* p1, const void* p2)
{
return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
//正序排序方法
void Swap(char* buf1, char* buf2, size_t width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void bubble_sort2(void* base, size_t sz, size_t width, int (*cmp)(const void* p1, const void* p2))
{
int i = 0;
for (i = 0; i < sz - 1; i++)
{
int j = 0;
for (j = 0; j < sz - 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 test4()
{
struct Stu arr[] = { {"zhangsan",18},{"lisi",35},{"wangwu",15}};
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort2(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%s %d\n", arr[i].name, arr[i].age);
}
}
int main()
{
test4();
return 0;
}
总结:
1.用冒泡排序改变qsort这个函数只需要改变传参,2个元素的比较和2个元素值的交换。
2.对于各种各样的类型,都要void*指针转换成char*指针,方便计算得到2个元素的地址。//比较和交换值
3.第一个元素的地址 = (char*)类型首元素地址+j*元素大小。
第二个元素的地址 = (char*)类型首元素地址 +( j+1)* 元素大小。
4.交换值是一个字节一个字节的交换,所以要转换成char*,元素大小有多大就交换几次。//交换
5.设计好了这个函数,排序任意类型数据,我们每次也只需要修改排序的方法。
6.函数指针很重要:不同类型数据排序,2个元素比较的方法一定会有差异。 -- 所以就把2个元素比较方法抽离成函数,作为参数形参写成函数指针,用函数指针这个函数,就把参数传进来。