using qsort() to sort pointers to structs containing strings
Asked Answered
S

3

6

I'm not sure if this is possible to do with qsort because what I want to sort (array of pointers to struct) is not what I am comparing (strings).

Here is an abridged version of my program (assume that all the student data is in core before we call qsort() and n is the number of records to sort):

struct student {
        char lname[NAMESIZE + 1];
        char fname[NAMESIZE + 1];
        short mid;
        short final;
        short hmwks;
};

int cmp(const void *, const void *);

int
main(int argc, char **argv)
{
        int n;
        struct student *data[MAX];

        qsort(data, n, sizeof(struct student *), cmp);

        return 0;
}

int
cmp(const void *p0, const void *p1)
{
        return strcmp((*(struct student *) p0).lname,
                      (*(struct student *) p1).lname);
}
Shiloh answered 4/5, 2012 at 5:27 Comment(0)
E
3

It should be something like this:

int
cmp(const void *p0, const void *p1)
{
        // pn is a pointer to an element of the array,
        // so, it's effectively a pointer to a pointer to a struct.
        // Therefore, we need to cast it appropriately to struct student **.
        // To get a pointer to a struct from it, we dereference it once,
        // hence the "*". Then we need to extract a pointer to the beginning
        // of a string, hence the "->".
        return strcmp((*(struct student **) p0)->lname,
                      (*(struct student **) p1)->lname);
}
Epistasis answered 4/5, 2012 at 5:37 Comment(1)
Thanks, that explained everythingShiloh
S
5

What will be passed to cmp() are struct student** parameters (in the guise of void*). So change cmp() like so:

int
cmp(const void *p0, const void *p1)
{
        struct student* ps0 = *(struct student**) p0;
        struct student* ps1 = *(struct student**) p1;

        return strcmp( ps0->lname, ps1->lname);
}
Silvanasilvano answered 4/5, 2012 at 5:38 Comment(0)
E
3

It should be something like this:

int
cmp(const void *p0, const void *p1)
{
        // pn is a pointer to an element of the array,
        // so, it's effectively a pointer to a pointer to a struct.
        // Therefore, we need to cast it appropriately to struct student **.
        // To get a pointer to a struct from it, we dereference it once,
        // hence the "*". Then we need to extract a pointer to the beginning
        // of a string, hence the "->".
        return strcmp((*(struct student **) p0)->lname,
                      (*(struct student **) p1)->lname);
}
Epistasis answered 4/5, 2012 at 5:37 Comment(1)
Thanks, that explained everythingShiloh
H
1

The other answers are correct in everything but one little detail. I've just hit this, so i'll leave it here in case someone else finds themselves struggling with an extremely pedantic compiler.

qsort() comparator accepts two const void * parameters. Which means that when you dereference them to get pointers to your actual structs, you have to keep that const-ness. So, if you were to follow every single rule of C language, your code would look like this:

int
cmp(const void *p0, const void *p1)
{
    const struct student* ps0 = *(const struct student* const*) p0;
    const struct student* ps1 = *(const struct student* const*) p1;

    return strcmp(ps0->lname, ps1->lname);
}

Note the "const pointer to const" construct - that's you telling the compiler that at no point your original pointers (p0 and p1) lose their const-ness - you first dereference them to a const * const *, and then dereference that to const *. If you simply use const **, that's a const * to * (non-const) pointer, which discards the original const-ness of const void *.

Hoem answered 16/2, 2018 at 10:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.