Как отсортировать массив структуры с помощью qsort? - PullRequest
0 голосов
/ 31 мая 2019

Мы с другом пытаемся научить себя Си и решили выполнить, то, что сначала считалось легким, упражнение, в котором мы создаем структуру из двух символов, содержащую 1. имя и 2. фамилию.Функция read_person получает вводимые пользователем данные, сохраняет их в структуре и возвращает их.Входные данные должны быть сохранены в динамически распределенном массиве (все, что якобы до сих пор было сделано правильно).Затем, используя qsort, массив должен сортироваться по возрастанию, когда дело доходит до имени, по убыванию, когда дело доходит до фамилии, и, наконец, с учетом длины имени.если имена одинаково длинные, фамилии следует сравнить.Мы оба очень старались, чтобы qsort работал, но он просто не сортировался, поэтому нам было интересно, есть ли у кого-нибудь идеи, как это сделать?

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

struct details{
  char forename[10];
  char surname[10];
}[5];

struct details read_person(){
struct details d;
printf("Enter your forename: ");
fgets(d.forename, 10, stdin);
printf("Enter your surname: ");
fgets(d.surname, 10, stdin);
struct details *arr_dt=malloc(5 * sizeof(struct details));
free(arr_dt);
return d;
}



int main(){
read_person();
return 0;
}

1 Ответ

2 голосов
/ 31 мая 2019

У вас здесь происходит несколько вещей, которые не совсем правильны, но они также затрагивают несколько тонких вопросов, которые не будут очевидны при попытке наткнуться, хотя, как заполнить вашу структуру.

Сначала вы объявляете глобальный массив struct details (5 из них). Не делай этого. Хотя это допустимо, вместо этого вы просто хотите объявить struct в глобальной области видимости, а затем объявить каждый экземпляр в пределах main() и передать либо копию структуры, либо указатель на структуру в качестве параметров любой функции в вашем коде, которая нуждается в это.

Во-вторых, вы объявляете d локально для read_person, а затем возвращаете d в конце для назначения обратно в main(). Это хорошо, но ... понимаю, почему это хорошо. Когда вы объявляете d все члены struct details все имеют автоматический тип хранения, и хранилище для каждого члена полностью определено в этот момент. Нет необходимости вызывать malloc где-либо в вашей функции. Когда вы возвращаете d в конце, struct присваивание позволяет функции возвращать структуру и иметь все значения, назначенные и доступные обратно в main().

Наконец, в main() вы вызываете read_person();, но не можете назначить возврат или использовать сохраненные значения в d любым способом.

Вместо создания глобального массива структуры просто объявите саму структуру, например ::101027

#define MAXNM  32   /* if you need a constant, #define one (or more) */
                    /* (don't skimp on buffer size) */
struct details {
    char forename[MAXNM];
    char surname[MAXNM];
};

Затем для вашей функции read_person(void) исключите вызовы к malloc и просто выполните:

struct details read_person (void)
{
    struct details d;

    printf ("Enter your forename: ");
    fgets (d.forename, MAXNM, stdin);
    d.forename[strcspn(d.forename, "\n")] = 0;  /* trim \n from end */

    printf("Enter your surname : ");
    fgets(d.surname, MAXNM, stdin);
    d.surname[strcspn(d.surname, "\n")] = 0;    /* trim \n from end */

    return d;
}

( примечание: вы не хотите, чтобы конец строки '\n' оставлялся в конце каждого из имен, поэтому вам нужно перезаписать конечный '\n' с помощью nul- символ '\0' (или эквивалентно 0). Хотя есть несколько способов сделать это, использование strcspn, вероятно, является одним из самых надежных и простых способов сделать это. strcspn возвращает число символов в строке, НЕ включенной в набор exclude . Поэтому просто включите в свой набор exclude конец строки "\n", и он возвращает количество символов в строке до '\n', который вы затем просто установите на 0)

( также обратите внимание: использование void в struct details read_person (void) для указания того, что read_person не принимает аргументов. В C, если вы просто оставляете пустым (), функция принимает неопределенный количество аргументов)

