指针详解 冒泡排序 qsort(三)

目录

模拟计算器案例

​编辑

1、回调函数

2、转移表

冒泡排序

strcmp函数

​编辑

qsort函数

测试qsort函数排序整型数据

***通过qsort实现冒泡排序***(重点)

(为什么要用强制转换:因为void*类型是方便输入的数据为任意类型,进入后不是int型无法计算,强制类型转换后才可以进行运算)

测试qsort函数排序结构体数据


 
初识指针(笔记)-CSDN博客

指针详解(笔记)-CSDN博客

指针详解(二)-CSDN博客

模拟计算器案例

//使用回调函数改造前​
/*
实现一个计算器
这个计算器可以实现整数的加减乘除
*/

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)
{
	if (y == 0) {
		printf("除数不能为0\n");
		return -1;
	}
	else {
		return x / y;
	}

}

void menu()
{
	printf("*************************\n");
	printf("****  1.add   3.sub  ****\n");
	printf("****  3.mul   4.div  ****\n");
	printf("****  0.exit         ****\n");
	printf("*************************\n");
}

int main()
{
	int x, y;
	int input = 1;
	int ret = 0;
	do
	{
		menu();
		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;
}

指针详解 冒泡排序 qsort(三)_第1张图片

这是一个基本的计算器程序,可以实现整数的加减乘除和退出功能。程序使用了一个简单的菜单驱动方式,让用户可以通过输入数字来选择要执行的操作。但是有大量的代码复用,在当前的代码中,加、减、乘、除的操作都是类似的,但是代码却是重复的。如果能够将这些操作封装到一个函数中,并通过参数来区分不同的操作,那么代码就会更加简洁和易于维护。
解决这些问题的思路如下:

1、使用转移表

2、使用回调函数

1、回调函数

指针详解 冒泡排序 qsort(三)_第2张图片

指针详解 冒泡排序 qsort(三)_第3张图片

回调函数是什么?
回调函数就是一个通过函数指针调用的函数。
如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,被调用的函数就是回调函数。回调函数不是由该函数的实现方直接调用,而是在

特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

相同(相似)的代码出现了多份,就显得有些冗余,有没有办法,简化一些呢?
我们可以把调用的函数的地址以参数的形式传递过去,使用函数指针接收,函数指针指向什么函数就调用什么函数,这里其实使用的就是回调函数的功能。

回调函数改造思路:

1、定义回调函数,该函数接受两个整数参数并返回一个整数。
2、在主函数中,创建一个数组,其中包含所有可能的操作符和对应的回调函数。
3、根据用户输入的操作符,查找相应的回调函数并调用它。
4、将结果存储在一个变量中,并将其打印出来。

指针详解 冒泡排序 qsort(三)_第4张图片

1、先定义一个函数calc,这个函数接受一个函数指针pf作为参数。
2、在calc函数内部,首先定义了三个整数变量:x、y和ret。
3、然后,程序会输出"请输入两个操作数:",并使用scanf函数从用户处获取两个整数输入,分别赋值给x和y。
4、接着,使用函数指针pf调用函数,并将x和y作为参数传递。函数的返回值被赋值给ret。
5、最后,程序会输出这个返回值。

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)
{
	if (y == 0) {
		printf("除数不能为0\n");
		return -1;
	}
	else {
		return x / y;
	}

}

void menu()
{
	printf("*************************\n");
	printf("****  1.add   3.sub  ****\n");
	printf("****  3.mul   4.div  ****\n");
	printf("****  0.exit         ****\n");
	printf("*************************\n");
}

void calc(int(*pf)(int, int))
//通过函数指针调用函数
//把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,
//被调用的函数就是回调函数
{
	int x = 0, y = 0, ret = 0;
	printf("请输入两个操作数:");
	scanf("%d %d", &x, &y);
	ret = pf(x, y);
	printf("%d\n", ret);
		
}

int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	do
	{
		menu();
		//用回调函数的方法解决switch太长的问题
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			calc(Add);
			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;
}

2、转移表

转移表改造思路:
转移表是一种用于实现多路复用的数据结构,可以用来实现复杂的菜单驱动程序。使用转移表可以将用户输入的操作符映射到相应的操作上。

1、创建一个转移表,该表以操作符为键,以对应的操作函数为值。
2、在主函数中,使用scanf()函数读取用户输入的操作符。
3、使用转移表查找相应的操作函数,并将其调用。
4、将结果存储在一个变量中,并将其打印出来。

为什么要用NULL?

因为选项中0是exit,所以不能把Add放在第一个

指针详解 冒泡排序 qsort(三)_第5张图片

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)
{
	if (y == 0) {
		printf("除数不能为0\n");
		return -1;
	}
	else {
		return x / y;
	}

}

