深入理解指针与回调函数:从基础到实践

news2025/3/9 4:50:54

引言

在C语言中,指针和回调函数是两个非常重要的概念。指针为我们提供了直接操作内存的能力,而回调函数则为我们提供了一种灵活的编程方式,使得我们可以将函数作为参数传递给其他函数,从而实现更加模块化和可复用的代码。本文将深入探讨指针和回调函数的概念,并通过实际的代码示例来展示它们的应用。

1. 回调函数是什么?


1.1 回调函数的定义


回调函数(Callback Function)是一种通过函数指针调用的函数。简单来说,回调函数是一个在特定事件或条件发生时被调用的函数。回调函数不是由该函数的实现方直接调用,而是通过函数指针传递给另一个函数,由该函数在适当的时机调用。

1.2 回调函数的作用


回调函数的主要作用是解耦代码。通过将函数作为参数传递,我们可以将代码的逻辑分离,使得代码更加模块化和可复用。例如,在一个计算器程序中,我们可以将加法、减法、乘法、除法等操作封装成不同的函数,然后通过回调函数的方式将这些函数传递给一个统一的处理函数,从而避免重复的代码。

1.3 回调函数的示例


让我们通过一个简单的例子来理解回调函数的使用。假设我们有一个计算器程序,用户可以选择不同的操作(加法、减法、乘法、除法),程序会根据用户的选择执行相应的操作。

1.3.1 使用回调函数改造前
#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;
}

int main() {
    int x, y;
    int input = 1;
    int ret = 0;

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



在这个代码中,我们使用了switch语句来根据用户的选择调用不同的函数。虽然这个代码可以正常工作,但是它存在一个问题:每次添加一个新的操作时,我们都需要修改switch语句,这会导致代码的冗余和不易维护。

1.3.2 使用回调函数改造后


为了简化代码,我们可以使用回调函数的方式将不同的操作函数作为参数传递给一个统一的处理函数。这样,当我们添加新的操作时,只需要添加新的函数,而不需要修改switch语句。

#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 calculate(int (*func)(int, int)) {
    int x, y;
    printf("输入操作数:");
    scanf("%d %d", &x, &y);
    int ret = func(x, y);
    printf("ret = %d\n", ret);
}

int main() {
    int input = 1;

    do {
        printf("**********\n");
        printf(" 1:add \n");
        printf(" 2:sub \n");
        printf(" 3:mul \n");
        printf(" 4:div \n");
        printf(" 0:exit \n");
        printf("**********\n");
        printf("请选择:");
        scanf("%d", &input);

        switch (input) {
            case 1:
                calculate(add);
                break;
            case 2:
                calculate(sub);
                break;
            case 3:
                calculate(mul);
                break;
            case 4:
                calculate(div);
                break;
            case 0:
                printf("退出程序\n");
                break;
            default:
                printf("选择错误\n");
                break;
        }
    } while (input);

    return 0;
}


在这个改造后的代码中,我们定义了一个calculate函数,它接受一个函数指针作为参数。这个函数指针指向一个接受两个int参数并返回int的函数。在main函数中,我们根据用户的选择将不同的操作函数传递给calculate函数,从而实现了代码的简化和模块化。

2. qsort使用举例


2.1 qsort函数简介


qsort是C标准库中的一个函数,用于对数组进行排序。它的原型如下:

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


base:指向要排序的数组的第一个元素的指针。

nmemb:数组中元素的个数。

size:数组中每个元素的大小(以字节为单位)。

compar:指向比较函数的指针。这个函数用于比较两个元素的大小。

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


让我们通过一个简单的例子来演示如何使用qsort函数对整型数组进行排序。

#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;
}


在这个例子中,我们定义了一个int_cmp函数,用于比较两个整型数的大小。然后我们使用qsort函数对数组arr进行排序。qsort函数会根据int_cmp函数的返回值来决定元素的顺序。

