简体   繁体   中英

How to make qsort function in C, more generic

Consider the following code snippet:

typedef int (*FNPTR)(const void* p1, const void* p2);

typedef struct {
    char name[100];
    int age;
} Person_t;


void display(Person_t p[], size_t size)
{
    for (size_t i = 0; i < size; i++) {
        printf("%s: %d\n", p[i].name, p[i].age);
    }
}

int compare_by_age(const void* p1, const void* p2)
{
    return ((Person_t *)p1)->age - ((Person_t *)p2)->age;
}

int compare_by_name(const void* p1, const void* p2)
{
    return strcmp(((Person_t *)p1)->name, ((Person_t *)p2)->name);
}

FNPTR compare(int choice)
{
    return (choice == 0) ? compare_by_age : compare_by_name;
}

int main()
{
    Person_t p[] = {
        {"John", 21},
        {"Albert", 23}
    };

    size_t size = sizeof(p)/sizeof(p[0]);

    qsort(p, size, sizeof(p[0]), compare(1));
    display(p, size);
    ...
}

One disadvantage of this program is, if we keep adding fields to the Person structure, then we have to write "n" compare functions. Is it possible to create and return relevant compare function inside the compare itself, based on the choice(without adding "n" compare functions). Something similar to C++ lambads so that we can create and return a function within another function in C (or any other better logic).

TBH, I think your best option is to write a custom sort function instead of shoehorning something into qsort() . But below are some ways to do it.

Note: This solution is (probably) not thread safe

It's probably impossible to do in a very neat way. This is C after all. But you could use a global variable like this:

enum field { NAME, AGE } field;

int compare(const void* p1, const void* p2)
{
    switch(field) {
    case AGE: 
        return ((Person_t *)p1)->age - ((Person_t *)p2)->age;        
    case NAME:
        return strcmp(((Person_t *)p1)->name, ((Person_t *)p2)->name);
    }
}

Not the sweetest solution, but at least you don't have to write tons of compare functions.

If you really want to be McGyver to avoid globals, you could use NULL as a sentinel for first argument to trigger configuration mode and then use p2->age as the configuration parameter, but I strongly advice against it. I'm showing it just to show that it's possible, but this is just silly:

int compare(const void* p1, const void* p2)
{
    static enum field field;

    if(!p1) { 
        switch((Person_t *)p2)->age) {
        case NAME: field = NAME; break;
        case AGE: field = AGE; break;
        }
        return 0; // Just to exit
    }

    switch(field) {
    case AGE: 
        return ((Person_t *)p1)->age - ((Person_t *)p2)->age;    
    case NAME:
        return strcmp(((Person_t *)p1)->name, ((Person_t *)p2)->name);
    }
}

A third option, also not a very good one, is to send the data in via the Person_t structs:

typedef struct {
    char name[100];
    int age;
    // Extra fields that the compare function can use
} Person_t;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM