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 表示程序正常结束。

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Uniapp-Vue3】创建DB schema数据表结构

右键uniCloud文件下的database文件&#xff0c;点击“新建DB schema”&#xff0c;选择模板&#xff0c;修改文件名&#xff0c;点击“创建” 创建完成后会出现对应的文件&#xff0c;进入该文件进行配置 对文件中的必填选项&#xff0c;用户权限&#xff0c;字段进行配置 其…

Java基础进阶-水仙花数

/* 功能&#xff1a;求水仙花数&#xff0c;打印并统计总个数。 思路&#xff1a; 水仙花数是定义范围100-999&#xff0c;满足每个位上的数子的3次方相加和等于这个数 第一步&#xff1a;循环遍历数据范围 第二步&#xff1b;取出当前数字的个位&#xff0c;十位&#xff0c;百…

DDD - 领域事件_解耦微服务的关键

文章目录 Pre领域事件的核心概念领域事件的作用领域事件的识别领域事件的技术实现领域事件的运行机制案例领域事件驱动的优势 Pre DDD - 微服务设计与领域驱动设计实战(中)_ 解决微服务拆分难题 EDA - Spring Boot构建基于事件驱动的消息系统 领域事件的核心概念 领域事件&a…

MacBook Pro(M1芯片)Qt环境配置

MacBook Pro&#xff08;M1芯片&#xff09;Qt环境配置 1、准备 试图写一个跨平台的桌面应用&#xff0c;此时想到了使用Qt&#xff0c;于是开始了搭建开发环境&#xff5e; 在M1芯片的电脑上安装&#xff0c;使用brew工具比较方便 Apple Silicon&#xff08;ARM/M1&#xf…

简单本地部署deepseek(软件版)

Download Ollama on Windows 下载 下载安装 winr 输入 cmd 然后输入ollama -v&#xff0c;出现ollama版本&#xff0c;安装成功 deepseek-r1 选择1.5b 输入 cmd 下面代码 ollama run deepseek-r1:1.5b 删除deepseek的代码如下&#xff1a; ollama rm deepseek-r1:1.5b 使用…

Linux生成自签证书【Nginx】

&#x1f468;‍&#x1f393;博主简介 &#x1f3c5;CSDN博客专家   &#x1f3c5;云计算领域优质创作者   &#x1f3c5;华为云开发者社区专家博主   &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社区&#xff1a;运维交流社区 欢迎大家的加入&#xff01…

Docker基础以及单体实战

Docker 一、Docker1.1 Docker组成1.2 Dcoker运行图1.3 名称空间Namepace 1.4 docker、Docker compose、kubermetes 二、Docker安装2.1 在线Docker安装2.2 使用官方通用安装脚本2.3 二进制安装Docker三、Docker基础命令3.1 启动类3.2 镜像类3.3 容器类3.4 网络类3.5 Docker comp…

MySQL表的CURD

目录 一、Create 1.1单行数据全列插入 1.2多行数据指定列插入 1.3插入否则更新 1.4替换 2.Retrieve 2.1 select列 2.1.1全列查询 2.1.2指定列查询 2.1.3查询字段为表达式 2.1.4为查询结果指定别名 2.1.5结果去重 2.2where条件 2.3结果排序 2.4筛选分页结果 三…

如何优化垃圾回收机制?

垃圾回收机制 掌握 GC 算法之前&#xff0c;我们需要先弄清楚 3 个问题。第一&#xff0c;回收发生在哪里&#xff1f;第二&#xff0c;对象在 什么时候可以被回收&#xff1f;第三&#xff0c;如何回收这些对象&#xff1f; 回收发生在哪里&#xff1f; JVM 的内存区域中&…

基于SpringBoot的体检预约管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

PostgreSQL / PostGIS:创建地理要素

PostGIS详细教程可以参考官方文档&#xff1a;https://postgis.net/workshops/zh_Hans/postgis-intro/&#xff0c;并且官方文档提供了练习数据、教程、PPT版本教程。我这里参考QGIS文档中关于PostGIS的教程进行学习。 PostGIS 可以被认为是一组数据库内函数的集合&#xff0c…

embeddingbag词袋

文章目录 1. embeddingbag2. pytorch 1. embeddingbag 词袋embeddingbag 是在embedding词表的基础上演变起来的,nn.embedding的作用是构建一个词表&#xff0c;通过输入index序号来索引词对应的词向量&#xff0c;是可以根据词索引index进行forward计算的&#xff0c;embeddin…

分享|通过Self-Instruct框架将语言模型与自生成指令对齐

结论 在大型 “指令调整” 语言模型依赖的人类编写指令数据存在数量、多样性和创造性局限&#xff0c; 从而阻碍模型通用性的背景下&#xff0c; Self - Instruct 框架&#xff0c; 通过 自动生成 并 筛选指令数据 微调预训练语言模型&#xff0c; 有效提升了其指令遵循能…

无穿戴动捕数字人互动方案 展馆展览创新引擎,推动文旅数字化转型

随着经济社会的发展和文旅融合的加速推进&#xff0c;“博物馆热”已成为一种不可忽视的社会现象&#xff0c;成为文化领域最具潜力的增长点之一。在信息技术的迅猛推动下&#xff0c;科技赋能的博物馆展览日益受到公众的关注&#xff0c;其中&#xff0c;“元宇宙”等创新概念…

5.6 Mybatis代码生成器Mybatis Generator (MBG)实战详解

文章目录 前言一、Mybatis Generator简介二、Maven插件运行方式三、生成配置 generatorConfig.xml MyBatis3Simple风格MyBatis3风格MyBatis3DynamicSql风格 四、Java代码运行方式五、MGB生成全部表六、增加Ext包七、Git提交总结 前言 本文我们主要实战Mybatis官方的代码生成器…

C++六大默认成员函数

C六大默认成员函数 默认构造函数默认析构函数RAII技术RAII的核心思想优点示例应用场景 默认拷贝构造深拷贝和浅拷贝 默认拷贝赋值运算符移动构造函数&#xff08;C11起&#xff09;默认移动赋值运算符&#xff08;C11起&#xff09;取地址及const取地址操作符重载取地址操作符重…

Ext文件系统

文件内容属性 被打开的文件在内存中&#xff0c;没有被打开的文件在磁盘里文件系统的工作就是根据路径帮我们找到在磁盘上的文件 磁盘&#xff08;硬件&#xff09; 磁盘的存储结构 磁头在传动臂的运动下共同进退&#xff0c;向磁盘写入的时候是向柱面批量写入的 OS文件系统访…

AURIX TC275学习笔记3 官方例程 (UART LED WDT)

文章目录 参考资料1. ASCLIN_UART_12. GPIO_LED_Button_13. WDT (Watch Dog Timer) 参考资料 AURIX TC275学习笔记1 资料收集Getting Started with AURIX™ Development Studio 官方帮助文档happy hacking for TC275! 硬件平台使用AURIX™ TC275 Lite 套件&#xff0c;按照参…

免费接入DeepSeek等多种大模型

核心代码 import cn.hutool.core.collection.CollUtil; import com.tool4j.pasteshare.entity.params.AiParams; import com.tool4j.pasteshare.entity.params.Message; import com.tool4j.pasteshare.service.AiCompletionService; import com.tool4j.pasteshare.util.deepse…

PostIn简明安装教程(入门级)

PostIn是一款开源免费的接口管理工具&#xff0c;包含接口调试、接口文档设计、数据MOCK等模块&#xff0c;本文将介绍如何快速安装配置&#xff0c;以快速入门上手。 1、服务端安装 私有部署版本支持多种操作系统&#xff0c;包括 Linux、Docker、Windows及macOS&#xff0c;…