C语言:深入了解指针3

1.回调函数是什么?

基本概念

回调函数就是⼀个通过函数指针调⽤的函数。
如果你把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被⽤来调⽤其所指向的函数
时,被调⽤的函数就是回调函数。回调函数不是由该函数的实现⽅直接调⽤,⽽是在特定的事件或条件发⽣时由另外的⼀⽅调⽤的,⽤于对该事件或条件进⾏响应。

使用场景

  • 事件驱动编程:在图形用户界面(GUI)编程中,当用户点击按钮、输入文本等操作发生时,系统会调用预先注册的回调函数来处理这些事件。
  • 排序算法:标准库中的qsort函数允许用户传入一个比较函数作为回调函数,以实现自定义的排序规则。
  • 异步操作:在多线程或异步编程中,当一个异步任务完成时,可以通过回调函数通知主线程进行后续处理。

实现方式

在 C 语言中,实现回调函数主要涉及函数指针的使用。函数指针是指向函数的指针变量,它可以存储函数的地址,并且可以通过该指针调用相应的函数。

示例代码

示例 1:简单的回调函数示例
#include <stdio.h>// 定义回调函数类型
typedef int (*Callback)(int, int);//typedef是自定义类型名字// 回调函数1:加法
int add(int a, int b) 
{return a + b;
}// 回调函数2:减法
int subtract(int a, int b) 
{return a - b;
}// 主调函数,接受一个回调函数作为参数
int operate(int a, int b, Callback func) //Callback func ==int(*func)(int,int)
{return func(a, b);
}int main() 
{int x = 10, y = 5;// 使用加法回调函数int result1 = operate(x, y, add);//函数k名就是地址printf("加法结果: %d\n", result1);// 使用减法回调函数int result2 = operate(x, y, subtract);printf("减法结果: %d\n", result2);return 0;
}

代码解释

1. 定义回调函数类型

typedef int (*Callback)(int, int);

  • typedef 是 C 语言中的一个关键字,用于为已有的数据类型定义一个新的类型名,目的是让代码更具可读性和可维护性。
  • int (*Callback)(int, int) 定义了一个函数指针类型。具体来说,Callback 是一个新的类型名,它代表的是一个指向函数的指针,这个函数接收两个 int 类型的参数,并且返回一个 int 类型的值。

2. 定义回调函数

加法函数 add

 int add(int a, int b)

{

      return a + b;

}

  • 这是一个普通的函数,它接受两个 int 类型的参数 a 和 b,并返回它们的和。
  • 该函数的参数和返回值类型与前面定义的 Callback 类型相匹配,所以它可以作为 Callback 类型的回调函数使用。
减法函数 subtract

 int subtract(int a, int b) 

{

       return a - b; 

}

  • 同样是一个普通函数,接受两个 int 类型的参数 a 和 b,返回它们的差。
  • 它的参数和返回值类型也与 Callback 类型匹配,也能作为回调函数使用。

3. 定义主调函数

 int operate(int a, int b, Callback func) 

        return func(a, b); 

}

  • operate 是主调函数,它接受三个参数:两个 int 类型的参数 a 和 b,以及一个 Callback 类型的参数 func
  • Callback func 等价于 int(*func)(int, int),即 func 是一个函数指针,它指向一个符合 Callback 类型定义的函数。
  • 在函数体中,通过 func(a, b) 调用 func 所指向的函数,并将 a 和 b 作为参数传递给该函数,最后返回该函数的返回值。
