【问题标题】:How to sort an array of a structure using qsort?如何使用 qsort 对结构的数组进行排序?
【发布时间】:2019-05-31 17:38:51
【问题描述】:

我和一个朋友正在尝试自学 C,并决定做一个最初被认为很容易的练习,在其中我们创建一个包含 1. 名字和 2. 姓氏的两个 char 的结构。函数 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;
}

【问题讨论】:

  • 你的比较功能在哪里?
  • 这就是程序失败的地方。我不知道如何编写比较结构的每个字符串的函数
  • 如果您可以显示失败的Minimal Reproducible Example,则更容易看出它“无法排序”的原因。了解如何将 qsort 与带有初始化数据的普通数组一起使用,以使示例保持简单。从surname 上的排序开始。然后考虑forename 如果它们相同。一步一步来。
  • 您是否为寻找类似问题而烦恼?搜索框中的[c] sort structure array qsort 将产生大量的人,他们对使用 qsort 对结构数组进行排序有类似/相同的查询。不相关,read_person 中的malloc 完全没有意义,但至少你没有泄露它,所以.. 道具。

标签: c arrays struct qsort


【解决方案1】:

这里发生了许多不正确的事情,但也涉及到一些微妙的问题,这些问题在尝试填充结构时不会很明显。

首先,您声明一个由struct details(其中5 个)组成的全局数组。不要那样做。虽然合法,但您只需在全局范围内声明 struct,然后在 main() 内声明每个实例,并将结构的副本或指向结构的指针作为参数传递给代码中需要的任何函数它。

其次,您将d 声明为read_person 的本地地址,然后在最后返回d 以分配回main()。这很好,......但是......明白为什么它很好。当您声明d 时,struct details 的所有成员都具有自动存储类型,并且每个成员的存储都在此时完全定义。无需在函数中的任何位置调用 malloc。当您在最后返回 d 时,struct assignment 允许函数返回结构,并在 main() 中分配并返回所有值。

最后在main(),你调用read_person();,但是没有分配返回值或者以任何方式使用d中存储的值。

不用创建一个全局结构数组,只需声明结构本身,例如:

#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',因此您需要用 nul 覆盖尾随 '\n' -character '\0'(或等效的0)。虽然有多种方法可以做到这一点,但使用strcspn 可能是最可靠和最简单的方法之一。strcspn 返回排除集中未包含的字符串中的字符数。因此,只需让您的排除集包含行尾"\n",它就会返回其中的字符数直到 '\n' 的字符串,然后您只需将其设置为 0)

(另请注意:struct details read_person (void) 中使用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,但请注意,使用指向结构的指针,您使用-&gt; 运算符而不是@987654361 来访问成员@。例如你可以这样做:

#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,那么你不妨学习一下如何使用它来为结构分配存储空间,以及每个forenamesurname,以便两者都使用正确的保存输入名称的字节数,仅此而已。在为任何内容分配存储时,您对分配的任何内存块有 3 个责任:(1) 在使用内存块之前始终验证分配是否成功,(2) 总是为内存块保留一个指向起始地址的指针,因此,(3) 当不再需要它时可以释放

这将在您动态分配存储的任何位置添加一些重复但重要的代码行。例如,在这种情况下,您为结构和结构内的forenamesurname 动态分配,您的结构声明和函数可能是:

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 终止。无需再次使用 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) { ... }

其实很简单。 ab 只是指向要比较的数组元素的指针。因此,如果您有一个 数组 struct detailsab 只是 指向 struct details 的指针。您只需编写 compare 函数即可将 ab 转换为适当的类型。

要对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

(注意: 因为MickeyMinnie 都有姓氏Mouse,对于按surname 排序,然后它们进一步按forname 排序,所以有一个正确的上面列表中的规范排序)

现在,希望我们已经解决了您问题的所有方面。如果您还有其他问题,请仔细查看并告诉我。

【讨论】:

    猜你喜欢
    • 2021-04-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-02
    • 2014-07-04
    相关资源
    最近更新 更多