C语言指针深入浅出5

目录

  • [1. 回调函数](#1. 回调函数)
  • [2. qsort 使用举例](#2. qsort 使用举例)
    • [2.1 使用qsort来排序整型数组](#2.1 使用qsort来排序整型数组)
    • [2.2 使用qsort来排序结构体数据](#2.2 使用qsort来排序结构体数据)
  • 3.qsort函数的模拟实现

1. 回调函数

  1. 定义
    回调函数是一种 函数指针(地址) 作为参数传递给另一个函数的机制。
  2. 调用方式
    1. 回调函数本身 不是直接由程序显式调用 的。
    2. 它是在特定的 事件或条件发生时,由被传入的另一方函数调用的。
  3. 使用目的
    回调函数通常用于 对特定事件或条件做响应。也就是,当某件事情发生或某个条件满足时,程序会触发回调函数执行对应的逻辑。
  4. 特点总结
    1. 传递灵活:可以在运行时把不同的函数作为参数传入,动态改变行为。
    2. 事件驱动:常用于事件驱动编程,如 GUI 操作、异步操作、定时器等。
    3. 解耦合:调用者只负责触发事件,不需要知道回调函数的具体实现,提高代码可维护性。
c 复制代码
// 定义回调函数:接受两个整数并返回整数
int Add(int x, int y)
{
    return x + y;
}

// Compute函数接受两个整数和一个回调函数指针
int Compute(int a, int b, int (*p)(int, int))
{
    // 调用回调函数 p,实际调用的是 Add
    return p(a, b);
}

int main()
{
    //将Add函数作为回调函数传入Compute
    int r = Compute(2, 3, Add);

    printf("%d\n", r); // 输出 5

    return 0;
}

这里需要用到函数指针的知识,知识点回顾:C语言指针深入浅出4

学习完回调函数后,我们就又可以改写在C语言指针深入浅出4中讲解转移表(简易计算器的实现)的代码。

代码实现:

c 复制代码
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");
}

void calc(int (*p)(int, int))
{
	int x = 0;
	int y = 0;
	printf("请输入两个数字:");
	scanf("%d %d", &x, &y);
	int r = p(x, y); //调用的是对应的函数,被调用的函数就是回调函数
	printf("结果是:%d\n", r);
}

int main()
{
	int r = 0;
	int input = 1;
	menu();
	do
	{
		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. qsort 使用举例

qsort 是 C 标准库提供的通用排序函数 ,用于对数组中的任意类型元素进行排序。它内部使用快速排序(Quick Sort)算法,但只需要提供数组和比较方法即可,qsort的返回值是void类型。
qsort函数原型:

下面我来对qsort函数中的参数逐一进行说明:

  1. void *base
    • 指向要排序数组的首元素的指针
    • 由于是通用排序,类型用void* 表示,可以指向任意类型 的数组。小tip:void* 也被称为泛型指针,在C语言深入浅出1中讲解了void* 指针相关内容。
  2. size_t num
    • 数组中元素的数量。
  3. size_t size
    • 数组中每个元素占用的字节数。
  4. int (*compar)(const void *, const void *)
    • 比较函数的指针,也叫回调函数。
    • 函数接收两个元素的指针,返回值表示这两个元素的相对顺序:
      • < 0 :第一个元素排在前
      • 0 :两个元素相等
      • > 0 :第一个元素排在后
    • 这个函数决定了 qsort 的排序规则。
      更加大白话的理解:函数的返回值能体现出p1和p2指向的数据的大小。
      p1指向的数据 > p2指向的数据, 返回 > 0的数字
      p1指向的数据 < p2指向的数据, 返回 < 0的数字
      p1指向的数据 == p2指向的数据, 返回 0
      如果对于上述还有不理解的地方,下面这张图片可以加深对于qsort函数的理解:

2.1 使用qsort来排序整型数组

c 复制代码
#include <stdio.h>
#include <stdlib.h> //使用qsort函数要包含头文件

int cmp_int(const void* p1, const void* p2)
{
// void* 类型的指针不能直接解引用取值
// 需要先强制转换为 int*,再解引用得到整数
	return *((int*)p1) - *((int*)p2);
}

int main()
{
	int arr[10] = { 8,7,6,9,5,4,3,1,2,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr,sz,sizeof(arr[0]), cmp_int); // 将比较函数 cmp_int 作为回调函数传给 qsort
	for (int i = 0; i < sz; i++)
		printf("%d ", arr[i]);
	return 0;
}

qsort函数工作原理示意图:


2.2 使用qsort来排序结构体数据

讲解之前,我们要讲解之前C语言操作符详解中还没讲解的结构体成员间接访问 的方式,我们还需要补充一个操作符:结构体指针访问操作符-> , 当我们通过结构体指针 访问结构体成员时,可以使用 -> 操作符。
结构体成员访问:
1. 结构体变量.成员名
2. 结构体指针->成员名

c 复制代码
struct Stu
{
	char name[20];
	int age;
};

int main()
{
	int a = 0;
	struct Stu p1 = { "zhangsan",18 };
	struct Stu* p2= &p1;
	// 通过结构体指针访问结构体成员
	printf("%s %d\n", p2->name, p2->age);
	
	//修改结构体成员
	p2->age = 20;
	return 0;
}

讲解完这个后,C语言操作符详解 的介绍就正式告一段落了,接下来我们继续学习。

他由于下面我创建的结构体成员是char name[20]int age,相应的比较方式也不同,我们一一进行学习。
第一种情况:比较年龄

c 复制代码
#include <stdio.h>
#include <stdlib.h> //使用qsort函数要包含头文件

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

int cmp_stu_by_age(const void* p1, const void* p2)
{
	return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
	// p1 和 p2 指向的是结构体数组中的元素,所以需要强转为 struct Stu* 类型(结构体指针类型)
}

int main()
{
	// 定义结构体数组,用来存储学生信息
	struct Stu p[] = { {"zhangsan",21} ,{"lisi",19 }, {"wangwu",20 } };
	//结构体的比较要分情况进行比较,在这个示例中要么比较年龄,要么比较名字
	int sz = sizeof(p) / sizeof(p[0]);//计算数组的元素个数
	qsort(p, sz, sizeof(p[0]), cmp_stu_by_age);
	//qsort(结构体数组首地址, 学生个数, 每个学生结构体的大小, 按年龄比较);
	for (int i = 0; i < sz; i++)
	{
		printf("%s %d\n", p[i].name,p[i].age);
		//结构体成员的访问
	}
	return 0;
}

注意:在qsort函数的第三个参数时容易出错,因为比较的是年龄,但排序移动的是整个结构体,所以第三个参数必须是 sizeof(p[0]) ,不能是 sizeof(int)

第二种情况:比较姓名

因为在 C 语言中,字符串不能直接使用 ><== 来比较内容大小,所以需要使用库函数 strcmpstrcmp 是字符串比较函数,用于按照字典序比较两个字符串的大小。

项目 说明
函数名 strcmp
头文件 #include <string.h>
函数原型 int strcmp(const char* str1, const char* str2);
功能 比较两个字符串的大小
比较方式 从两个字符串的第一个字符开始逐个比较,直到遇到不同字符或字符串结束标志 \0
比较规则 按字符的 ASCII 码值进行比较
注意事项 strcmp 比较的是字符串内容,不是字符串长度
参数 含义
str1 第一个要比较的字符串
str2 第二个要比较的字符串
返回值 含义
< 0 str1 小于 str2
= 0 str1 等于 str2
> 0 str1 大于 str2
c 复制代码
#include <stdio.h>
#include <string.h> //运用strcmp要包含头文件
#include <stdlib.h>

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 main()
{
	// 定义结构体数组,用来存储学生信息
	struct Stu p[] = { {"zhangsan",29} ,{"lisi",30 }, {"wangwu",10 } };
	int sz = sizeof(p) / sizeof(p[0]);//计算数组的元素个数
	qsort(p, sz, sizeof(p[0]), cmp_stu_by_name);
	for (int i = 0; i < sz; i++)
	{
		printf("%s %d\n", p[i].name, p[i].age);
	}
	return 0;
}

3.qsort函数的模拟实现

在通用排序算法中,通常有以下几步:

由于qsort函数内部使用快速排序(Quick Sort)算法,我们暂时没有讲到,我们就用冒泡的方式 来实现,冒泡排序的相关讲解:C语言深入浅出3
注意事项:
1. basevoid*类型,不能直接进行下标访问。
2. 由于 void* 不知道元素大小,所以要强转为 char*,按字节移动。
3. 第 j 个元素的地址为 : (char*)base + j * width

4. width 表示每个元素占用的字节数
5. cmp 用来比较两个元素的大小,传入的是两个元素的地址
6. Swapwidth 个字节 交换,交换的是整个元素,因为我们将数组强制类型转化为char*,所以我们只能逐字节地进行交换 ,交换次数即为数组每个元素所占用的字节数width
7. 排序依据由 cmp 决定,交换大小由 width 决定。
代码实现:

c 复制代码
#include <stdio.h>
#include <string.h>

void Swap(char* buf1, char* buf2, size_t width)
{
	for (int i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}

void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void* p1, const void* p2))
{
	for (size_t i = 0; i < sz; i++)
	{
		for (size_t 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);
			}
		}
	}
}

int cmp_arr(const void* p1, const void* p2)
{
	return *((int*)p1) - *((int*)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*)p2)->name);
}