void menu()
{
	printf("*************************\n");
	printf("****  1.add   3.sub  ****\n");
	printf("****  3.mul   4.div  ****\n");
	printf("****  0.exit         ****\n");
	printf("*************************\n");
}


int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	do
	{
		menu();
		//函数指针数组的方法解决switch太长的问题
		//这里的函数指针数组,被称为转移表
		int (*pfArr[])(int, int) = { NULL,Add,Sub,Mul,Div };
		//							0    1   2   3
		printf("请选择:");
		scanf("%d", &input);

		if (input == 0)
		{
			printf("退出计算器\n");
		}
		else if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数:");
			scanf("%d %d", &x, &y);
			ret = pfArr[input](x, y);
			printf("%d\n", ret);
		}
		else {
			printf("选择错误,重新选择\n");
		}
		
	} while (input);
	return 0;
}

冒泡排序

先解析一下void bubbleSort(int arr[], int sz)

 冒泡排序的核心思想就是:两两相邻的元素进行比较先写一个基本框架再实现函数定义部分 ,先外层循环确定趟数,再内层循环确定每趟交换的对数,最后判断相邻元素大小,如果不满足顺序就交换

指针详解 冒泡排序 qsort(三)_第6张图片

void bubble_sort(int* arr, int sz)
{
	//趟数
	int i = 0, j = 0;
	for (i = 0; i < sz - 1; i++)
	{
		//一趟冒泡排序的过程
		//两两元素相邻比较
		for (j = 0; j < sz - i - 1; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int t = arr[j];
				arr[j] = arr[j + 1];
				arr[j+1] = t;
			}
		}
	}
}

void print(int* arr, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);

	}
	printf("\n");
}

int main()
{
	//将一组整数排序为升序
	int arr[10] = { 3,1,2,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);

	bubble_sort(arr, sz);
	print(arr, sz);
	return 0;
}

但是上述代码还可以再进行优化,试想一下,如果要排序的数组是

9,0,1,2,3,4,5,6,7,8  第一趟排序完便已经升序了 ,但是还在不停的循环。所以,我们可以这样优化。

指针详解 冒泡排序 qsort(三)_第7张图片

加入flag变量,表示数组当前是否有序。而判断有序的方法,则是如果一趟冒泡排序下来,没有一对交换,则证明有序。 反之,如果有交换,则flag置为0,表示无序,则继续下一趟冒泡排序。这样,就可以节省时间

strcmp函数

指针详解 冒泡排序 qsort(三)_第8张图片

#include 
#include 
 
int main ()
{
   char str1[15];
   char str2[15];
   int ret;
 
 
   strcpy(str1, "abcdef");
   strcpy(str2, "ABCDEF");
 
   ret = strcmp(str1, str2);
 
   if(ret < 0)
   {
      printf("str1 小于 str2");
   }
   else if(ret > 0) 
   {
      printf("str1 大于 str2");
   }
   else 
   {
      printf("str1 等于 str2");
   }
   
   return(0);
}

指针详解 冒泡排序 qsort(三)_第9张图片

特别注意:strcmp(const char *s1,const char * s2) 这里面只能比较字符串,即可用于比较两个字符串常量,或比较数组和字符串常量,不能比较数字等其他形式的参数。

ANSI 标准规定,返回值为正数,负数,0 。而确切数值是依赖不同的C实现的。

当两个字符串不相等时,C 标准没有规定返回值会是 1 或 -1,只规定了正数和负数。

有些会把两个字符的 ASCII 码之差作为比较结果由函数值返回。

qsort函数

指针详解 冒泡排序 qsort(三)_第10张图片

指针详解 冒泡排序 qsort(三)_第11张图片

函数调用的使用:qsort    quick sort
qsort 是库函数,这个函数可以完成任意类型的排序
1.qsort确实可以排序任意的数据类型
2.使用的时候,需要使用者传递一个函数的地址,
这个函数用来比较待排序数组中的两元素

指针详解 冒泡排序 qsort(三)_第12张图片

测试qsort函数排序整型数据

正常使用冒泡排序

void bubbleSort(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++)
		{
			if (arr[j] > arr[j + 1])
			{
				int t = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = t;
			}
		}
	}
}

***通过qsort实现冒泡排序***(重点)

void bubbleSort(int arr[], int sz)

解析在冒泡中,以下为用模拟qsort的解析

此为模拟void bubbleSort(int arr[], int sz)的函数

指针详解 冒泡排序 qsort(三)_第13张图片arr进入void bubbleSort2函数后

执行以下模拟冒泡的语句

指针详解 冒泡排序 qsort(三)_第14张图片

每两个元素依次进入cmp进行比较

为什么要用强制转换:因为void*类型是方便输入的数据为任意类型,进入后不是int型无法计算,强制类型转换后才可以进行运算

指针详解 冒泡排序 qsort(三)_第15张图片