Затем в main() назначьте возврат и используйте его каким-либо образом, например,

int main (void) {

    struct details person = read_person();

    printf ("\nname: %s, %s\n", person.forename, person.surname);

    return 0;
}

Другой вариант - объявить структуру в main() и передать указатель на функцию read_person для заполнения. Просто объявите структуру в main() и затем передайте адрес структуры в read_person, но обратите внимание, что с указателем на структуру вы используете оператор -> для доступа к членам, а не '.' , Например, вы можете сделать:

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

#define MAXNM  32   /* if you need a constant, #define one (or more) */
                    /* (don't skimp on buffer size) */
struct details {
    char forename[MAXNM];
    char surname[MAXNM];
};

void read_person (struct details *d)
{
    printf ("Enter your forename: ");
    fgets (d->forename, MAXNM, stdin);
    d->forename[strcspn(d->forename, "\n")] = 0;  /* trim \n from end */

    printf("Enter your surname : ");
    fgets(d->surname, MAXNM, stdin);
    d->surname[strcspn(d->surname, "\n")] = 0;    /* trim \n from end */
}

int main (void) {

    struct details person;

    read_person (&person);

    printf ("\nname: %s, %s\n", person.forename, person.surname);

    return 0;
}

И, наконец, поскольку вы включили mailloc, вы также можете узнать, как вы могли бы использовать его для выделения памяти для структуры, а также для каждого forename и surname, чтобы оба они использовали в точности правильный количество байтов для хранения введенных имен и не более. При выделении памяти для чего-либо у вас есть 3 обязанности в отношении любого выделенного блока памяти: (1) всегда проверять распределение успешно перед использованием блока памяти, (2) всегда сохраняйте указатель на начальный адрес для блока памяти, поэтому (3) он может быть освобожден , когда он больше не нужен.

Это добавит несколько повторяющихся, но важных строк кода, где бы вы ни динамически выделяли хранилище. Например, в этом случае, когда вы динамически выделяете для структуры и для forename и surname внутри структуры, ваши объявление структуры и функция могут быть:

struct details {
    char *forename;
    char *surname;
};

struct details *read_person (void)
{
    char buf[MAXNM];
    size_t len;
    struct details *d = malloc (sizeof *d);  /* allocate storage */

    if (d == NULL) {                         /* validate allocation succeeds */
        perror ("malloc-d");
        return NULL;
    }

    printf ("Enter your forename: ");
    fgets (buf, MAXNM, stdin);
    len = strcspn(buf, "\n");
    buf[len] = 0;
    d->forename = malloc (len + 1);      /* allocate */
    if (d->forename == NULL) {           /* validate */
        perror ("malloc-d->forename");
        free (d);
        return NULL;
    }
    memcpy (d->forename, buf, len + 1);

    printf ("Enter your surname : ");
    fgets (buf, MAXNM, stdin);
    len = strcspn(buf, "\n");
    buf[len] = 0;
    d->surname = malloc (len + 1);       /* allocate */
    if (d->surname == NULL) {            /* validate */
        perror ("malloc-d->surname");
        free (d->forename);
        free (d);
        return NULL;
    }
    memcpy (d->surname, buf, len + 1);

    return d;
}

