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

我只是想知道是否有办法让我将一个额外的参数传递给我的比较器,然后在我的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个不同的比较器function。 如果我添加更多,它会变得更复杂。 有没有办法改进这个程序的设计?

编辑:不允许全局和外部变量。

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

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

在您的示例中, 最好有两个不同的比较器。 如果您只有一个,那么每次比较都会不必要地确定排序顺序,无论如何,对于任何有意义的结果,您无法在中间排序。 所以不要将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) )。

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

– 你可以在我的例子中用一个返回适当比较器的函数替换三元运算符( ? : :),如下面的注释所示

你应该做的是将参数切换到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); 

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

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

 #include  #include  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:在函数'comparefx'中:
 4210689.c:7:警告:从指针强制转换为不同大小的整数

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

它按预期运行

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

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

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

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

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

可以这样想:最后你最终会编写所有相同的比较器代码。 但是,有8个函数,而不是复杂的嵌套if / else 8个案例。 区别在于一些额外的函数声明。

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

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

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

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

只需使用lambda函数即可关闭。 像这个C ++代码:

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

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中的可选function,基于TR-24731),但两者在签名上又有所不同。 也许幸运的是附件K的function没有得到广泛实施。

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()函数匹配。

微软qsort_s()

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

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

摘要

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

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