关于我、重生到500年前凭借C语言改变世界科技vlog.15——深入理解指针(4)

文章目录

1.回调函数的介绍

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

如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数

正如我们在上一篇 vlog 中讲到的转移表,就是经典的回调函数,回调函数不是函数本身自己实现的,而是在特定的事件或条件发生的时候由另一方调用,对该事件或条件进行响应

传送门:关于我、重生到500年前凭借C语言改变世界科技vlog.14------常见C语言算法

还是以一个简易的计算器做例子

csharp 复制代码
int add(int a, int b)
{
     return a + b;
}
int sub(int a, int b)
{
     return a - b;
}
int mul(int a, int b)
{
     return a * b;
}
int div(int a, int b)
{
     return a / b;
}

int main()
{
     int x, y;
     int input = 1;
     int ret = 0;
     do
     {
         printf("*************************\n");
         printf("    1:add          2:sub \n");
         printf("    3:mul          4:div \n");
         printf("    0:exit               \n");
         printf("*************************\n");
         printf("请选择:");
         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;
}

每写一个计算方法都要写一种情况,不断地 scanf 输入,printf 输出,显得过于啰嗦

csharp 复制代码
#include <stdio.h>
int add(int a, int b)
{
	return a + b;
}
int sub(int a, int b)
{
	return a - b;
}
int mul(int a, int b)
{
	return a * b;
}
int div(int a, int b)
{
	return a / b;
}
void calc(int(*pf)(int, int))
{
	int ret = 0;
	int x, y;
	printf("输⼊操作数:");
	scanf("%d %d", &x, &y);
	ret = pf(x, y);
	printf("ret = %d\n", ret);
}
int main()
{
	int input = 1;
	do
	{

		printf("*************************\n");
		printf("    1:add        2:sub   \n");
		printf("    3:mul        4:div   \n");
	    printf("*************************\n");
		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;
}

把每种情况的函数统一由一个函数管理,把这些函数以参数的形式传给该函数,这些函数就被称为回调函数

值得注意的是,在上一篇 vlog 中的优化方案是使用函数指针数组,这里使用的是回调函数

2. qsort使用实例

2.1 qsort函数介绍

什么是qsort函数?

传送门:qsort-C++参考


qsort函数的作用:对数组的元素进行排序,对指向的数组的元素进行排序,每个元素字节长,使用函数确定顺序,此函数使用的排序算法通过调用指定的函数来比较元素对,并将指向它们的指针作为参数,该函数不返回任何值,但通过按照 定义对其元素重新排序来修改指向的数组的内容

包含头文件 #include <stdlib.h>, 其语法形式为:

void qsort (void* base, size_t num, size_t size,

int (compar)(const void,const void*));

base:指向要排序的数组的第一个对象的指针,转换为 .void*

num:指向的数组中的元素数,是无符号整型

size:数组中每个元素的大小(以字节为单位),是无符号整型
compare:指向比较两个元素的函数的指针,此函数被重复调用以比较两个元素

假设第一个指针为p1,第二个指针为p2,那么有以下结论:

1.如果p1指向的元素小于p2,则返回小于0的数字

2.如果二者相等,则返回0

3.如果p1指向的元素大于p2,则返回大于0的数字

4.默认排序为升序,若想降序以上结论反转即可

那么 qsort 函数是如何一个一个比较的呢?

只是单纯两个比较吗?需要加循环结构吗?

当qsort函数在执行排序过程中,每当需要比较两个数组元素以确定它们的相对顺序时,就会调用用户提供的这个比较函数

1.在划分步骤中,通常会选择一个基准元素(pivot),并通过设置两个指针(比如一个从数组开头,一个从数组结尾)来对数组进行划分,这两个指针的初始范围是从数组的起始地址和结束地址开始

2.例如,对于一个整数数组int arr[] = {1, 2, 3, 4,> 5},如果选择第一个元素作为基准元素,那么一个指针可能从&arr[0]开始(指向首元素),另一个指针可能从&arr[sizeof(arr) / sizeof(arr[0]) - 1]开始(指向尾元素)

3.然后这两个指针会根据与基准元素的比较结果(通过调用比较函数)进行移动,在移动过程中,它们的范围会不断变化,直到完成划分操作,使得数组被分成两部分,一部分元素小于基准元素,另一部分元素大于基准元素。此时这两个指针的最终范围就是划分后两部分数组的边界地址,比如一个指针可能停留在小于基准元素那部分数组的最后一个元素地址处,另一个指针可能停留在大于基准元素那部分数组的第一个元素地址处

2.2使用 qsort 函数排序整型数据

csharp 复制代码
#include <stdio.h>
#include <stdlib.h>

    int int_cmp(const void * p1, const void * p2)
{
    return (*( int *)p1 - *(int *) p2);
}
int main()
{
    int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
    int i = 0;
    qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
    for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
    {
        printf( "%d ", arr[i]);
    }
    printf("\n");
    return 0;
}

强调 p1 和 p2 必须是 const void* 类型的指针

这里定义了int_cmp函数,qsort函数在对数组进行排序时,需要一个能比较数组元素大小关系的函数作为参数,int_cmp函数接受两个const void *类型的指针p1和p2,这种通用指针类型使得qsort函数可以处理各种类型数据的排序(虽然这里实际只用于整数数组),在函数内部,先将p1和p2这两个通用指针转换为int *类型指针,以便能解引用获取到对应的整数,然后通过计算这两个整数的差值并返回,返回值的正负情况决定了qsort函数对数组元素的排序顺序:返回值小于 0,表示p1所指向的整数小于p2所指向的整数,那么在排序结果中p1对应的元素会排在p2对应的元素之前;返回值大于 0,意味着p1所指向的整数大于p2所指向的整数,p1对应的元素会排在p2对应的元素之后;若返回值等于 0,则表明这两个整数相等,此时元素的相对顺序在排序中可保持原有顺序或按其他默认规则处理

2.3使用 qsort 排序结构数据

csharp 复制代码
struct Stu //学⽣
{
    char name[20];//名字
    int age;//年龄
};
//假设按照年龄来⽐较
int cmp_stu_by_age(const void* e1, const void* e2)
{
    return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
//假设按照名字来⽐较
int cmp_stu_by_name(const void* e1, const void* e2)
{
    return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
//按照年龄来排序
void test2()
{
    struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };
    int sz = sizeof(s) / sizeof(s[0]);
    qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
}
//按照名字来排序
void test3()
{
    struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };
    int sz = sizeof(s) / sizeof(s[0]);
    qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}
int main()
{
    test2();
    test3();
    return 0;
}

strcmp - 是库函数,是专门用来比较两个字符串的大小的

该函数也和整数排列类似,只不过指针类型有所区别

3. qsort的模拟实现

使用回调函数,模拟实现qsort,

qsost底层采用的是快速排序的方法,在这里我们使用更简单的冒泡排序的排序算法来模拟实现qsort函数,对快排想要了解更多的,在上一篇 vlog 讲到了冒泡排序

传送门:关于我、重生到500年前凭借C语言改变世界科技vlog.14------常见C语言算法

首先是要实现排序模拟,那么运用冒泡排序就行,然后对两个数进行对比:

csharp 复制代码
void Swap(char* buf1, char* buf2, size_t width)
{
    int i = 0;
    char tmp = 0;
    for (i = 0; i < width; i++)
    {
        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))
{
    //趟数
    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)
            {
                //交换两个元素
                Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
            }
        }
    }
}

