如何使用qsort对结构数组进行排序?

时间:2019-05-31 17:38:52

标签: c arrays struct qsort

我和一个朋友正在尝试自学C,并决定去做一个最初被认为很容易的练习,在该练习中,我们创建了一个两个字符的结构,分别包含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 个答案:

答案 0 :(得分:2)

您在这里发生的许多事情是不正确的,但是也涉及到一些细微的问题,这些问题在尝试绊倒如何填充结构时不会明显。

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

第二,您将d声明为read_person的本地对象,然后最后返回d以便在main()中进行分配。很好,但是……了解为什么很好。当您声明d时,struct details的所有成员都具有自动存储类型,并且此时每个成员的存储已完全定义。无需在函数中的任何地方调用malloc。当您最后返回d时,struct assignment 允许函数返回该结构,并在main()中分配并提供所有值。

最后,在main()中,您调用read_person();,但无法以任何方式分配返回值或使用d中的存储值。

与其创建全局的struct数组,不如声明它本身,例如:

#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-character '\n'(或等效的'\0')。虽然有几种方法可以使用,但0的使用可能是最健壮且最可靠的方法之一最简单的方法。strcspn返回排除集中未包含的字符串中的字符数。因此,只需让您的排除集包含以下行即可:以strcspn结尾,它返回字符串中的字符数,直到"\n",然后将其简单地设置为'\n'

另请注意::在0中使用void来指定struct details read_person (void)不带参数。在C中,如果您只保留空白{{1 }},那么该函数将使用不确定的个参数)

然后在read_person中分配收益并以某种方式使用它,例如

()

另一种选择是在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分配存储空间,因此两者都使用正确的字节数来保存输入的名称,仅此而已。当为任何要分配的内存块分配3个职责的存储空间时:(1)在使用内存块之前,始终验证分配是否成功,(2) 始终为内存块保留指向起始地址的指针,因此,(3)不再需要时可以将其释放

无论您在哪里动态分配存储,这都会添加一些重复但重要的代码行。例如,在这种情况下,您为结构以及结构中的forenamesurname动态分配结构,则您的结构声明和函数可以是:

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扫描了字符串的结尾以获取数字字符串中的字符,然后用nul终止字符串。无需使用strcpy再次扫描字符串结尾,只需复制字符数(+1即可复制< em> nul-terminate 字符)和strcspn

尝试看看您是否可以理解上述功能中为何包含strcpy功能以及该功能的作用。

将完整的示例动态分配在一起,您可以执行以下操作:

memcpy

注意:在程序退出之前,所有内存都已释放。请理解,该内存将在退出时自动释放,但是如果您习惯于始终保持与动态有关的三项责任分配的内存,以后您就不会再随代码大小的增加而泄漏内存了。)

使用/输出示例

所有示例均产生相同的输出。一个例子是:

free()

内存使用/错误检查

当务之急是使用一个内存错误检查程序来确保您不会尝试访问内存或在已分配的块的边界之外/之外进行写入,不要试图以未初始化的值读取或基于条件跳转,最后,以确认您释放了已分配的所有内存。

对于Linux,#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; } 是正常选择。每个平台都有类似的内存检查器。它们都很容易使用,只需通过它运行程序即可。

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

name: Samuel, Clemens

始终确认已释放已分配的所有内存,并且没有内存错误。

处理结构详细信息数组的qsort

在使用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) 有关。 struct details易于使用,您只需传递数组,要排序的成员数,每个成员的大小以及一个根据第一个元素是否排序就返回qsort的比较函数,等于或在第二个元素传递给函数之后排序。

使用qsort的唯一职责是编写-1, 0, 1函数。尽管qsort的新用户通常在看到该函数的声明时会回头一想:

compare

实际上很简单。 qsortint compare (const void *a, const void *b) { ... } 只是指向要比较的数组元素的指针。因此,如果您有一个 a数组。 bstruct details只是指向{em> a的指针。您只需要编写b函数即可将struct detailscompare转换为适当的类型。

要对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,因此上面的列表中有正确的规范排序)

现在,希望我们能解决您问题的所有方面。仔细研究一下,如果您还有其他问题,请告诉我。