深入理解C语言指针(4)

深入理解指针(4)

1回调函数

回调函数就是一个通过函数指针调用的函数

如果我们把函数的地址作为参数传递给另外一个函数, 当这个指针被用来调用其所指向的函数时,被调用的函数就是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定事件或条件发生时由另外一方调用的,用于对该时间或条件进行响应。

我们之前学的用函数指针 解决计算器的问题 实际上就是回调函数的应用

void menu()
{
	printf("**************************************\n");
	printf("**************简单计算器***************\n");
	printf("******1.Add  2.Sub  3.Mid  4.Div******\n");
	printf("****************0.exit****************\n");
}
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 Calc(int (*p)(int, int))
{
	int x, y;
	int ret = 0;
	printf("请输入两个操作数\n");
	scanf("%d %d", &x, &y);
	ret = p(x, y); // 这里通过函数指针来实现对函数的调用
	printf("%d\n", ret);
}
int main()
{
	int input = 0;
	int x, y;
	int ret = 0;
	do
	{
		menu();
		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.qsort 函数 (有点难)

qsort 函数 – 用来排序的 来自库函数
底层使用的是快速排序

在讲述qsort函数之前 我们先来回忆一下我们之前所学过得冒泡排序

// 冒泡排序
void bubble_sort(int arr[],int sz)
{
	int flag = 0;
	for (int i = 0; i < sz - 1; i++)
	{
		for (int j = 0; j < sz - 1; j++)
		{
			//if (arr[j] > arr[j + 1])
			//{
			//	int tmp = arr[j];
			//	arr[j] = arr[j + 1];
			//	arr[j + 1] = tmp;
			//	flag = 1;
			//}
			// 也可以改成指针的形式
			if (*(arr + j) > *(arr + j + 1))
			{
				int tmp = *(arr+j);
				*(arr + j) = *(arr + j + 1);
				*(arr + j + 1) = tmp;
				flag = 1;
			}
		}
		if (flag == 0)
			break;
	}
}
void Print(int* p, int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
		
	}
}
int main()
{
	int arr[] = { 1,3,2,4,6,7,5,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz);
	Print(arr, sz);

	return 0;
}

我们现在来思考一下冒泡排序的逻辑是怎么样的

image-20240324104533826

首先它会两两比较 直到比完最后一个元素 不满足顺序就交换 而一次这个过程叫做一趟冒泡排序

而如果需要排序的元素有n个的话 它就需要n-1 躺冒泡排序

但是这个冒泡排序是有点问题的

因为它只能排序整形数据

这个时候我们需要学习的qsort函数 它可以排序任意数据

那它是如何做到的呢 这个时候我们就来学习它

我们首先来看 qsort 函数

void qsort( void *ptr,  // 指针,指向的是待排序数组的首元素地址  为什么是void* 是因为不知道传进来的	参数是什么类型的
			size_t count, // 是ptr所指向的的待排序数组的元素个数
            size_t size, // ptr所指向的待排序数组的元素大小
            int (*compar)(const void*, const void*) // 函数指针变量
            //  指向的就是对两个元素进行比较的函数
            // 为什么这里是一个函数指针变量呢?
            );

那么为什么qsort函数的形参有一个函数指针变量呢

从前面的冒泡排序中我们就可以看到原因

我们发现 在冒泡排序中

/if (arr[j] > arr[j + 1])
	//{
	//	int tmp = arr[j];
	//	arr[j] = arr[j + 1];
	//	arr[j + 1] = tmp;
	// }

我们发现其对两个元素的比较是直接使用 > 去比较的

而要想实现对任意元素都能进行排序的话 这里就会出现问题 比如字符串类型 和结构体都 无法使用 这个方式

因此我们可以联想到 在if语句那边 采用回调函数的思想 对不同类型的元素 采用相应的函数去进行比较

要想实现回调函数 那么接受的参数内 自然就需要函数指针类型了

经过这样的思考我们就可以知道为何qsort函数需要一个函数指针变量的形参了

实际上qsort函数 就是像我们上面说的这么做的

由于我们是qsort的使用者 我们清楚我们需要比较的两个类型 是什么类型 。 应该如何去比较

因此这个比较的函数是修需要我们自己写的并把函数指针传给sqort

// 在qsort函数内部调用的函数 是有要求的
// 如果比较的两个元素 :
// p1 > p2 要返回一个大于0的数
// p1 = p2 要返回0
// p1 < p2 要返回一个小于0的数

我们来看使用qsort函数的实例:
// 在qsort函数内部调用的函数 是有要求的
// 如果比较的两个元素 :
// p1 > p2  要返回一个大于0的数
// p1 = p2  要返回0
// p1 < p2  要返回一个小于0的数

int cmp_int(const void* p1, const void* p2) //p1 p2 分别放着第一个和第二个要做比较的元素的地址
// 这里我们需要注意一个细节
//void*类型的指针 是无具体类型的指针,这种类型的指针无法直接解引用 也不能进行+-整数得到运算
{
//if (*(int*)p1 >*(int*) p2) // 我们自己是清楚这个函数内部比较的两个元素是int型的 因此我们直接采取强制转换
//	return 1;
//else if (*(int*)p1 == *(int*)p2)
//	return 0;
//else
//	return -1;
 这里其实可以简化代码
return  *(int*)p1 - *(int*)p2;
 并且如果我们想要实现降序的话 
//return *(int*)p2 - *(int*)p1; // 这个就可以实现降序
}

void Print(int* p, int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));

	}
}