由于不知道用户排序的数据类型,传过来的数组首元素地址我们必须使用void*指针接收,不能进行解引用,且数据类型不能传参的,那我们该怎么找到相邻元素比较呢?

因为不知道接收数据的类型所以我们用char* 来一个字节一个字节移动,同样对比两个数也是如此,这就保证了这个模拟的函数能够接受各种类型的数据

使用 void* 指针实现了对不同数据排序,这种编程也叫做泛型编程

希望读者们多多三连支持

小编会继续更新

你们的鼓励就是我前进的动力!

相关推荐
Theodore_10222 小时前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
网易独家音乐人Mike Zhou2 小时前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
----云烟----4 小时前
QT中QString类的各种使用
开发语言·qt
lsx2024064 小时前
SQL SELECT 语句:基础与进阶应用
开发语言
开心工作室_kaic4 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
向宇it4 小时前
【unity小技巧】unity 什么是反射?反射的作用?反射的使用场景?反射的缺点?常用的反射操作?反射常见示例
开发语言·游戏·unity·c#·游戏引擎
武子康4 小时前
Java-06 深入浅出 MyBatis - 一对一模型 SqlMapConfig 与 Mapper 详细讲解测试
java·开发语言·数据仓库·sql·mybatis·springboot·springcloud
转世成为计算机大神5 小时前
易考八股文之Java中的设计模式?
java·开发语言·设计模式
搬砖的小码农_Sky5 小时前
C语言:数组
c语言·数据结构
宅小海5 小时前
scala String
大数据·开发语言·scala