qsort() 中使用的比较函数是否有任何限制

Is there any restriction about the compare function used in qsort()

我编写了这个代码片段来将字符串数组排序为最小化它们的连接的顺序:

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

int cmpstr(const void* p1, const void* p2){
    int p1l = strlen((const char*)p1);
    int p2l = strlen((const char*)p2);
    int r = strncmp((const char*)p1, (const char*)p2, p1l<p2l?p1l:p2l);
    if(r == 0 && p1l != p2l){
        if(p1l < p2l){
            return cmpstr(p1, (const char*)p2 + p1l);
        }
        return cmpstr((const char*)p1 + p2l, p2);
    }
    return r;
}

int main(){
    const char* arrstr[] = {"93", "936", "15", "152", "946"};
    int num = sizeof(arrstr) / sizeof(char*);
    qsort(arrstr, num, sizeof(char*), cmpstr);
    for(int i = 0; i < num; i++){
        printf("%s\n", arrstr[i]);
    }
}

这些字符串应按 15 152 936 93 946 的顺序排序。我们希望 93936946 之间,因为 936 93 < 93 93693 946 < 946 93 (忽略添加的空格为了清楚起见)。

但是代码没有按预期运行。尽管我对 cmpstr() 的测试完全符合我的预期,但数组根本没有排序。

我错了什么?

我注意到,当我将 cmpstr() 的演员表部分从 *(char* const*) 更改为 (char*) 时,qsort() 也不起作用。这是为什么?

传递给qsort的比较函数接收要比较的两个数组元素的地址。由于每个数组元素都是一个 char *,因此每个元素的地址都是一个 char **。所以你错过了一级间接。

您需要将每个参数转换为 char * const *,然后取消引用以获取指向字符串的指针:

int cmpstr(const void* p1p, const void* p2p){
    char *p1 = *(char * const *)p1p;
    char *p2 = *(char * const *)p2p;
    ...
}

编辑:

因为你想递归地调用这个函数,你需要一个 non-recursive 包装函数来包围你的递归函数,因为它们采用的参数不一样:

// internal recursive function that takes two strings
static int cmpstr_int(const char* p1, const char* p2){
    int p1l = strlen(p1);
    int p2l = strlen(p2);
    int r = strncmp(p1, p2, p1l<p2l?p1l:p2l);
    if(r == 0 && p1l != p2l){
        if(p1l < p2l){
            return cmpstr_int(p1, p2 + p1l);
        }
        return cmpstr_int(p1 + p2l, p2);
    }
    return r;
}

// comparison function that extracts desired datatype from void * params
// and passes them to recursive function
static int cmpstr(const void* p1p, const void* p2p){
    const char *p1 = *(char * const *)p1p;
    const char *p2 = *(char * const *)p2p;
    return cmpstr_int(p1, p2);
}

您的比较函数接收指向数组元素的指针。每个元素都是一个指向 char 的指针,因此您将得到一个指向 char 的指针。

比较逻辑也有点over-complicated;这是一个工作版本:

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

int cmpstr(const void* a_, const void* b_)
{
    const char *const *a = a_;
    const char *const *b = b_;
    int la = strlen(*a);
    int lb = strlen(*b);

    if (la == lb) {
        /* same length - sort lexicographically */
        return strcmp(*a, *b);
    }

    if (la < lb) {
        /* a is shorter */
        int result = strncmp(*a, *b, la);
        if (!result) {
            /* a is a prefix of b */
            result = strcmp(*a, *b + la);
        }
        return result;
    }

    /* else, b is shorter - re-enter with arguments swapped,
       and negate the result */
    return -cmpstr(b_, a_);
}

int main() {
    const char* arrstr[] = {"93", "936", "15", "152", "946"};
    const size_t num = sizeof arrstr / sizeof *arrstr;
    qsort(arrstr, num, sizeof *arrstr, cmpstr);
    for (size_t i = 0; i < num; i++) {
        printf("%s\n", arrstr[i]);
    }
}

输出:

15
152
936
93
946

如果你认为我上面的 cmpstr() 与原来的偏差太大,请考虑这个 less intrusively-modified 代码,它使用你想要的递归比较,用一个单独的包装器来适应它适合 qsort():

int compare_strings(const char *a, const char *b)
{
    int la = strlen(a);
    int lb = strlen(b);
    int r = strncmp(a, b, la<lb?la:lb);
    if (r == 0 && la != lb) {
        if (la < lb) {
            return compare_strings(a, b + la);
        }
        return compare_strings(a + lb, b);
    }
    return r;
}

int compare_strings_qsort(const void* a_, const void* b_)
{
    const char *const *a = a_;
    const char *const *b = b_;
    return compare_strings(*a, *b);
}

我仍然不得不更改您的变量名,因为我发现 p1l 之类的东西难以阅读。我可以进一步简化,我认为这比原始函数和我上面的第一次尝试都更清楚(但可能需要一些评论):

int compare_strings(const char *a, const char *b)
{
    const int la = strlen(a);
    const int lb = strlen(b);
    const int r = strncmp(a, b, la<lb?la:lb);

    return (la == lb || r)
        ? r
        : (la < lb)
        ? compare_strings(a, b + la)
        : compare_strings(a + lb, b);
}