4. main 函数

 int main() 

    int x = 10, y = 5

     // 使用加法回调函数 

     int result1 = operate(x, y, add ); 

      printf("加法结果: %d\n", result1 ); 

        // 使用减法回调函数

        int result2 = operate(x, y, subtract ); 

         printf("减法结果: %d\n", result2 ); 

         return 0

}

  • 首先,定义并初始化两个 int 类型的变量 x 和 y,分别赋值为 10 和 5。
  • 调用 operate 函数进行加法运算:
    • operate(x, y, add) 中,将 x 和 y 作为前两个参数传递给 operate 函数,将 add 函数名作为回调函数传递给 operate 函数。在 C 语言中,函数名可以隐式转换为函数的地址,所以 add 实际上传递的是 add 函数的地址。
    • operate 函数内部会通过函数指针 func 调用 add 函数,计算 x 和 y 的和,并将结果返回给 result1
    • 使用 printf 函数输出加法结果。
  • 调用 operate 函数进行减法运算:
    • 类似地,operate(x, y, subtract) 将 subtract 函数的地址作为回调函数传递给 operate 函数。
    • operate 函数内部通过函数指针 func 调用 subtract 函数,计算 x 和 y 的差,并将结果返回给 result2
    • 使用 printf 函数输出减法结果。

2. qsort 使⽤举例

函数原型

void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));

参数解释

  • base:指向要排序的数组的第一个元素的指针。由于使用 void * 类型,所以可以处理任意类型的数组。
  • nmemb:数组中元素的个数。
  • size:数组中每个元素的大小(以字节为单位)。
  • compar:一个指向比较函数的指针。该比较函数用于确定元素之间的顺序关系,它接受两个 const void * 类型的参数,并返回一个整数值,表示两个元素的相对顺序。

比较函数规则

比较函数 int_comp 的返回值规则如下:

  • 如果返回值小于 0,表示第一个参数小于第二个参数。
  • 如果返回值等于 0,表示两个参数相等。
  • 如果返回值大于 0,表示第一个参数大于第二个参数。
#include <stdio.h>
#include <stdlib.h>
//qosrt函数的使⽤者得实现⼀个⽐较函数int int_cmp(const void * p1, const void * p2)
{return (*( int *)p1 - *(int *) p2);//小于返回一个负数,大于返回一个正数,等于返回0.
}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;
}

代码解释:

1. 比较函数 int_cmp

  • 参数qsort 函数要求使用者提供一个比较函数,这个比较函数必须接受两个 const void * 类型的参数。const void * 是一种通用的指针类型,可以指向任意类型的数据,使用它是为了让 qsort 函数能够处理不同类型的数组。这里的 p1 和 p2 就是指向要比较的两个元素的指针。
  • 类型转换:在比较函数内部,由于 p1 和 p2 是 const void * 类型,不能直接进行解引用操作,所以需要将它们转换为 int * 类型(因为这里要比较的是整数数组),然后再进行解引用,得到具体的整数值。
  • 返回值:比较函数的返回值决定了两个元素的相对顺序。如果 *(int *)p1 - *(int *) p2 的结果小于 0,说明 p1 指向的元素小于 p2 指向的元素;如果结果等于 0,说明两个元素相等;如果结果大于 0,说明 p1 指向的元素大于 p2 指向的元素。

2. main 函数

2.1 数组定义与变量声明
  • 定义了一个包含 10 个整数的数组 arr,初始化为 { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 }
  • 声明一个整型变量 i 用于后续的循环操作。
2.2 调用 qsort 函数进行排序
  • qsort 函数的原型为 void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
    • base:指向要排序的数组的第一个元素的指针,这里传入 arr,即数组的首地址。
    • nmemb:数组中元素的个数,通过 sizeof(arr) / sizeof(arr[0]) 计算得到,即数组的总字节数除以单个元素的字节数。
    • size:数组中每个元素的大小(以字节为单位),这里是 sizeof(int),表示每个整数元素占用的字节数。
    • compar:指向比较函数的指针,这里传入 int_cmp,即前面定义的比较函数。
2.3 输出排序后的数组
  • 使用 for 循环遍历排序后的数组 arr,并使用 printf 函数将每个元素输出,元素之间用空格分隔。
  • 最后使用 printf("\n"); 输出一个换行符,使输出结果更美观。
  • main 函数返回 0 表示程序正常结束。

    综上所述,这段代码的主要功能是使用 qsort 函数对一个整数数组进行排序,并将排序后的数组元素输出。通过自定义比较函数 int_cmp,可以灵活地控制排序的顺序。

3. qsort函数的模拟实现

    下面就是模拟qsort函数的模拟实现的

