• c语言进阶篇:指针(三)


    ✨作者介绍:大家好,我是摸鱼王胖嘟嘟,可以叫我小嘟💕
    ✨作者主页:摸鱼王胖嘟嘟的个人博客主页.🎉
    🎈作者的gitee: 小比特_嘟嘟的个人gitee
    🎈系列专栏: 【从0到1,漫游c语言的世界】
    ✨小嘟和大家一起学习,一起进步!尽己所能,写好每一篇博客,沉醉在自己进步的喜悦当中🤭。如果文章有错误,欢迎大家在评论区✏️指正。让我们开始今天的学习吧!😊
    请添加图片描述

    💻前言

    🍁大家好哇~今天要来接着讲指针进阶了,这篇会带领大家实现一个很重要的函数,希望大家仔细学习,话不多说,让我们开始今天的学习吧!

    🎈指向函数指针数组的指针

    🍁指向函数指针数组的指针,是个指针,指向的是函数指针数组,里面存的是函数指针数组的地址。

    #include
    
    int main()
    {
    	//变量名先和[]结合就是数组,先和*结合就是指针
    	//函数指针
    	int (*pf)(int, int);
    	//指针pf的类型是int(*)(int,int);指针pf指向的函数类型是int()(int,int)
    	
    	//函数指针数组
    	int (*ppf[5])(int, int);
    	//数组ppf有5个元素,每个元素的类型是int(*)(int,int)
    
    	//指向函数指针数组的指针
    	int (*(*pppf)[5])(int, int);
    	/*
    	指向函数指针数组的指针只是比函数指针数组多了一颗 * 
    	第二颗 * 说明是个指针,pppf指向的函数指针数组的类型是int(* [5])(int,int)
    	这个数组有5个元素,每个元素的类型是int(*)(int,int)
    	也可以这样看,先看变量名pppf,变量名先和*结合,说明是个指针,往外一看,是[]符号,
    	说明指针指向的是个数组,这个数组有5个元素,每个元素的类型是int(*)(int,int)
    	那么这个数组就是个函数指针数组。
    	*/
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    🎈回调函数

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

    🍁回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

    下面会通过一个例子详细了解回调函数的用途

    首先,我们先回想一下冒泡排序,通过相邻两个元素的比较最终完成排序。但是冒泡排序只能用于整型数组的排序。

    ✏️复习冒泡排序

    🍁将数组arr中的元素排成升序

    #include
    
    void bubble_sort(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 temp = arr[j];
    				arr[j] = arr[j + 1];
    				arr[j + 1] = temp;
    			}
    		}
    	}
    }
    
    void print_arr(int arr[], int sz)
    {
    	int i = 0;
    	for (i = 0; i < sz; i++)
    	{
    		printf("%d ", arr[i]);
    	}
    	printf("\n");
    }
    
    int main()
    {
    	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
    	int sz = sizeof(arr) / sizeof(arr[0]);
    	bubble_sort(arr, sz);
    	print_arr(arr, sz);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    效果展示:
    在这里插入图片描述

    ✏️了解qsort库函数

    使用快速排序的思想实现的一个排序函数,这个函数可以排序任意类型的数据(默认是排成升序)

    void qsort(void* base,//base中存放的是待排序数据中的第一个对象的地址
    			size_t num,//排序数据元素的个数
    			size_t size,//排序数据中一个元素的大小,单位是字节
    			int (*compar)(const void*,const void*)//是用来比较待排序数据中的2个元素的函数
    			//函数指针,可以接收不同的函数地址
    			//compar: 这是一个比较函数的指针,且这个函数指针指向的函数返回类型是int,有两个参数,参数的类型是void*
    			)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    先来看一下代码:(升序)

    #include
    #include
    
    //比较两个整型元素的比较函数
    //e1指向一个指数
    //e2指向另一个整数
    int int_cmp(const void* e1, const void* e2)
    {
    	return (*(int*)e1 - *(int*)e2);
    	//e1>e2,返回的是>0的数,是升序
    	//return (*(int*)e2 - *(int*)e1); 返回的是>0的数,是降序
    }
    void print_arr(int arr[], int sz)
    {
    	int i = 0;
    	for (i = 0; i < sz; i++)
    	{
    		printf("%d ", arr[i]);
    	}
    	printf("\n");
    }
    int main()
    {
    	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]),int_cmp);
    	print_arr(arr, sz);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    效果展示:
    在这里插入图片描述
    🍁因为qsort是个库函数,所以在这个函数内部到底是怎么进行排序的,这个过程是已经被设置好的,但是通过观察这个函数的函数声明,我们知道这个函数内部一定有一个地方进行了函数指针的调用,这个地方就是两个元素进行比较时。

    🍁因为函数指针接收了传过来的比较函数的地址,所以在qsort函数内部是可以通过函数指针来调用比较函数的。只是因为qsort函数不需要我们来写,我们看不到它内部是怎么实现的而已。

    下面我们着重分析一下qsort函数的最后一个参数 int ( *cmp )(const void *e1, const void *e2 )

    🍁因为qsort函数可以排序任意类型的数据,而不同类型的数据在排序的时候,两个元素的比较方式是不同的(整型可以用><来比较,结构体类型比较方式就不一样了)
    🍁我们需要qsort函数内部可以实现不同类型数据的比较,即我们需要qsort函数内部可以完成多个函数的调用。
    🍁所以,qsort函数参数部分是一个可以接收不同比较函数(整型的比较函数,结构体类型的比较函数)的地址的函数指针,这样,通过函数指针就可以调用其所指向的不同对象了。

    我们要自己写的就是比较函数,这个比较函数的返回类型是 int ,有两个参数,参数的类型是void* 这个函数内部只需要实现比较的功能。

    🎉返回类型:

    🍁这个函数的返回类型是有规定的,e1指向的元素>e2指向的元素时(需要交换),返回>0的数;=时,返回0;<时,返回<0的数。这样做,最终的排序结果就是升序。
    🍁于此类推,我想要返回的结果是降序,那么我就让e1指向的元素0的数;=时,返回0;>时,返回<0的数。这样做,最终的排序结果就是降序。

    🎉参数:

    🍁e1指向要比较的第一个元素,e2指向要比较的第二个元素,
    🍁e1是要比较的第一个元素的地址,e2是要比较的第二个元素的地址。

    🎉void*:

    🍁void* 是无具体类型的指针,这种指针可以接收任意类型的地址
    又因为void是无具体类型的指针,所以void不能解引用操作,也不能±整数。
    因为当我不知道传过来的是什么类型的地址时,我只能用void*类型的指针来接收

    我们说qsort这个函数可以排序任意类型的数据,下面来排序一下结构体(默认是升序)

    1.按名字排序

    #include
    #include
    
    struct Stu
    {
    	char name[20];
    	int age;
    };
    
    int cmp_Stu_by_name(const void* e1, const void* e2)
    {
    	//strcmp --> >0 ==0 <0
    	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
    }
    
    int main()
    {
    	struct Stu s[] = { {"xiaoming",50},{"huahua",30} ,{"wangpeng",40} };
    	int sz = sizeof(s) / sizeof(s[0]);
    	qsort(s, sz,sizeof(s[0]),cmp_Stu_by_name);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在这里插入图片描述

    2.按年龄排序:

    #include
    #include
    
    struct Stu
    {
    	char name[20];
    	int age;
    };
    
    int cmp_Stu_by_age(const void* e1, const void* e2)
    {
    	//strcmp --> >0 ==0 <0
    	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;;
    }
    
    int main()
    {
    	struct Stu s[] = { {"xiaoming",50},{"huahua",30} ,{"wangpeng",40} };
    	int sz = sizeof(s) / sizeof(s[0]);
    	qsort(s, sz,sizeof(s[0]),cmp_Stu_by_age);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在这里插入图片描述

    ✏️将冒泡排序函数改造成一个类似qsort的函数

    改造之前我们需要了解的:

    1、为什么base这个函数的类型是void*,因为 qsort函数在设计的时候,作者并不知道我们会使用qsort来排序什么类型的数据,所以不能写具体的某个类型,而void*可以接收任意类型的地址,所以用void *
    2、我们在排序时,肯定要遍历一下我的数据,所以要知道元素个数
    3、我们还需要知道一个元素是几个字节,我们已经知道了起始位置和元素个数,再知道一个元素占几个字节,我们就可以将这些元素一一找出来,然后就可以对数据进行操作了
    4、函数指针,是为了通过函数指针调用其所指向的函数

    #include
    
    void Swap(char* buf1, char* buf2, int width)
    {
        int i = 0;
        for (i = 0; i < width; i++)
        {
            char tmp = *buf1;
            *buf1 = *buf2;
            *buf2 = tmp;
            buf1++;
            buf2++;
        }
    }
    
    
    void bubble_sort(void* base, int sum, int width, int (*cmp)(const void* e1, const void* e2))
    {
    
        int i = 0;
        for (i = 0; i < sum - 1; i++)
        {
            //一趟冒泡排序
            int flag = 1;
            int j = 0;
            for (j = 0; j < sum - 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);
                    flag = 0;
                }
            }
            if (flag == 1)
            {
                break;
            }
        }
    }
    
    
    int int_cmp(const void* e1, const void* e2)
    {
    	return (*(int*)e1 - *(int*)e2);
    }
    
    void print_arr(int arr[], int sz)
    {
    	int i = 0;
    	for (i = 0; i < sz; i++)
    	{
    		printf("%d ", arr[i]);
    	}
    	printf("\n");
    }
    
    int main()
    {
    	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
    	int sz = sizeof(arr) / sizeof(arr[0]);
    	bubble_sort(arr, sz, sizeof(arr[0]), int_cmp);
    	print_arr(arr, sz);
    
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    效果展示:
    在这里插入图片描述

  • 相关阅读:
    iframe实现pdf预览,并使用pdf.js修改内嵌标题,解决乱码问题
    前端数据库大批量存,indexdDB使用
    【java表达式引擎】四、高性能、轻量级的AviatorScript
    服务访问质量(QoS)介绍与技术 二
    unity资源管理之Addressable
    在线问诊配药的背后,看这家“数字化医院”如何守护数据流动安全
    部署k8s集群-docker
    ElasticSearch的安装部署-----图文介绍
    Flowable-流程设计
    手电筒UL1576测试报告申请流程亚马逊审核
  • 原文地址:https://blog.csdn.net/weixin_61341342/article/details/126039784