2.3 使用qsort排序结构数据


qsort函数不仅可以用于排序基本数据类型,还可以用于排序结构体数据。让我们通过一个例子来演示如何使用qsort函数对结构体数组进行排序。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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

    for (int i = 0; i < sz; i++) {
        printf("%s %d\n", s[i].name, s[i].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);

    for (int i = 0; i < sz; i++) {
        printf("%s %d\n", s[i].name, s[i].age);
    }
}

int main() {
    test2();
    test3();
    return 0;
}


在这个例子中,我们定义了一个Stu结构体,包含学生的姓名和年龄。我们定义了两个比较函数cmp_stu_by_age和cmp_stu_by_name,分别用于按照年龄和姓名对学生进行排序。然后我们使用qsort函数对结构体数组进行排序,并输出排序后的结果。

3. qsort函数的模拟实现

3.1 冒泡排序简介


冒泡排序是一种简单的排序算法。它重复地遍历要排序的数组,比较相邻的两个元素,如果它们的顺序错误就交换它们。遍历数组的工作会重复进行,直到没有需要交换的元素为止。

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


我们可以使用冒泡排序的思想来模拟实现qsort函数。为了支持不同类型的数组,我们需要使用void *指针和回调函数。

#include <stdio.h>

int int_cmp(const void *p1, const void *p2) {
    return (*(int *)p1 - *(int *)p2);
}

void _swap(void *p1, void *p2, int size) {
    int i = 0;
    for (i = 0; i < size; i++) {
        char tmp = *((char *)p1 + i);
        *((char *)p1 + i) = *((char *)p2 + i);
        *((char *)p2 + i) = tmp;
    }
}

void bubble(void *base, int count, int size, int (*cmp)(void *, void *)) {
    int i = 0;
    int j = 0;
    for (i = 0; i < count - 1; i++) {
        for (j = 0; j < count - i - 1; j++) {
            if (cmp((char *)base + j * size, (char *)base + (j + 1) * size) > 0) {
                _swap((char *)base + j * size, (char *)base + (j + 1) * size, size);
            }
        }
    }
}