#include<stdio.h>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]);}
}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 (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0){ //  交换两个元素Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);}}}
}void test1()//需要比较的
{int arr[] = { 9,8,7,4,6,2,10,1,3,5 };int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);//cmp_int == &cmp_int 地址需要指针接收。print_arr(arr, sz);
}
int main()
{test1();return 0;
}

1. 比较函数

1.1 cmp_int 函数

int cmp_int(const void* p1, const void* p2)

      return (*(int*)p1 - *(int*)p2); 

}

  • 功能:用于比较两个整型数据的大小。
  • 参数
    • p1 和 p2const void* 类型的指针,这是为了使函数具有通用性,可以接收任意类型的指针。
  • 实现细节
    • (int*)p1 和 (int*)p2:将 void* 类型的指针强制转换为 int* 类型,以便可以解引用获取整型值。
    • *(int*)p1 和 *(int*)p2:解引用指针,得到对应的整型值。
    • *(int*)p1 - *(int*)p2:计算两个整型值的差。如果结果大于 0,表示 p1 指向的值大于 p2 指向的值;如果结果小于 0,表示 p1 指向的值小于 p2 指向的值;如果结果等于 0,表示两个值相等。

2. 打印函数

2.1 print_arr 函数

void print_arr(int arr[], int sz)

{

    int i = 0;

    for (i = 0; i < sz; i++) 

  { 

   printf("%d ", arr[i]);

  } 

}

  • 功能:用于打印整型数组的元素。
  • 参数
    • arr:整型数组。
    • sz:数组的元素个数。
  • 实现细节
    • 使用 for 循环遍历数组的每个元素。
    • 使用 printf 函数以整数格式打印每个元素,并在元素之间添加一个空格。

3. 交换函数 Swap

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++; 

      } 

}

  • 功能:用于交换两个内存块的内容。
  • 参数
    • buf1 和 buf2char* 类型的指针,指向要交换的两个内存块。
    • width:每个内存块的字节数。
  • 实现细节
    • 使用 for 循环逐字节交换两个内存块的内容。
    • 通过 char 类型的临时变量 tmp 来存储中间值,实现交换。
    • 每次交换一个字节后,将指针 buf1 和 buf2 向后移动一个字节,直到交换完整个内存块。

4. 冒泡排序函数 bubble_sort

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 (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0) {Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);}}}
}
  • 功能:实现通用的冒泡排序算法。
  • 参数
    • basevoid* 类型的指针,指向待排序数组的起始地址,使用 void* 类型可以接收任意类型的数组。
    • sz:数组的元素个数。
    • width:每个元素的字节数。
    • cmp:函数指针,指向一个比较函数,用于确定元素的顺序。
  • 实现细节
    • 外层 for 循环控制排序的趟数,共进行 sz - 1 趟。
    • 内层 for 循环进行每一趟的两两比较,比较相邻的两个元素。
    • cmp((char*)base + j * width, (char*)base + (j + 1) * width):调用比较函数 cmp 比较相邻两个元素的大小。(char*)base + j * width 和 (char*)base + (j + 1) * width 分别计算相邻两个元素的地址。
    • 如果比较结果大于 0,表示顺序不正确,调用 Swap 函数交换这两个元素的位置。

5. 测试函数

5.1 test1 函数

void test1()

                 int arr[] = { 9,8,7,4,6,2,10,1,3,5 };

                 int sz = sizeof(arr) / sizeof(arr[0]);

                 bubble_sort(arr, sz, sizeof(arr[0]), cmp_int); 

                 print_arr(arr, sz); 

}

 

  • 功能:创建一个整型数组,调用 bubble_sort 函数对数组进行排序,然后调用 print_arr 函数打印排序后的数组。
  • 实现细节
    • int arr[] = { 9,8,7,4,6,2,10,1,3,5 };:定义一个包含 10 个整数的数组。
    • int sz = sizeof(arr) / sizeof(arr[0]);:计算数组的元素个数。
    • bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);:调用 bubble_sort 函数对数组进行排序,使用 cmp_int 函数作为比较规则。
    • print_arr(arr, sz);:调用 print_arr 函数打印排序后的数组。

