吃透指针通用用法:回调函数与 qsort 的使用和模拟

🏠个人主页:黎雁

🎬作者简介:C/C++/JAVA后端开发学习者

❄️个人专栏:C语言数据结构(C语言)EasyX游戏规划

✨ 从来绝巘须孤往,万里同尘即玉京

文章目录

    • [前景回顾:前四篇指针核心速记 📝](#前景回顾:前四篇指针核心速记 📝)
    • [一、回调函数:通过函数指针调用的函数 📞](#一、回调函数:通过函数指针调用的函数 📞)
      • [1. 回调函数的定义与理解](#1. 回调函数的定义与理解)
      • [2. 代码示例:计算器中的回调函数](#2. 代码示例:计算器中的回调函数)
      • [3. 生活类比理解回调函数](#3. 生活类比理解回调函数)
    • [二、qsort函数:通用排序的利器 ⚡](#二、qsort函数:通用排序的利器 ⚡)
    • [三、模拟实现qsort:基于冒泡排序的通用改造 🔧](#三、模拟实现qsort:基于冒泡排序的通用改造 🔧)
      • [1. 核心改造思路](#1. 核心改造思路)
      • [2. 完整模拟实现代码](#2. 完整模拟实现代码)
    • [写在最后 📝](#写在最后 📝)

指针系列的倒数第二篇来啦!这一篇我们将聚焦指针的终极实战用法------回调函数,同时深度解析库函数qsort的使用方法和模拟实现,帮你彻底掌握指针在通用算法中的灵活应用,为下一篇的笔试面试题精讲做好充分准备!

前景回顾:前四篇指针核心速记 📝

指针第一讲:从内存到运算,吃透指针核心逻辑
指针第二讲:const 修饰、野指针规避与传址调用
指针第三讲:数组与指针深度绑定 + 二级指针 + 指针数组全解析
指针第四讲:字符指针、数组指针、函数指针及转移表应用

想要吃透本篇的实战内容,先回顾前四篇的关键知识点:

  1. 指针本质是地址,不同类型的指针指向不同的目标对象,包括变量、数组、函数。
  2. 数组与指针深度绑定,数组传参本质传递首元素地址;函数指针可存储函数地址,实现对函数的间接调用。
  3. 函数指针数组可以构建转移表,简化多分支逻辑;typedef可重命名复杂的指针类型,提升代码可读性。

一、回调函数:通过函数指针调用的函数 📞

回调函数是C语言中一种重要的编程思想,它的核心是把函数的地址作为参数传递给另一个函数,在合适的时机通过函数指针调用这个函数。这一知识点也是笔面试中的高频考点。

1. 回调函数的定义与理解

  • 回调函数不是由函数的实现者直接调用,而是由其他函数通过函数指针间接调用。
  • 回调函数的出现,让程序的逻辑分层更清晰,也让功能的拓展更灵活。

2. 代码示例:计算器中的回调函数

以简易计算器为例,AddSub等功能函数就是回调函数,它们的地址被传递给calc函数,在calc函数中被调用。

c 复制代码
#include <stdio.h>

// 功能函数------回调函数
int Add(int x, int y) { return x + y; }
int Sub(int x, int y) { return x - y; }

// 中间层函数:接收函数指针,调用回调函数
void calc(int (*p)(int, int))
{
    int x = 0, y = 0, r = 0;
    printf("请输入两个操作数:");
    scanf("%d %d", &x, &y);
    r = p(x, y); // 通过函数指针调用回调函数
    printf("计算结果:%d\n", r);
}

// 菜单函数
void menu()
{
    printf("*************************\n");
    printf("*** 1.add    2.sub    ***\n");
    printf("*** 0.exit            ***\n");
    printf("*************************\n");
}

int main()
{
    int input = 0;
    do
    {
        menu();
        printf("请选择:");
        scanf("%d", &input);
        switch(input)
        {
            case 1:
                calc(Add); // 传递Add函数地址
                break;
            case 2:
                calc(Sub); // 传递Sub函数地址
                break;
            case 0:
                printf("退出程序\n");
                break;
            default:
                printf("选择错误\n");
                break;
        }
    } while(input);
    return 0;
}

3. 生活类比理解回调函数

回调函数的逻辑就像酒店的叫醒服务

  1. 用户(主函数)告诉前台(中间层函数)叫醒的时间和方式(传递回调函数地址)。
  2. 到了指定时间,前台(中间层函数)按照用户要求的方式(调用回调函数)叫醒用户。
  3. 用户不需要自己定闹钟,只需要提供"叫醒方式",前台负责执行。

二、qsort函数:通用排序的利器 ⚡

qsort是C语言标准库中的排序函数,基于快速排序算法实现,可以排序任意类型的数组 ,包括整型、字符型、结构体等,其核心就是借助回调函数实现通用比较逻辑。qsort的使用与模拟实现是笔面试的重点考察内容。

1. qsort函数的原型

使用qsort需要包含头文件<stdlib.h>,函数原型如下:

c 复制代码
void qsort(
    void* base,                // 待排序数组的首元素地址
    size_t num,                // 待排序数组的元素个数
    size_t size,               // 数组中每个元素的大小(单位:字节)
    int (*compar)(const void*, const void*) // 比较两个元素的回调函数
);

2. 比较函数的规则

qsort的第四个参数是一个函数指针,指向的比较函数需要遵循固定规则:

  • p1 指向的元素 > p2 指向的元素,返回 大于0 的数。
  • p1 指向的元素 == p2 指向的元素,返回 0
  • p1 指向的元素 < p2 指向的元素,返回 小于0 的数。
  • 函数参数是const void*类型,可接收任意类型的地址,使用时需强制类型转换。

3. qsort的使用案例

案例1:排序整型数组
c 复制代码
#include <stdio.h>
#include <stdlib.h>

// 整型比较函数(升序)
int cmp_int(const void* p1, const void* p2)
{
    // void* 不能直接解引用,需强制转换为int*
    return *(int*)p1 - *(int*)p2;
}

// 整型比较函数(降序)
// int cmp_int(const void* p1, const void* p2)
// {
//     return *(int*)p2 - *(int*)p1;
// }

// 打印数组
void print_arr(int arr[], int sz)
{
    int i = 0;
    for(i=0; i<sz; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

void test1()
{
    int arr[] = {9,7,5,3,1,2,4,6,8,0};
    int sz = sizeof(arr)/sizeof(arr[0]);
    qsort(arr, sz, sizeof(arr[0]), cmp_int);
    printf("排序后:");
    print_arr(arr, sz);
}

int main()
{
    test1();
    return 0;
}
案例2:排序结构体数组

按姓名或年龄排序结构体数组,只需编写对应的比较函数。

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct Stu
{
    char name[20];
    int age;
};

// 按姓名比较(字典序)
int cmp_stu_by_name(const void* p1, const void* p2)
{
    // 结构体指针访问成员用->
    return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}

// 按年龄比较(升序)
int cmp_stu_by_age(const void* p1, const void* p2)
{
    return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}

// 打印结构体数组
void print_stu(struct Stu arr[], int sz)
{
    int i = 0;
    for(i=0; i<sz; i++)
    {
        printf("姓名:%s 年龄:%d\n", arr[i].name, arr[i].age);
    }
}

void test2()
{
    struct Stu arr[] = {{"zhangsan",20},{"lisi",25},{"wangwu",18}};
    int sz = sizeof(arr)/sizeof(arr[0]);
    // 按姓名排序
    qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
    printf("按姓名排序后:\n");
    print_stu(arr, sz);

    // 按年龄排序
    // qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
    // printf("按年龄排序后:\n");
    // print_stu(arr, sz);
}

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

三、模拟实现qsort:基于冒泡排序的通用改造 🔧

我们可以基于冒泡排序的思想,结合回调函数,模拟实现一个通用的qsort函数,理解其底层逻辑。这一实现思路在笔面试中极容易被考察。

1. 核心改造思路

  1. 参数设计 :参考库函数qsort,设计void* base、元素个数、元素大小、比较函数指针四个参数。
  2. 元素比较:借助比较函数指针,调用用户提供的比较逻辑,判断两个元素的大小。
  3. 元素交换 :因为元素类型不确定,需按字节交换,设计一个通用的Swap函数。

2. 完整模拟实现代码

c 复制代码
#include <stdio.h>
#include <string.h>

// 通用交换函数:按字节交换两个元素
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++;
    }
}

// 模拟实现qsort(基于冒泡排序)
void bubble_sort(
    void* base,
    int sz,
    int width,
    int (*cmp)(const void* p1, const void* p2)
)
{
    int i = 0;
    // 控制冒泡排序的趟数
    for(i=0; i<sz-1; i++)
    {
        int flag = 1; // 标记是否已有序
        int j = 0;
        // 控制每一趟的比较次数
        for(j=0; j<sz-1-i; j++)
        {
            // 计算第j个和第j+1个元素的地址
            // 强转为char*,+width就是跳过一个元素的字节数
            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 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]);
    }
    printf("\n");
}

void test()
{
    int arr[] = {9,7,5,3,1,2,4,6,8,0};
    int sz = sizeof(arr)/sizeof(arr[0]);
    bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
    printf("排序后:");
    print_arr(arr, sz);
}

int main()
{
    test();
    return 0;
}

写在最后 📝

本篇内容聚焦指针在实战和算法中的核心应用,回调函数与qsort的知识点紧密关联笔面试考点。掌握这些内容,你就拥有了应对指针类编程题的重要基础。

下一篇,我们将直击指针经典笔试面试题,从易到难拆解各类高频考题,帮你理清解题思路,轻松应对求职和考试中的指针难关!

相关推荐
JAVA+C语言2 小时前
C#——接口
开发语言·c#
whn19772 小时前
达梦数据库的整体负载变化查看
java·开发语言·数据库
脏脏a2 小时前
聊聊 C 里的进制转换、移位操作与算术转换
c语言·开发语言·移位操作符
xie_pin_an2 小时前
深入解析 C 语言排序算法:从快排优化到外排序实现
c语言·算法·排序算法
陳10302 小时前
C++:string(4)
开发语言·c++
ZHang......2 小时前
synchronized(三)
开发语言·笔记·juc
许泽宇的技术分享2 小时前
AgentFramework:错误处理策略
开发语言·c#
阿里嘎多学长2 小时前
2025-12-21 GitHub 热点项目精选
开发语言·程序员·github·代码托管
wanghowie2 小时前
01.04 Java基础篇|泛型、注解与反射实战
java·开发语言·windows