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 类型的参数 ab,并返回它们的和。
  • 该函数的参数和返回值类型与前面定义的 Callback 类型相匹配,所以它可以作为 Callback 类型的回调函数使用。
减法函数 subtract

int subtract(int a, int b)

{

return a - b;

}

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

3. 定义主调函数

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

{

return func(a, b);

}

  • operate 是主调函数,它接受三个参数:两个 int 类型的参数 ab,以及一个 Callback 类型的参数 func
  • Callback func 等价于 int(*func)(int, int),即 func 是一个函数指针,它指向一个符合 Callback 类型定义的函数。
  • 在函数体中,通过 func(a, b) 调用 func 所指向的函数,并将 ab 作为参数传递给该函数,最后返回该函数的返回值。
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 类型的变量 xy,分别赋值为 10 和 5。
  • 调用 operate 函数进行加法运算:
    • operate(x, y, add) 中,将 xy 作为前两个参数传递给 operate 函数,将 add 函数名作为回调函数传递给 operate 函数。在 C 语言中,函数名可以隐式转换为函数的地址,所以 add 实际上传递的是 add 函数的地址。
    • operate 函数内部会通过函数指针 func 调用 add 函数,计算 xy 的和,并将结果返回给 result1
    • 使用 printf 函数输出加法结果。
  • 调用 operate 函数进行减法运算:
    • 类似地,operate(x, y, subtract)subtract 函数的地址作为回调函数传递给 operate 函数。
    • operate 函数内部通过函数指针 func 调用 subtract 函数,计算 xy 的差,并将结果返回给 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 函数能够处理不同类型的数组。这里的 p1p2 就是指向要比较的两个元素的指针。
  • 类型转换 :在比较函数内部,由于 p1p2const 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);

}

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

2. 打印函数

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

}

}

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

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

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

相关推荐
小仇学长7 分钟前
嵌入式八股文面试题(一)C语言部分
c语言·c++·面试·嵌入式·八股文
生命不息战斗不止(王子晗)8 分钟前
JVM 四虚拟机栈
开发语言·jvm
Lsland..11 分钟前
Spring Boot 配置文件详解:YAML vs Properties
java·前端·spring boot
比特在路上13 分钟前
蓝桥杯之c++入门(一)【C++入门】
c语言·c++·蓝桥杯
NoneCoder17 分钟前
JavaScript系列(54)--性能优化技术详解
开发语言·javascript·性能优化
大模型铲屎官20 分钟前
【HTML性能优化】提升网站加载速度:GZIP、懒加载与资源合并
前端·性能优化·html·gzip·懒加载·网站加载·资源合并
码农白衣23 分钟前
前端八股CSS:盒模型、CSS权重、+与~选择器、z-index、水平垂直居中、左侧固定,右侧自适应、三栏均分布局
前端·css·学习
laocooon52385788640 分钟前
HTML 会动的蜡烛。
前端·css·css3
小林熬夜学编程1 小时前
【MySQL】第一弹---MySQL 在 Centos 7环境安装
linux·开发语言·数据库·mysql·算法
xiaocang6688881 小时前
如何使用Python调用大语言模型的API接口?
开发语言·python·语言模型