将额外参数传递给qsort的比较器

时间:2010-11-18 00:57:15

标签: c

我只是想知道是否有办法让我将一个额外的参数传递给我的比较器,然后在我的qsort函数中使用它?

例如,我有这两个比较器(一个按升序排列,另一个按降序排列)

qsort(entries, 3, sizeof(struct entry), compare_desc);

int compare_asc(const void *elem1, const void *elem2)
{
     return strcmp(elem1.name.last, elem2.name.last);
}


int compare_desc(const void *elem1, const void *elem2)
{
     return strcmp(elem2.name.last, elem1.name.last);
}

有没有办法让我可以做这样的事情:

int compare(const void *elem1, const void *elem2, const char *order)
{
     if (strcmp(order, "asc") == 0)
         return strcmp(elem1.name.last, elem2.name.last);
     else if (strcmp(order, "desc") == 0)
         return strcmp(elem2.name.last, elem1.name.last);
}

我问的原因是我的排序程序必须接受开关,如果我有2个不同的开关(+ a,-a)分别用于升序和降序,那么我必须制作2个不同的比较器功能。如果我添加更多,它会变得更复杂。有没有办法改进这个程序的设计?

编辑:没有全球&允许使用外部变量。

9 个答案:

答案 0 :(得分:9)

老问题,但万一有人偶然发现它......

有一些非标准版本的qsort()可以让你将额外的参数传递给回调函数。 glib提供qsort_r(),而VC提供qsort_s()。

答案 1 :(得分:5)

在您的示例中,更好有两个不同的比较器。如果您只有一个,那么每次比较都会不必要地确定排序顺序,无论如何对于任何有意义的结果都无法进行中间排序。因此,不要将if (ascending_sort) { } else { }放在比较器中,而是将其放在qsort电话:

qsort(e, n, sizeof(*e), (strcmp(order, "asc") ? compare_desc : compare_asc));

编辑:添加更多比较器的一些提示:

