Qsort 比较函数

11

我是C语言的初学者, 我正在尝试理解qsort函数所需的比较函数。

第一部分: 语法

一个简单的建议用法是这样的(我已经包含了一些main()代码来打印结果):

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

int values[] = { 40, 10, 100, 90, 20, 25, 12, 13, 10, 40 };

int compare(const void *a, const void *b)
{
    const int *ia = (const int *)a; // casting pointer types 
    const int *ib = (const int *)b;
    return *ia  - *ib; 
}

int main()
{
    int n;
    for (n=0; n<10; n++)
    {
        printf("%d ",values[n]);
    }
    printf("\n");
    qsort(values, 10, sizeof(int), compare);
    for (n=0; n<10; n++)
    {
        printf("%d ",values[n]);
    }
    printf("\n");
    system("pause");
    return 0;
}

我不明白为什么你需要在比较函数中加入所有的额外内容,所以我将它简化成这样:

int compare (int *a, int *b)
    {
        return *a-*b; 
    }

这仍然可用,并产生相同的结果。有人能否解释一下我删除了什么,以及为什么它仍然有效?

第二部分:为什么要使用指针?

另外,我真的需要使用指针吗?为什么不能像这样直接比较“a”和“b”(这是行不通的):

int compare (int a, int b)
        {
            return a-b; 
        }

出于某种原因,对于多维数组,我竟然可以不使用指针就成功了!这是怎么回事呢?(以下是按每个子数组中第二项对多维数组进行排序的示例代码):

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

int values[7][3] = { {40,55}, {10,52}, {100,8}, {90,90}, {20,91}, {25,24} };

int compare(int a[2], int b[2])
{
    return a[1] - b[1];
}

int main()
{
    int n;
    for (n=0; n<6; n++)
    {
        printf("%d,",values[n][0]);
        printf("%d ",values[n][1]);
    }
    printf("\n");
    qsort(values, 6, sizeof(int)*3, compare);
    for (n=0; n<6; n++)
    {
        printf("%d,",values[n][0]);
        printf("%d ",values[n][1]);
    }
    printf("\n");
    system("pause");
    return 0;
}

我真的很高兴多维数组排序正在工作,因为那是我的最终目标,但我不知道我如何使它工作(除了运气和剪辑代码之外),所以我真的希望能对我提供的一些示例为什么有些有效而有些无效进行解释!

4个回答

12
这仍然有效,并产生相同的结果。有人能解释一下我删除了什么以及为什么它仍然有效吗? 你在C中调用未定义行为。请参阅C99 6.3.2.3指针/8: “一个指向某种类型函数的指针可以转换为指向另一种类型函数的指针,然后再转换回来;结果应该与原始指针相等。如果转换后的指针用于调用其指向类型与所指类型不兼容的函数,则行为未定义。”
在C++中,这个程序是不合法的:http://ideone.com/9zRYSj 它仍然“偶然起作用”,因为compare函数期望一对指针;并且在你特定的平台上,sizeof(void*)sizeof(int*)相同,因此调用类型为int(void *,void *)的函数指针实际上包含类型为int(int*,int*)的函数指针,实际上等同于在你特定的平台上,在此特定时间点上的指针类型转换。
另外,我真的需要使用指针吗?为什么不能直接像这样比较“a”和“b”(这样不起作用): 因为qsort需要一个通用的比较函数来比较任何两种类型;而不仅仅是int。所以它不知道指针解引用到哪种类型。
因为以下原型相同: 1. int foo(int *a, int *b); 2. int foo(int a[], int b[]) 也就是说,当传递给函数时,数组会衰变成为一个指针。像你做的那样明确指定数组的长度:
int foo(int a[2], int b[2])

让编译器将sizeof和其他编译时的位作为一个二元数组处理;但是当函数降到机器级别时,该函数仍然接受一对指针。

在这些情况下,如果传递的比较函数不使用一对void *,则会导致未定义行为。 "未定义行为"的一个有效结果是"它似乎工作正常"。另一个有效结果可能是"它只能在星期二工作"或"它格式化硬盘"。不要依赖于这种行为。