( примечание: использование memcpy вместо strcpy. Вы уже отсканировали конец строки с помощью strcspn, чтобы получить количество символов в строке, а затем nul-termianted строка в этой точке. Нет необходимости снова искать конец строки с помощью strcpy, просто скопируйте количество символов (+1, чтобы также скопировать символ nul-terminating ) с memcpy.

Попытайтесь понять, можете ли вы понять, почему функция free() включена в вышеприведенную функцию и для какой цели она служит.

Собрав полный пример, который динамически распределяется, вы могли бы сделать что-то похожее на следующее:

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

#define MAXNM  1024

struct details {
    char *forename;
    char *surname;
};

struct details *read_person (void)
{
    char buf[MAXNM];
    size_t len;
    struct details *d = malloc (sizeof *d);

    if (d == NULL) {
        perror ("malloc-d");
        return NULL;
    }

    printf ("Enter your forename: ");
    fgets (buf, MAXNM, stdin);
    len = strcspn(buf, "\n");
    buf[len] = 0;
    d->forename = malloc (len + 1);
    if (d->forename == NULL) {
        perror ("malloc-d->forename");
        free (d);
        return NULL;
    }
    memcpy (d->forename, buf, len + 1);

    printf ("Enter your surname : ");
    fgets (buf, MAXNM, stdin);
    len = strcspn(buf, "\n");
    buf[len] = 0;
    d->surname = malloc (len + 1);
    if (d->surname == NULL) {
        perror ("malloc-d->surname");
        free (d->forename);
        free (d);
        return NULL;
    }
    memcpy (d->surname, buf, len + 1);

    return d;
}

int main (void) {

    struct details *person = read_person();

    if (person != NULL) {   /* validate the function succeeded */
        printf ("\nname: %s, %s\n", person->forename, person->surname);

        free (person->forename);
        free (person->surname);
        free (person);
    }

    return 0;
}

( примечание: вся память освобождается до выхода из программы. Поймите, что память будет освобождена автоматически при выходе, но если у вас есть привычка всегда заботиться о своих 3-х обязанностях, касающихся динамически выделяемого памяти, у вас никогда не возникнет проблем с утечкой памяти в дальнейшем по мере увеличения размера кода.)

Пример использования / Вывод

Все примеры дают одинаковый результат. Примером может быть:

$ ./bin/struct_name3
Enter your forename: Samuel
Enter your surname : Clemens

name: Samuel, Clemens

Использование памяти / проверка ошибок

Крайне важно, чтобы вы использовали программу проверки ошибок памяти, чтобы убедиться, что вы не пытаетесь получить доступ к памяти или писать за пределами / за пределами выделенного блока, пытаться прочитать или основать условный переход на неинициализированном значении и, наконец, , чтобы подтвердить, что вы освобождаете всю выделенную память.

Для Linux valgrind - нормальный выбор. Для каждой платформы есть похожие проверки памяти. Все они просты в использовании, просто запустите вашу программу через него.

$ valgrind ./bin/struct_name3
==14430== Memcheck, a memory error detector
==14430== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==14430== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==14430== Command: ./bin/struct_name3
==14430==
Enter your forename: Samuel
Enter your surname : Clemens

name: Samuel, Clemens
==14430==
==14430== HEAP SUMMARY:
==14430==     in use at exit: 0 bytes in 0 blocks
==14430==   total heap usage: 3 allocs, 3 frees, 31 bytes allocated
==14430==
==14430== All heap blocks were freed -- no leaks are possible
==14430==
==14430== For counts of detected and suppressed errors, rerun with: -v
==14430== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Всегда подтверждайте, что вы освободили всю выделенную память и что ошибок памяти нет.

Обработка qsort массива структурных деталей

После того, как вы решили все начальные проблемы с использованием struct details, я почти забыл, что ваш первоначальный вопрос относится к qsort. qsort прост в использовании, вы просто передаете массив, количество элементов для сортировки, размер каждого элемента и функцию сравнения, которая возвращает -1, 0, 1 в зависимости от того, равен ли первый элемент ранее, равен или сортирует после того, как второй элемент передан функции.

Ваша единственная ответственность при использовании qsort заключается в написании функции compare. В то время как новые пользователи qsort обычно закатывают глаза, когда видят объявление для функции:

int compare (const void *a, const void *b) { ... }

На самом деле все довольно просто. a и b - это просто указатели на элементы массива для сравнения. Так что если у вас есть массив struct details. a и b являются просто указателями на struct details. Вам просто нужно написать функцию compare для приведения a и b к соответствующему типу.

Чтобы отсортировать по forename, вы можете сравнить функцию:

int compare_fore (const void *a, const void *b)
{
    const struct details *name1 = a,
                         *name2 = b;
    int rtn = strcmp (name1->forename, name2->forename); /* compare forename */

    if (rtn != 0)       /* if forenames are different */
        return rtn;     /* return result of strcmp */

    /* otherwise return result of strcmp of surname */
    return strcmp (name1->surname, name2->surname);      /* compare surname */
}

Чтобы отсортировать по surname, вам потребуется:

int compare_sur (const void *a, const void *b)
{
    const struct details *name1 = a,
                         *name2 = b;
    int rtn = strcmp (name1->surname, name2->surname);

    if (rtn != 0)
        return rtn;

    return strcmp (name1->forename, name2->forename);
}

Затем в main() вы просто объявляете массив struct details и вызываете qsort, например,

int main (void) {

    struct details person[MAXS];

    for (int i = 0; i < MAXS; i++)
        person[i] = read_person();

    qsort (person, MAXS, sizeof *person, compare_fore);

    puts ("\nSorted by forename:\n");
    for (int i = 0; i < MAXS; i++)
        printf ("  %s, %s\n", person[i].forename, person[i].surname);

    qsort (person, MAXS, sizeof *person, compare_sur);

    puts ("\nSorted by surname:\n");
    for (int i = 0; i < MAXS; i++)
        printf ("  %s, %s\n", person[i].forename, person[i].surname);

    return 0;
}

Или, в целом, приведя полный пример, вы получите:

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

#define MAXS    5   /* if you need a constant, #define one (or more) */
#define MAXNM  32   /* (don't skimp on buffer size) */

struct details {
    char forename[MAXNM];
    char surname[MAXNM];
};

int compare_fore (const void *a, const void *b)
{
    const struct details *name1 = a,
                         *name2 = b;
    int rtn = strcmp (name1->forename, name2->forename); /* compare forename */

    if (rtn != 0)       /* if forenames are different */
        return rtn;     /* return result of strcmp */

    /* otherwise return result of strcmp of surname */
    return strcmp (name1->surname, name2->surname);      /* compare surname */
}

int compare_sur (const void *a, const void *b)
{
    const struct details *name1 = a,
                         *name2 = b;
    int rtn = strcmp (name1->surname, name2->surname);

    if (rtn != 0)
        return rtn;

    return strcmp (name1->forename, name2->forename);
}

struct details read_person (void)
{
    struct details d;

    printf ("\nEnter your forename: ");
    fgets (d.forename, MAXNM, stdin);
    d.forename[strcspn(d.forename, "\n")] = 0;  /* trim \n from end */

    printf("Enter your surname : ");
    fgets(d.surname, MAXNM, stdin);
    d.surname[strcspn(d.surname, "\n")] = 0;    /* trim \n from end */

    return d;
}

int main (void) {

    struct details person[MAXS];

    for (int i = 0; i < MAXS; i++)
        person[i] = read_person();

    qsort (person, MAXS, sizeof *person, compare_fore);

    puts ("\nSorted by forename:\n");
    for (int i = 0; i < MAXS; i++)
        printf ("  %s, %s\n", person[i].forename, person[i].surname);

    qsort (person, MAXS, sizeof *person, compare_sur);

    puts ("\nSorted by surname:\n");
    for (int i = 0; i < MAXS; i++)
        printf ("  %s, %s\n", person[i].forename, person[i].surname);

    return 0;
}

Пример использования / Вывод

$ ./bin/struct_name4

Enter your forename: Mickey
Enter your surname : Mouse

Enter your forename: Minnie
Enter your surname : Mouse

Enter your forename: Samuel
Enter your surname : Clemens

Enter your forename: Mark
Enter your surname : Twain

Enter your forename: Walt
Enter your surname : Disney

Sorted by forename:

  Mark, Twain
  Mickey, Mouse
  Minnie, Mouse
  Samuel, Clemens
  Walt, Disney

Sorted by surname:

  Samuel, Clemens
  Walt, Disney
  Mickey, Mouse
  Minnie, Mouse
  Mark, Twain

( примечание: , поскольку Mickey и Minnie оба имеют фамилию Mouse, для сортировки по surname они затем дополнительно сортируются по forname, поэтому существует правильный канонический сортировать по списку выше)

Теперь, надеюсь, мы рассмотрели все аспекты вашего вопроса. Посмотрите вещи и дайте мне знать, если у вас есть дополнительные вопросы.

...