- 记住你不需要重写每个比较器;如果你在多个字段上排序,你可以让他们互相调用(你总是可以用-反转比较器的结果,例如,compare_asc(a, b)可以返回-compare_desc(a, b)

- 最终可以很容易地颠倒整个数组的顺序,因此您不需要将比较器数量加倍,以支持反转整个排序顺序的选项

- 你可以在我的例子中用一个函数替换三元运算符(? :),该函数返回下面评论中建议的相应比较器

答案 2 :(得分:2)

您应该做的是将参数切换到qsort,以便您根据需要传递函数指针。

根据您的情况,它可能类似于

// selectively invoke qsort:
if(strcmp(var, "+a")){
    qsort(entries, 3, sizeof(struct entry), compare_asc);
}else{
    qsort(entries, 3, sizeof(struct entry), compare_desc);
}

或者您可以执行以下操作:

// declare a function pointer
int (*func)(const void*, const void*);

// sometime later decide which function to assign
// to the function pointer
if(strcmp(var, "+a")){
    func = compare_asc;
}else{
    func = compare_Desc;
}

// sometime later invoke qsort
qsort(entries, 3, sizeof(struct entry), compare_desc);

答案 3 :(得分:2)

  

> 有没有办法改进这个程序的设计?

不要这样做 - 这不是设计改进,这只是一个实验。

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

int comparefx(const void *a, const void *b) {
    static int extra = 0;
    if (a == NULL) {
        extra = (int)b;
        return 0;
    }
    switch (extra) {
        case 24: puts("24"); return *(const int*)a + *(const int*)b; break;
        case 42: puts("42"); return *(const int*)b - *(const int*)a; break;
        default: puts("--"); return *(const int*)a - *(const int*)b; break;
    }
}

int main(void) {
    int entries[] = {4, 2, 8};

    qsort(entries, 3, sizeof *entries, comparefx);
    printf("%d %d %d\n", entries[0], entries[1], entries[2]);

    comparefx(NULL, (void*)42); /* set 'extra' parameter */
    qsort(entries, 3, sizeof *entries, comparefx);
    printf("%d %d %d\n", entries[0], entries[1], entries[2]);

    return 0;
}

用3个编译器“干净地”编译

$ gcc -std=c89 -pedantic -Wall 4210689.c
4210689.c: In function 'comparefx':
4210689.c:7: warning: cast from pointer to integer of different size

$ clang -std=c89 -pedantic -Wall 4210689.c
$ tcc -Wall 4210689.c
$ 

它按预期运行

$ ./a.out
--
--
--
2 4 8
42
42
42
8 4 2

答案 4 :(得分:1)

在简单的情况下,您可以使用全局变量。

答案 5 :(得分:1)

在不使用全局变量的情况下,AFAIK一般不能,你必须为这两种排序方法提供两种不同的函数。实际上这是the reasons之一,为什么经常使用C ++ 仿函数(提供重载函数调用运算符的对象)。

答案 6 :(得分:0)

缺少类和闭包几乎意味着您不得不为每种不同类型的比较编写单独的比较器。

可以做的一件事是让数组的每个元素都是一个结构,包含valuesort_order个字段。所有sort_order字段都是相同的......但这比只有2个比较器更糟糕。

以这种方式思考:无论如何,你最终都会编写所有相同的比较器代码。但是,与8个案例的复杂嵌套if / else不同,你有8个函数。不同的是一些额外的函数声明。

编辑:回复R的评论..这是一个好点。之前我有这个,但我删除了它:

您可以创建类似于Python的list.sort()函数的框架。差不多:

  • 使用valuesortvalue字段创建结构。
  • 将初始值放在value中。
  • 编写任何代码以将项目转换为sortvalue字段。
  • 使用标准比较器以及qsort
  • 完成后,只需从value字段中取出元素即可。它们将根据sortvalue进行排序,但值将是正确的。

这在Python中使用,例如,如果要按元组中的第4项排序,则不要编写整个比较器(如lambda v1,v2: v1[3]-v2[3]),而只需转换输入key函数(lambda k: k[3])并使用标准排序方法。它可以在“数十亿种”的情况下工作,因为您的代码可以执行任何复杂的操作,无论多少输入都可以转换值。

答案 7 :(得分:0)

只需使用lambda函数进行闭包。 像这个C ++代码:

string sortOrder="asc";   
qsort(entries, 3, sizeof(struct entry),    
[=](const void *elem1, const void *elem2) -> int{
        myCompare(elem1,elem2,sortOrde)             

});

答案 8 :(得分:0)

qsort_r()qsort_s()

在某些实现中有一些名为qsort_r()qsort_s()的函数可以执行您想要的操作 - 获取指向传递给比较器函数的额外数据的指针。

BSD变体实现(包括macOS或Mac OS X)提供qsort_r()的版本,GNU C库也是如此。不幸的是,这两种变体具有不同的签名。这并不能阻止它们有用,但它确实意味着不能在这两个平台上使用相同的源代码,而且您必须确保了解哪个qsort_r()变体可用于任何机器上你试着用它。

同样,Microsoft提供了qsort_s()的版本,C11标准定义了qsort_s()的版本(作为附件K中的可选功能,基于TR-24731),但两者的区别在于再次签名。也许幸运的是附件K的功能没有得到广泛实施。

BSD qsort_r()

void qsort_r(void *base, size_t nel, size_t width, void *thunk,
             int (*compar)(void *, const void *, const void *));

GNU C库qsort_r()

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

请注意,在BSD中,'thunk'相当于GNU中的'arg',但这些参数出现在qsort_r()函数的调用序列中的不同位置(比较器函数指针之前和之后)。此外,请注意'thunk'作为参数1传递给BSD比较器函数,但'arg'作为参数3传递给GNU比较器函数。

qsort_r的助记符:上下文数据是根据调用序列中的比较器指定的,其关系与上下文相对于被比较的两个值传递给比较器的关系相同。指向比较器的指针之前的上下文表示在调用比较器之前的值之前指向比较器的指针之后的上下文表示调用比较器后值的上下文。

附件K qsort_s()

errno_t qsort_s(void *base, rsize_t nmemb, rsize_t size,
               int (*compar)(const void *x, const void *y, void *context),
               void *context);

附件K qsort_s()在返回值时是唯一的;所有其他变体都不会返回任何值。否则,出于大多数实际目的,它与GNU qsort_r()函数匹配。

Microsoft qsort_s()

void qsort_s(void *base, size_t num, size_t width,
             int (__cdecl *compare )(void *, const void *, const void *),
             void * context);

rsize_tsize_t区别在比较qsort_s()的附件K和Microsoft变体时并不是非常重要,但在附件K qsort_s()中,上下文通过作为比较器的参数3,但在Microsoft qsort_s()中,上下文作为参数1传递给比较器。

摘要

名为qsort_r()qsort_s()的函数提供所需的功能。但是,您必须检查存在哪个函数的平台规范,以及对于排序函数的参数的正确调用顺序,对比较器的参数的正确调用顺序。

名义上,您应该检查函数的返回类型,但很少有程序会考虑检查它,主要是因为qsort()的大多数变体都没有返回值。