C语言qsort函数详解:参数、多类型应用及冒泡排序模拟实现

阿澈
阿澈
发布时间:2026-10-15| 3人浏览过
原创

本文详细解析C标准库中qsort函数的用法,通过整型和结构体数组实例展示其泛型特性。void*指针是打破类型限制的核心,配合自定义比较函数实现任意数据的排序。文章进一步从内存操作层面出发,模拟实现了基于通用指针交换的冒泡排序,深入讲解其内部运作原理。

1. qsort 函数原型与参数解析

qsort 位于 <stdlib.h> 头文件中,是一个基于快速排序(或特定优化版本)实现的通用排序函数。其核心在于使用泛型指针来处理未知数据类型。

函数定义:

关键参数说明:

  • void *base:指向待排序数据起始位置的指针。因为不知道具体类型,统一使用 void* 接收。
  • size_t num:待排序的数据元素总个数,决定了循环边界。
  • size_t width:单个数据元素的字节大小。这是进行字节偏移计算的关键参数。
  • int (*compar)(const void *, const void *):函数指针,用于指定两个元素之间的比较逻辑。排序算法本身不关心数据类型,它只委托这个回调函数来决定先后顺序。

这种设计允许开发者在不知道内部数据结构的情况下,依然能够对复杂对象进行高效排序。

2. 针对不同数据类型的排序实战

下面通过代码演示如何使用 qsort 对基本类型和结构体类型进行排序。注意观察比较函数 cmp_int 和 cmp_stu_by_name 是如何配合 void* 指针使用的。

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

// 针对整数类型的比较函数
int cmp_int(const void* e1, const void* e2)
{
    return (*(int*)e1 - *(int*)e2);
}

void test_sort_int()
{
    int arr[] = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
    int sz = sizeof(arr) / sizeof(arr[0]);
    
    // 入口:stdlib.h中的qsort
    // 动作:传入数组首地址、元素个数、单个元素大小、比较函数
    qsort(arr, sz, sizeof(arr[0]), cmp_int);
    
    for (int i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
}

// 定义学生结构体
struct Stu
{
    char name[20];
    int age;
};

// 针对姓名进行字符串比较
int cmp_stu_by_name(const void* e1, const void* e2)
{
    // 将无类型指针转换为结构体指针,再进行字段访问
    return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}

void test_sort_struct()
{
    struct Stu s[] = {{"zhangsan", 20}, {"lisi", 21}, {"wangwu", 19}};
    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("Name: %s, Age: %d\n", s[i].name, s[i].age);
    }
}

int main()
{
    test_sort_int();
    printf("\n");
    test_sort_struct();
    return 0;
}

关于 void* 指针的深度理解:

在 test_sort_int 和 test_sort_struct 中,我们传递的分别是 int* 和 struct Stu*,但 qsort 的参数要求是 void*。这是完全可行的。

为什么不兼容警告?

如果尝试直接将 char* 赋值给 int* 而不加转换,编译器会报错。但是 void* 是一种特殊的“无类型指针”,它可以接受任何对象的地址。

局限性: 正因为它是泛型的,编译器无法确定它指向的具体类型,因此不能直接对它进行解引用操作(如 *base),也不能进行加减运算(如 base + 1,单位不确定)。必须进行强制类型转换后才能正常使用。

这正是 C 语言实现泛型编程的核心技巧:利用 void* 处理内存布局,由调用者提供具体的操作规则(即比较函数)。

3. 探本溯源:模拟实现泛型排序

要彻底掌握 qsort,最好的方法是自己写一遍。虽然 qsort 内部通常使用的是快速排序,但为了清晰展示泛型操作的流程,我们这里用更直观的冒泡排序(Bubble Sort)逻辑来实现一个通用的 my_qsort。

第1步:设计底层字节交换 Swap 函数

由于我们只知道元素的大小 width,而不知道该元素是 int 还是 float,我们不能直接做值交换。我们需要做的是逐字节搬运。

将基地址强转为 char* 后,每次增加 1 个字节,正好对应 width 指定的元素长度。

void Swap(char* buf1, char* buf2, int width)
{
    for (int k = 0; k < width; k++)
    {
        char tmp = *buf1;
        *buf1 = *buf2;
        *buf2 = tmp;
        buf1++;
        buf2++;
    }
}

第2步:构建泛型排序框架

排序循环中,我们要访问相邻的两个元素,计算它们的地址必须使用 (char*)base + offset 的形式,其中偏移量 = 索引 * width。

void bubble_sort(void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2))
{
    for (int i = 0; i < sz - 1; i++)
    {
        int flag = 1;
        for (int j = 0; j < sz - 1 - i; j++)
        {
            // 获取相邻两个元素的地址并传给比较函数
            if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
            {
                // 如果顺序不对,调用Swap函数按字节交换
                Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
                flag = 0;
            }
        }
        if (flag == 1) break; // 已有序,提前退出
    }
}

第3步:整合测试验证

将上述逻辑组合在一起,替换掉标准的 qsort 调用进行测试,以确保模拟实现的正确性。

int cmp_int(const void* e1, const void* e2)
{
    return (*(int*)e1 - *(int*)e2);
}

void test_mysort_int()
{
    int arr[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    // 调用我们自己写的泛型排序函数
    bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
    
    for (int i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
}

int cmp_stu_by_name(const void* e1, const void* e2)
{
    return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}

void test_mysort_struct()
{
    struct Stu s[] = {{"zhangsan", 20}, {"lisi", 21}, {"wangwu", 19}};
    int sz = sizeof(s) / sizeof(s[0]);
    
    bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_name);
    
    for (int i = 0; i < sz; i++)
    {
        printf("Name: %s, Age: %d\n", s[i].name, s[i].age);
    }
}

int main()
{
    test_mysort_int();
    printf("\n");
    test_mysort_struct();
    return 0;
}

运行结果证明,无论是基础数据类型还是复杂的结构体,只要提供了正确的比较规则和准确的宽度,我们的模拟实现都能正确地调整它们在内存中的物理位置。

总结

本文通过剖析 qsort 的使用场景,展示了 void* 在 C 语言泛型编程中的强大能力。理解了指针的强制类型转换和内存偏移计算,就能轻松驾驭任何复杂的排序需求。自己动手编写类似的泛型函数,是提升 C 语言内功的有效途径。以上就是C语言qsort函数详解的内容,更多关于C语言泛型编程的资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。