6. 主函数

    int main()

{

      test1();

      return 0;

}

  • 功能:程序的入口点,调用 test1 函数进行测试。
  • 返回值:返回 0 表示程序正常结束。

下面是我自己画的图(画的不是特别好)方便大家理解理解:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/12742.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

llama.cpp GGUF 模型格式

llama.cpp GGUF 模型格式 1. Specification1.1. GGUF Naming Convention (命名规则)1.1.1. Validating Above Naming Convention 1.2. File Structure 2. Standardized key-value pairs2.1. General2.1.1. Required2.1.2. General metadata2.1.3. Source metadata 2.2. LLM2.2.…

【C++】STL——vector底层实现

目录 &#x1f495; 1.vector三个核心 &#x1f495;2.begin函数&#xff0c;end函数的实现&#xff08;简单略讲&#xff09; &#x1f495;3.size函数&#xff0c;capacity函数的实现 &#xff08;简单略讲&#xff09; &#x1f495;4.reserve函数实现 &#xff08;细节…

Pinia状态管理

1、为什么要使用Pinia&#xff1f; Pinia 是 Vue 的存储库&#xff0c;它允许跨组件/页面共享状态 Pinia 最初是为了探索 Vuex 的下一次迭代会是什么样子&#xff0c;结合了 Vuex 5 核心团队讨论中的许多想法。最终&#xff0c;我们意识到 Pinia 已经实现了我们在 Vuex 5 中想…

TCP | RFC793

注&#xff1a;本文为 “ RFC793” 相关文章合辑。 RFC793-TCP 中文翻译 编码那些事儿已于 2022-07-14 16:02:16 修改 简介 翻译自&#xff1a; RFC 793 - Transmission Control Protocol https://datatracker.ietf.org/doc/html/rfc793 TCP 是一个高可靠的主机到主机之间…

VMware Workstation Pro安装了Ubuntu 24.04实现与Windows10之间的复制粘贴

windows10安装了VMware Workstation Pro&#xff0c;虚拟机上安装Ubuntu 24.04&#xff0c;想Ubuntu和windows之间实现复制粘贴&#xff0c;便于互相执行下面命令&#xff1a; sudo apt-get autoremove open-vm-tools //卸载已有的工具 sudo apt-get install open-vm-tools …

idea分析sql性能

idea对sql进行解析&#xff0c;可有效展示sql的性能问题&#xff0c;比直接看命令好。&#xff08;专业版才有数据库功能&#xff0c;可以在淘宝买&#xff0c;10块就好了&#xff09; 如下&#xff1a; 发现一个全表扫描&#xff0c;耗时6s&#xff0c;对应sql语句可以查看&…

智慧园区系统集成解决方案提升管理效率与智能化水平的新探索

内容概要 随着科技的不断进步&#xff0c;智慧园区管理系统已成为现代园区管理的重要组成部分。在众多系统中&#xff0c;快鲸智慧园区(楼宇)管理系统凭借其独特的优势&#xff0c;获得了广泛关注。该系统通过全面整合园区内各类智能设备&#xff0c;大幅提升了管理效率和智能…

Linux 的 sysfs 伪文件系统介绍【用户可以通过文件操作与内核交互(如调用内核函数),而无需编写内核代码】

1. 什么是 sysfs伪文件系统&#xff1f; sysfs 是 Linux 内核提供的 伪文件系统&#xff0c;用于向用户空间暴露内核对象的信息和控制接口。它是 procfs 的补充&#xff0c;主要用于管理 设备、驱动、内核子系统 等信息&#xff0c;使用户可以通过文件操作&#xff08;如用户空…

TCP编程

1.socket函数 int socket(int domain, int type, int protocol); 头文件&#xff1a;include<sys/types.h>&#xff0c;include<sys/socket.h> 参数 int domain AF_INET: IPv4 Internet protocols AF_INET6: IPv6 Internet protocols AF_UNIX, AF_LOCAL : Local…

springboot+vue+uniapp的校园二手交易小程序