int main()
{
	// 写一段代码使用sqort排序整型数据
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	Print(arr, sz); // 0 1 2 3 4 5 6 7 8 9
	return 0;
}

学了如何使用qsort函数排序整型数据

那我们来试一下qsort 排序结构体数据

//写一段代码 用sqort函数排序 结构体数据

struct Stu
{
	char name[20];
	int age;
};
// 有个问题  两个结构体元素怎么比较大小?
// 1.按照名字比较 - 字符串比较 - strcmp
// 2.按照年龄比较 - 整形比较

int cmp_stu_by_name(const void* p1, const void* p2)
{
	// 一样的需要对p1进行强制转换  变成结构体类型
	//if (strcmp(((struct Stu*)p1), ((struct Stu*)p1)) == 0)
	//	return 0;  
	//......  // 用条件判断可以  这里直接可以简化代码
	//strcmp(((struct Stu*)p1), ((struct Stu*)p2)); // strcmp 是按照对应字符串中的字符ASCII码值比较的
	// strcmp 自己在比较字符串大小的 时候 也是
	// p1 > p2  要返回一个大于0的数
	// p1 = p2  要返回0
	// p1 < p2  要返回一个小于0的数
	// 那我们这边直接返回一个 strcmp 函数
	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 test2()
{
	struct Stu arr[3] = { {"zhangsan",20},{"lisi",30},{"wangwu",40} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	//qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name); // 按照名字来排序
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age); // 按照年龄来排序

	for (int i = 0; i < sz; i++)
	{
		printf("%s %d\n", arr[i].name, arr[i].age);
	}
}


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

// 我们学会了如何使用qsort函数后
// 那我们能不能改造我们之前的冒泡排序
// 让其能对任意类型排序

struct Stu
{
	char name[20];
	int age;
};

int cmp_int(const void* p1, const void* p2) // 在前面已经写过了
{
	return  *(int*)p1 - *(int*)p2;
}

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 Swap(char* p1, char* p2, size_t width)// 为什么需要width呢 因为我们只知道了两个要交换元素的地址  并不知道他们的类型
										// 也就是不知道步长 那么这个时候就需要传入一个width来告知这个步长是多少
// 由于我们知道了地址和一个元素的字节大小  
// 我们就可以新建一个空间  把第一个元素放进去 再把第二个元素放到 第一个元素的空间  再把新建空间内的第一个元素放到第二个元素的空间
// 这样就完成了一次交换
{
	for (int i = 0; i < width; i++)// 传进来的数据 类型的大小是多少字节 那么就需要交换多少次
	// 因为一次只交换一个字节大小的数据  如果是int类型的数据 一个内存空间有四个字节 那么就需要交换四次
	{
		// 先让p1 p2 拿到各自一个字节的数据
		// 创建一个字节大小的tmp
		char tmp = *p1;
		*p1 = *p2;
		*p2 = tmp;
		p1++;
		p2++;
	}
}
// 冒泡函数内部的思想不变
void bubble_sort(void*base, size_t sz,size_t width,int (*cmp)(const void*p1, const void*p2))
{
	for (int i = 0; i < sz - 1; i++)
	{
		for (int j = 0; j < sz - 1; j++)
		{
			// 那我们如何比较传进来参数的大小呢 
			// 通过cmp比较函数去比较  将两个要比较的数传给cmp函数去比较
			// // 让cmp函数去比较两个数的大小 返回来>0就说明第一个参数大于第二个参数
			// 因此我们需要算出arr[i] 和 arr[j + 1] 的地址 去传给cmp函数让它去比较
			// 关键是如何获取到arr[j] 和  arr[j + 1] 的地址  
			if (cmp((char*)base+ j * width, (char*)base + (j + 1) * width)>0)  // 将传入进来的地址强制转化成char*类型 
				// j * width 是为了刚好跳过一个步长  这样刚好跳过一个元素的字节大小
			{
				// 交换两个元素
				Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
			}

		}
	}
}
void Print(int* p, int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));

	}
}

