指针(4)回调函数和qsort函数的实现

目录

一.回调函数是什么?

二.qsort使⽤举例 

1.认识qsort函数4个参数 

2. 1qsort 的使用

三.使⽤qsort排序结构数据

1.按照年龄比较

2.按照名字来比较

四. qsort函数的模拟实现  

1.qosrt函数参数深入理解

2.使用bubble_sort2函数来排序结构体


一.回调函数是什么?

函数指针到底有什么用呢?-- 把函数地址取出来, 又通过地址调用这个函数,为啥不直接函数名调用?函数指针可以用来实现回调函数。

代码举例:

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个元素比较方法抽离成函数,作为参数形参写成函数指针,用函数指针这个函数,就把参数传进来。 
 

  • 27
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值