开发语言&#xff1a;Java框架&#xff1a;springbootuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#…

【PyQt】使用PyQt5和Matplotlib实现的CSV数据可视化工具

使用PyQt5和Matplotlib实现的CSV数据可视化工具 界面展示 代码 import sys from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,QHBoxLayout, QPushButton, QComboBox, QFileDialog,QLabel, QMessageBox) import pandas as pd from matplotlib.f…

软件工程导论三级项目报告--《软件工程》课程网站

《软件工程》课程网站 摘要 本文详细介绍了《软件工程》课程网站的设计与实现方案&#xff0c;包括可行性分析、需求分析、总体设计、详细设计、测试用例。首先&#xff0c;通过可行性分析从各方面确认了该工程的可实现性&#xff0c;接着需求分析明确了系统的目标用户群和功能…

数据结构-堆和PriorityQueue

1.堆&#xff08;Heap&#xff09; 1.1堆的概念 堆是一种非常重要的数据结构&#xff0c;通常被实现为一种特殊的完全二叉树 如果有一个关键码的集合K{k0,k1,k2,...,kn-1}&#xff0c;把它所有的元素按照完全二叉树的顺序存储在一个一维数组中&#xff0c;如果满足ki<k2i…

Spring @Lazy:延迟初始化,为应用减负

在Spring框架中&#xff0c;Lazy注解的作用非常直观&#xff0c;它就是用来告诉Spring容器&#xff1a;“嘿&#xff0c;这个Bean嘛&#xff0c;先别急着创建和初始化&#xff0c;等到真正需要用到的时候再弄吧&#xff01;” 默认情况下&#xff0c;Spring容器在启动时会立即创…

SynchronousQueue 与 LinkedBlockingQueue区别及应用场景

文章目录 前言认识SynchronousQueue基本对比及比较1. **基本特性**2. **内部实现**3. **性能特点**4. **使用场景**5. **总结对比** SynchronousQueue案例JDK应用案例案例1&#xff1a;SynchronousQueue的简单用例案例2&#xff1a;SynchronousQueue公平锁、非公平锁案例案例3&…

MySQL 缓存机制与架构解析

目录 一、MySQL缓存机制概述 二、MySQL整体架构 三、SQL查询执行全流程 四、MySQL 8.0为何移除查询缓存&#xff1f; 五、MySQL 8.0前的查询缓存配置 六、替代方案&#xff1a;应用层缓存与优化建议 总结 一、MySQL缓存机制概述 MySQL的缓存机制旨在提升数据访问效率&am…

【C++】STL——list的使用

目录 &#x1f495;1.带头双向链表List &#x1f495;2.list用法介绍 &#x1f495;3.list的初始化 &#x1f495;4.size函数与resize函数 &#x1f495;5.empty函数 &#x1f495;6.front函数与back函数 &#x1f495;7.push_front,push_back,pop_front,pop_back函数…

MySQL知识点总结(一)

1.SQL分类 数据定义&#xff08;DDL&#xff09;:创/改/删/名/清&#xff08;cadrt&#xff09; 数据库对象&#xff1a;表/视图/存储/函数/触发器/事件 数据操作&#xff08;DML&#xff09;&#xff1a;增/删/改/查&#xff08;idus&#xff09; 操作数据库对象 数据控制&…

快速幂,错位排序笔记

​ 记一下刚学明白的快速幂和错位怕排序的原理和代码 快速幂 原理&#xff1a; a^b (a^&#xff08;b/2&#xff09;) ^ 2&#xff08;b为偶数&#xff09; a^b a*&#xff08;a^&#xff08; (b-1)/2&#xff09;&#xff09;^2&#xff08;b为奇数&#xff09; 指数为偶数…

【缴纳过路费——并查集】

题目 分析 题目乍看一下像最短路径的求解。但是从复杂度上面分析应该不是这样。题目关键点在于“路程花费是最贵的那一段” 和 “最少花费在区间内”。 合起来就是两点所有路程中最便宜的最贵段&#xff0c;要在区间内&#xff1a;如果按权值从小到大遍历边&#xff0c;能合并连…