繁体   English   中英

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

[英]How to sort an array of a structure using qsort?

我和一个朋友正在尝试自学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;
}

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

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

其次,将d local声明为read_person ,然后在末尾返回d ,将其分配回main() 很好,但是……了解为什么很好。 声明dstruct details所有成员均具有自动存储类型,并且此时每个成员的存储已完全定义。 无需在函数中的任何地方调用malloc 当您最后返回d时, struct 分配允许函数返回结构,并在main()分配所有可用值。

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

无需创建全局的struct数组,只需声明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字符 '\\0' (或等价的0 )覆盖尾随的'\\n'尽管有多种方法可以执行此操作,但使用strcspn可能是最健壮和最简单的方法之一,因为strcspn返回不包含在排除集中的字符串中的字符数,因此只需设置排除集即可包含行尾"\\n" ,它返回字符串中的字符数,直到'\\n'为止,然后将其简单地设置为0

还要注意:使用voidstruct 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 ,你可以学学你如何能够用它来分配存储空间的结构,以及每个forenamesurname ,这样既准确地使用正确的字节数持有进入的名称和不再。 在为任何分配的内存分配任何内容时,您有3个职责 :(1)在使用内存块之前始终验证分配是否成功 ,(2) 始终保留指向内存块起始地址的指针,因此,(3)不再需要它时可以将其释放

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

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扫描了字符串的strcspn以获取字符串中的字符数,然后在该位置使用nul终止了该字符串。无需扫描对于再次使用strcpy的字符串结尾,只需使用memcpy复制字符数(+1也复制nul终止字符)。

尝试看看您是否可以理解上述函数中为何包含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;
}

注意:所有内存在程序退出之前都会被释放。要了解的是,该内存将在退出时自动释放,但是如果您习惯于始终照顾与动态分配的内存有关的三项职责,那么您就不会有泄漏内存的问题。稍后随着代码大小的增加。)

使用/输出示例

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

$ ./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 details ab只是指向 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

注意:由于MickeyMinniesurname均为Mouse ,因此按surname排序,然后forname forname排序,因此上面的列表中存在正确的规范排序)

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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