int main()
{
	int arr[10] = { 9,8,7,6,5,4,0,3,2,1 };
	struct Stu p[] = { {"zhangsan",21} ,{"lisi",19 }, {"wangwu",20 } };
	int sz1 = sizeof(arr) / sizeof(arr[0]);
	int sz2 = sizeof(p) / sizeof(p[0]);
	bubble_sort(arr, sz1, sizeof(int), cmp_arr);
	bubble_sort(p, sz2, sizeof(p[0]), cmp_stu_by_name);
	for (int i = 0; i < sz1; i++)
		printf("%d ", arr[i]);
	printf("\n\n");
	for (int i = 0; i < sz2;i++)
		printf("%s %d\n", p[i].name, p[i].age);
	
	return 0;
}

相关推荐
csbysj20201 小时前
Vue.js 监听属性
开发语言
匠在江湖1 小时前
EtherCAT从站(LAN9252+STM32)配置全解析与优化指南
c语言
Hesionberger1 小时前
LeetCode 101:对称二叉树(多语言解法)
开发语言·python
小陈的进阶之路1 小时前
Python系列课(11)——PySpark
开发语言·python·ajax
宏笋1 小时前
C++ 回调函数详解和常用场景
开发语言·c++
说不得明天1 小时前
网络管理:AutoarNM部分
c语言·网络·mcu·汽车·autosar
测试员周周1 小时前
【Appium 系列】第04节-Page Object 模式 — BasePage 基类设计
开发语言·数据库·人工智能·python·语言模型·appium·web app
折哥的程序人生 · 物流技术专研1 小时前
《Java 100 天进阶之路》第14篇:Java final关键字详解
java·开发语言·后端·面试
Cosmoshhhyyy1 小时前
《Effective Java》解读第 52 条:慎用重载
java·开发语言·windows