有趣的是,数组可以分解为指针。这是可靠的吗?还是存在固有的错误?您如何建议修改多维排序以使其可靠? - Dlinet
1
@Dlinet:始终让您的比较函数接受const void*。将该指针转换为const int*。然后像通常一样使用operator[] - Billy ONeal

2
我不明白为什么您在比较函数中需要所有额外的东西,所以我将其简化为这样。使用const限定符取决于您。您应该不会修改比较器中的值。但是,有可能取消const并打破向编译器做出的承诺。
"

qsort 期望一个函数指针作为参数,该函数接受两个const void *类型的指针作为参数,这就是为什么要传递指向比较函数的指针:

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

所以传递 ab 会导致将 解释为指针,这显然是错误的。
它可以在不传递多维数组指针的情况下工作,因为当您传递数组时,它们会衰减为指针。因此,您拥有的以下比较器是可以的。
int compare (int a[2], int b[2])
{
    return a[1] - b[1];
}

0
答案非常简单:从qsort的手册http://pubs.opengroup.org/onlinepubs/009695399/functions/qsort.html中可以看到函数指针的原型:
int comp(const void *a, const void *b)

与此原型相关的typedef可以这样声明

typedef int (*comp_qsort_funct_t) ( const void *, const void * )

comp_qsort_funct_t 是函数指针的类型。

qsort 的比较函数原型使用 const 变量,因为这是一个良好的实践/设计,因为数据不会被修改。

使用不同的原型可能会导致意外的结果,具体取决于编译器/平台。或者只是无法编译,例如在评论中提供的示例:http://ideone.com/9zRYSj

因此,您不应该使用不同的原型。


这是C语言,不是C++。在C++中,这是完全非法的;不仅仅是“糟糕的设计实践”。http://ideone.com/9zRYSj - Billy ONeal
我只是在解释为什么原型正在使用 const 变量... 我不明白你在这里提到 C++ 的观点... 请解释一下。 - benjarobin
在C语言中,这段代码是未定义行为。在C++中,这段代码根本就是不合法的。你的参考对象是一个C++源文件,但你在谈论C语言。请澄清这个区别,我会把-1改为+1。 - Billy ONeal
提供的链接是关于C而不是C++的...你刚刚读了URL的域名吗? 而且提供的代码不是格式错误的,在C中这就是函数原型声明的方式。 - benjarobin
原型并没有问题,问题出在调用qsort上。您指出的页面标题和内容中显示为“C++参考文献”;但这是C和C++存在显著差异的领域,因此仅简单替换为C++参考可能会让读者感到困惑。 - Billy ONeal
@BillyONeal 您的回答正是我想要解释的,但我没有使用正确的词语... 我编辑了我的回答,以防止任何关于 C++ 的混淆 / 暗示。让我们在聊天室中继续这个讨论 - benjarobin

0

我想确认Dlinet提出的原始问题的观察结果。

C编译器确实接受所有形式的比较函数,即使没有const,甚至使用int *而不是void *。然而,我的C++编译器拒绝这些变化,只接受const void *形式。

C编译器甚至接受不使用指针的int形式。我检查了一下,发现比较函数的int形式接收到的是指针值,而不是数组中的整数。结果是数组没有排序,仍然保持原来的顺序。我认为这一切都是可以预料的。

因此,在函数定义中可以使用int *代替void *,然后就不需要在函数内部进行强制转换。

无论这是否是良好的编程实践,都存在争议,有些程序员认为是,有些则认为不是。对我来说,无论是良好的编程实践与否,使用int *而不是void *减少混乱的优点是显而易见的。但是,C++编译器不允许您这样做。


我不明白为什么这个晚回答会得到一次赞成;这是完全错误的。C编译器接受所有类型的非法代码。所讨论的C代码会导致未定义行为。这不是良好的编程实践,任何懂得如何在C中编程的人都不会这样说。 - ad absurdum

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接