返回值大于0执行Swap交换语句

指针详解 冒泡排序 qsort(三)_第16张图片

交换后继续循环判断,直到结束

指针详解 冒泡排序 qsort(三)_第17张图片

int cmp_int(const void*p1,const void* p2)
{
	return *(int*)p1 - *(int*)p2;
}


void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

void Swap(char* buf1, char* buf2, unsigned int width)
{
	int i = 0;
	for (i = 0; i < width; i++)//一个一个字节交换
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}


void bubbleSort2(void* base, unsigned int sz, unsigned int width, int (*cmp)(const void* p1,const  void* p2))
/*
void* base - 这是要排序的内存块的起始地址。
unsigned int sz - 这是内存块的大小,以字节为单位。
unsigned int width - 这是每个元素的大小,以字节为单位。
int (*cmp)(const void* p1, const void* p2) - 这个函数应该返回一个整数,表示两个元素的相对顺序。
如果第一个元素应该排在第二个元素之前,那么这个函数应该返回负数。如果两个元素相等,
那么这个函数应该返回0。如果第一个元素应该排在第二个元素之后,那么这个函数应该返回正数。
*/
{
	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])
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
				//这里为什么要用char*
				//如果用int*,则需要跳过24/4个字节,才能表示为一个结构体元素
				//如果要计算结构体的大小,转换为char*最好算
			{
				/*int t = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = t;*/
				//交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}

void test3()
{
	int arr[] = { 3,1,5,7,9,2,4,0,8,6 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	//设计并实现bubbleSort2(),这个函数能够排序任意类型的数据
	bubbleSort2(arr, sz, sizeof(arr[0]), cmp_int);
	print_arr(arr, sz);
}


int main()
{
	test3();
	return 0;
}

指针详解 冒泡排序 qsort(三)_第18张图片

测试qsort函数排序结构体数据
int cmp_int(const void*p1,const void* p2)
{
	return *(int*)p1 - *(int*)p2;
}


void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

void Swap(char* buf1, char* buf2, unsigned int width)
{
	int i = 0;
	for (i = 0; i < width; i++)//一个一个字节交换
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}


void bubbleSort2(void* base, unsigned int sz, unsigned int width, int (*cmp)(const void* p1,const  void* p2))
/*
void* base - 这是要排序的内存块的起始地址。
unsigned int sz - 这是内存块的大小,以字节为单位。
unsigned int width - 这是每个元素的大小,以字节为单位。
int (*cmp)(const void* p1, const void* p2) - 这个函数应该返回一个整数,表示两个元素的相对顺序。
如果第一个元素应该排在第二个元素之前,那么这个函数应该返回负数。如果两个元素相等,
那么这个函数应该返回0。如果第一个元素应该排在第二个元素之后,那么这个函数应该返回正数。
*/
{
	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])
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
				//这里为什么要用char*
				//如果用int*,则需要跳过24/4个字节,才能表示为一个结构体元素
				//如果要计算结构体的大小,转换为char*最好算
			{
				/*int t = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = t;*/
				//交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}


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*)p2)->name);
}

int cmp_stu_by_age(const void* p1, const void* p2)
{
	return (((struct Stu*)p1)->age - ((struct Stu*)p2)->age);
}

void test4()
{
	struct Stu arr[] = { {"zhangsan",18},{"list",35}, {"wangwu",15} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	//sizeof(arr[0])
	//一个结构体元素24个字节
	bubbleSort2(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
	//打印arr数组的内容
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s %d\n", arr[i].name, arr[i].age);
	}

}

int main()
{
	test4();
	return 0;
}

指针详解 冒泡排序 qsort(三)_第19张图片

指针详解 冒泡排序 qsort(三)_第20张图片

这是按年龄排序的结果

这是按名字排序的结果

指针详解 冒泡排序 qsort(三)_第21张图片

指针详解 冒泡排序 qsort(三)_第22张图片

void qsort(
    void* base,//base 指向了要排序的数组的第一个元素    (待排序数组的起始位置)    
  //qsort可能排序任意类型的数据,为了能够接收任意的可能的指针类型,设计成void*
    size_t num,//base指向的数组中的元素个数(待排序的数组的元素个数)
    size_t size,//base指向的数组中元素的大小(待排序的数组的元素大小,单位是字节)
    int(*compar)(const void*p1, const void*p2)
    //该函数指针指向的是一个函数
    //指向的函数是用来比较待排序数组中的两个元素的
  //函数的使用者提供一个函数
    //函数指针 - 指针指向的函数是用来比较数组中的2个元素的
    //p1指向一个元素,p2也指向一个元素
);

如果你感觉上述的代码对你有帮助,可以给我点个赞吗?

创作不易,谢谢各位的点赞,咱们下期见!

你可能感兴趣的:(c语言,开发语言,笔记)