int main() {
    int arr[] = {1, 3, 5, 7, 9, 2, 4, 6, 8, 0};
    int i = 0;

    bubble(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;
}


在这个代码中,我们定义了一个bubble函数,它接受一个void *指针作为数组的基地址,数组的元素个数,每个元素的大小,以及一个比较函数。bubble函数使用冒泡排序算法对数组进行排序。为了支持不同类型的数组,我们使用void *指针和_swap函数来交换元素。

4. 总结


通过本文的学习,我们深入理解了指针和回调函数的概念,并通过实际的代码示例展示了它们的应用。回调函数为我们提供了一种灵活的编程方式,使得我们可以将函数作为参数传递给其他函数,从而实现更加模块化和可复用的代码。qsort函数是C标准库中的一个强大的排序函数,它通过回调函数的方式支持对不同类型的数据进行排序。我们还通过模拟实现qsort函数,进一步加深了对指针和回调函数的理解。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

linux磁盘非lvm分区

linux磁盘非lvm分区 类似于windows划分C盘、D盘&#xff0c;并且不需要多个磁盘空间合一 图形化直接分区 通过gparted 这个提供直观的图形化分区&#xff0c;类似windows的磁盘管理工具 下载方式&#xff1a; 乌班图/debian系列&#xff1a; sudo apt install gparted红帽…

Linux:文件描述符与重定向

目录 一、文件描述符 1.文件内核对象 2.文件描述符分配原则 二、文件重定向 1.重定向的现象 输出重定向 输入重定向 dup2 2.重定向的使用 三、标准输出和标准错误 继上篇文章中&#xff0c;我们了解了fd打印的值为文件描述符&#xff0c;那么它还有什么作用呢&…

C# 开发工具Visual Studio下载和安装

开发环境与工具 C#的主要开发环境是Visual Studio&#xff0c;这是一个功能强大的集成开发环境&#xff08;IDE&#xff09;&#xff0c;集成了代码编辑、调试、项目管理、版本控制等功能。此外&#xff0c;Visual Studio Code也是一个轻量级的跨平台代码编辑器&#xff0c;支…

网络安全ctf试题 ctf网络安全大赛真题

MISC 1 签到 难度 签到 复制给出的flag输入即可 2 range_download 难度 中等 flag{6095B134-5437-4B21-BE52-EDC46A276297} 0x01 分析dns流量&#xff0c;发现dns && ip.addr1.1.1.1存在dns隧道数据&#xff0c;整理后得到base64: cGFzc3dvcmQ6IG5zc195eWRzIQ 解…

Embedding技术:DeepWalkNode2vec

引言 在推荐系统中&#xff0c;Graph Embedding技术已经成为一种强大的工具&#xff0c;用于捕捉用户和物品之间的复杂关系。本文将介绍Graph Embedding的基本概念、原理及其在推荐系统中的应用。 什么是Graph Embedding&#xff1f; Graph Embedding是一种将图中的节点映射…

基于IMM算法的目标跟踪,四模型IMM|三维环境|4个模型分别是:CV、左转CT、右转CT、CA(基于EKF,订阅专栏后可获得完整源代码)

这段MATLAB代码实现了基于交互多模型(IMM)算法的目标跟踪,结合了四种运动模型(匀速直线、左转圆周、右转圆周和匀加速直线)。通过定义状态方程、生成带噪声的测量数据,以及执行IMM迭代,该代码有效地实现了多模型的状态估计和融合。最终,用户可以通过可视化结果观察目标…

前端开发10大框架深度解析

摘要 在现代前端开发中&#xff0c;框架的选择对项目的成功至关重要。本文旨在为开发者提供一份全面的前端框架指南&#xff0c;涵盖 React、Vue.js、Angular、Svelte、Ember.js、Preact、Backbone.js、Next.js、Nuxt.js 和 Gatsby。我们将从 简介、优缺点、适用场景 以及 实际…

图像形成与计算机视觉基础

1. 图像形成的基本原理 图像形成是物理世界与传感器&#xff08;如胶片、CCD/CMOS&#xff09;交互的过程&#xff0c;核心是光线的传播与记录。 1.1 直接放置胶片模型 物理原理&#xff1a;物体表面反射的光线直接照射到胶片上&#xff0c;但无任何遮挡或聚焦机制。 问题&a…

Spring Boot 缓存最佳实践:从基础到生产的完整指南

Spring Boot 缓存最佳实践&#xff1a;从基础到生产的完整指南 引言 在现代分布式系统中&#xff0c;缓存是提升系统性能的银弹。Spring Boot 通过 spring-boot-starter-cache​ 模块提供了开箱即用的缓存抽象&#xff0c;但如何根据业务需求实现灵活、可靠的缓存方案&#xf…

Ubuntu20.04双系统安装及软件安装(一):系统安装

Ubuntu20.04双系统安装及软件安装&#xff08;一&#xff09;&#xff1a;系统安装 Ubuntu系统卸载Ubuntu20.04安装BIOS进入系统安装 许久没写博客了&#xff0c;今天开始重新回归了。首先记录我在双系统上重装Ubuntu20.04的安装过程记录以及个人见解。 Ubuntu系统卸载 参考双…

Linux14-io多路复用

UDP:单循环服务器,服务器同一时刻只能响应一个客户端的请求 TCP:并发服务器,服务器同一时刻只能响应多个客户端的请求 一、构建TCP并发服务器 让TCP服务端具备同时响应多个客户端的能力。 1.多进程 资源消耗大,同资源平台下,并发量小。 2.多线程 创建线程、进程,比…

Next.js项目实战-ai助手帮我写文章发布视频第1节(共89节)

&#x1f602;Ai在国内外已经杀疯了&#xff0c;老板要求我们把速度再提升快一些&#xff0c;哪怕是几秒&#xff0c;几百毫秒也行&#xff5e;现在&#xff0c;马上就要&#xff0c;就地就要&#xff0c;只好搬出前端服务端(大保健)&#x1f613;。没错&#xff0c;今天我要分…

探秘Transformer系列之(9)--- 位置编码分类

探秘Transformer系列之&#xff08;9&#xff09;— 位置编码分类 文章目录 探秘Transformer系列之&#xff08;9&#xff09;--- 位置编码分类0x00 概述0x01 区别1.1 从直观角度来看1.2 从模型处理角度来看1.3 优劣 0x02 绝对位置编码2.1 基础方案2.2 训练式2.3 三角函数式2.4…

笔记四:C语言中的文件和文件操作

Faye&#xff1a;只要有正确的伴奏&#xff0c;什么都能变成好旋律。 ---------《寻找天堂》 目录 一、文件介绍 1.1程序文件 1.2 数据文件 1.3 文件名 二、文件的打开和关闭 2.1 文件指针 2.2.文件的打开和关闭 2.3 文件读取结束的判定 三、 文件的顺序读写 3.1 顺序读写…

Zabbix+Deepseek实现AI告警分析(非本地部署大模型版)

目录 前言技术架构DeepSeek API获取1. 注册账号2. 申请API-Key Zabbix告警AI分析 实现1. 创建Scripts2. Scripts关键参数说明3. 需要注意 测试参考链接 前言 最近手伤了&#xff0c;更新频率下降…… 近期在Zabbix社区看到了一篇文章&#xff1a;张世宏老师分享的《Zabbix告警分…

国产NAS系统飞牛云fnOS深度体验:从运维面板到博客生态全打通

文章目录 前言1. 飞牛云本地部署1Panel2. 1Panel功能介绍3. 公网访问1Panel控制面板4. 固定1Panel公网地址5. 1Panel搭建Halo博客6. 公网访问Halo个人博客 前言 嘿&#xff0c;小伙伴们&#xff01;是不是厌倦了服务器管理的繁琐和搭建个人网站的复杂&#xff1f;今天就来一场…

使用QT + 文件IO + 鼠标拖拽事件 + 线程 ,实现大文件的传输

第一题、使用qss&#xff0c;通过线程&#xff0c;使进度条自己动起来 mythread.h #ifndef MYTHREAD_H #define MYTHREAD_H#include <QObject> #include <QThread> #include <QDebug>class mythread : public QThread {Q_OBJECT public:mythread(QObject* …

博查搜索API日调用量突破3000万次,达到Bing API的1/3。

根据第三方机构统计&#xff0c;2024年Bing Search API 全球日均调用量为1.1亿次。截至2025年3月&#xff0c;博查 Search API日均调用量已达到3000万次&#xff08;约为Bing的1/3&#xff09;&#xff0c;承接着国内AI应用60%的联网搜索请求。

[内网安全] Windows 本地认证 — NTLM 哈希和 LM 哈希

关注这个专栏的其他相关笔记&#xff1a;[内网安全] 内网渗透 - 学习手册-CSDN博客 0x01&#xff1a;SAM 文件 & Windows 本地认证流程 0x0101&#xff1a;SAM 文件简介 Windows 本地账户的登录密码是存储在系统本地的 SAM 文件中的&#xff0c;在登录 Windows 的时候&am…

输电线路杆塔倾斜智能监测:守护电网安全的智慧之眼

​ ​2023年夏&#xff0c;某超高压输电线路突发倒塔事故&#xff0c;导致三省市大面积停电&#xff0c;直接经济损失超2.3亿元。事后调查显示&#xff0c;杆塔倾斜角度早已超出安全阈值&#xff0c;但传统巡检未能及时发现。这个刺痛行业的案例&#xff0c;揭开了电力设施监…