int test1()
{
	int arr[] = { 1,3,2,4,6,7,5,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
	Print(arr, sz);

	return 0;
}
void test2()
{
	struct Stu arr[3] = { {"zhangsan",20},{"lisi",30},{"wangwu",40} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	//bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name); // 按照名字来排序
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_age); // 按照年龄来排序

	for (int i = 0; i < sz; i++)
	{
		printf("%s %d\n", arr[i].name, arr[i].age);
	}
}
int main()
{
	test2();
	return 0;
}

总结 : 正式因为函数指针的存在我们才能实现想传入什么类型就传入什么类型

这正是回调函数的应用。
zhangsan",20},{“lisi”,30},{“wangwu”,40} };
int sz = sizeof(arr) / sizeof(arr[0]);
//bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name); // 按照名字来排序
bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_age); // 按照年龄来排序

for (int i = 0; i < sz; i++)
{
	printf("%s %d\n", arr[i].name, arr[i].age);
}

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


总结  :  正式因为函数指针的存在我们才能实现想传入什么类型就传入什么类型  

这正是回调函数的应用。
  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: "C语言指针详解.pdf" 是一份详细介绍C语言指针概念和使用的PDF文档。C语言中,指针是一种特殊的变量类型,用于存储其他变量的内存地址。 该PDF文档首先详细介绍了指针的定义和声明。指针的声明需要指定指针变量的类型和名称,并使用星号(*)来表示该变量是一个指针指针变量名的前面加上一个星号,可以获取所指向的变量的值,这被称为"解引用"。 文档还介绍了指针的运算。指针可以进行自增和自减运算,指针之间可以进行相减操作,返回的结果表示它们之间的距离或者偏移量。此外,还可以将指针赋值给另一个指针,或者将指针赋值给一个变量,反之亦然。 除了基本的指针概念,文档还详细介绍了指针的常见应用场景。这包括指针作为函数参数,用于在函数内部对传入的变量进行修改。还有通过指针来实现动态内存分配和释放,以及使用指针实现数据结构(如链表和树)等。 此外,该文档还包含一些常见的指针错误和问题的解决方案。这些错误包括空指针引用、野指针引用以及内存泄漏等。文档指出了这些错误的影响以及如何避免它们。 总的来说,"C语言指针详解.pdf" 是一份详细介绍C语言指针概念、使用和常见问题解决方案的文档,对于学习和理解C语言指针的人们是一份宝贵的资料。 ### 回答2: 《C语言指针详解.pdf》是一本关于C语言指针的详细解析的电子书。在这本书中,作者详细介绍了C语言指针的概念、用途和基本语法。 首先,指针C语言中非常重要的概念,它是一种数据类型,用于存储和操作内存地址。指针可以指向各种数据类型,如整数、字符、数组和结构体等。 在《C语言指针详解.pdf》中,作者详细讲解了指针的声明和初始化,以及如何通过指针来访问和修改变量的值。作者还介绍了指针与数组的关系,以及指针和函数之间的关联。 此外,书中还涵盖了指针的高级应用,如指针的算术运算、指向指针指针指针数组等。作者通过丰富的例子和代码来帮助读者理解这些概念和技巧。 《C语言指针详解.pdf》不仅适合C语言初学者,也适合有一定编程基础的读者。通过阅读此书,读者将能够更深入地理解C语言指针的功能和用法,掌握指针在编程中的灵活运用。 总之,《C语言指针详解.pdf》是一本内容详尽且易于理解的C语言指针教程。读者通过阅读此书,可以提高自己在C语言编程中的指针应用能力,从而更好地实现程序的设计和开发。 ### 回答3: 《C语言指针详解.pdf》是一本介绍C语言指针概念和使用方法的详细手册。C语言中的指针是一种非常重要和特殊的数据类型,它提供了直接访问内存地址的能力,使得C语言具有了更高的灵活性和效率。 这本手册首先会介绍指针的基本概念,包括指针变量的定义和声明、指针的初始化和赋值。它会详细讲解指针和变量之间的关系,以及指针的运算规则和使用方法。读者可以学习到如何通过指针操作变量的值和地址,以及如何利用指针实现函数的参数传递和返回值。 接下来,手册会介绍指针和数组之间的关系。C语言中,数组名本质上是一个指向数组首元素的常量指针,因此可以通过指针来操作数组。手册将详细讲解指针和数组的指针算术运算,以及指针和多维数组的关系。 此外,手册还会介绍指针和字符串之间的关系。C语言中,字符串本质上是以空字符结尾的字符数组,可以通过指针来操作字符串。手册将详细讲解指针和字符串的操作,包括字符串的输入输出、字符串的比较和拷贝。 最后,手册还会介绍指针和结构体之间的关系。C语言中,结构体是用户自定义的复合数据类型,可以通过指针来操作结构体。手册将详细讲解指针和结构体的操作,包括结构体指针的定义和使用,以及结构体指针作为函数参数的传递方式。 总之,《C语言指针详解.pdf》是一本深入浅出的指针教程,对于想更深入理解C语言指针的读者来说,是一本非常实用的参考书。无论是初学者还是有一定基础的读者,都可以从中获得很多宝贵的知识和技巧。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值