首页 文章

使用qsort和structs对列表进行排序

提问于
浏览
1

我是C的初学者并使用结构 . 我正在尝试创建一个对日期列表进行排序的程序 . 用户首先输入日期数,然后输入日期本身,即月,日和年 . 然后使用qsort我想按时间顺序对它进行排序(首先是按年然后是按月计算) . 我试图先对年份进行排序,但我只是将输出设为“0” .

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

typedef struct {
    char* month;
    int day;
    int year;
} date;

int sort(const void* a, const void* b)
{

    date* date1 = (date*)a;
    date* date2 = (date*)b;

    if (date2->year != date1->year) {
        int year2 = date2->year;
        int year1 = date2->year;
        if (year1 < 14) {
            year1 = year1 + 100;
        }
        if (year2 < 14) {
            year2 = year2 + 100;
        }
        int yearcompare = year2 - year1;
        return -yearcompare;
    }
}

output(date* ar, int i, int n)
{

    for (i = 0; i < n; i++) {
        //printf("Enter the date (month day year) i n the following format: text number number");
        // printf("%s ", ar[i].month);
        //printf("%d ", ar[i].day);
        printf("%d\n", ar[i].year);
    }
}

int main()
{
    int n;
    int i;
    int MIN_SIZE = 0;
    int MAX_SIZE = 1000;

    while (1) {
        printf("Enter number of dates you want to enter (between 1 and 10000):\n");
        scanf("%d", &n);

        if (n < MIN_SIZE) {

            printf("You have entered a number lower than 0\n");
        }

        if (n > MAX_SIZE) {

            printf("You have entered a number higher than 1000\n");
        }

        else {

            break;
        }
    }

    date* ar = malloc(sizeof(int) * n);
    //ALLOCATE MEMORY

    printf("Enter the date (month day year) in the following format: text, number(between 1 and 31), number(between 00 and 12): \n");
    for (i = 0; i < n; i++) {
        scanf("%s", ar[i].month);
        scanf("%d", &ar[i].day);
        scanf("%d", &ar[i].year);
    }

    qsort(ar, n, sizeof(date), sort);

    output(ar, i, n);
}

1 回答

  • 1

    看起来好像你需要相当多的帮助把拼图的所有部分组合在一起 . 首先,在 datedate 中,包括 char *month . 当你分配 ar 时,这是一个未初始化的指针,这意味着你需要为 ar[i].month 单独分配 . 你可以自由地做到这一点,(在这种情况下你可以有效地使用 strdup ),但为什么呢?如果您正在使用月份的字符串输入,则最大长度为10个字符( September nul-byte ) . 只需使用静态声明的 month10 或更多字符,并避免在 month 上进行动态分配 .

    例如,您可以使用单独的 #define 指令声明有用的常量以用于您的代码,或者您可以使用全局 enum 来完成相同的操作,例如:

    /* constants for max chars, max day, max year, max size */
    enum { MAXC = 12, MAX_DAY = 31, MAX_YEAR = 2017, MAX_SIZE = 1000 };
    
    typedef struct {
        char month[MAXC];   /* either make static or allocate separately */
        unsigned day;
        unsigned year;
    } date;
    

    您将遇到的下一次火车残骸是在 scanf 中混合字符和数字输入,每次调用时都不会清空 input buffer (例如 stdin ) . 这意味着如果用户为 'n' 输入了有效小数以外的其他内容(例如,如果他意外命中 'q' 而不是 '1' ),则 "q\n" 将保留在输入缓冲区中,该缓冲区将作为下面 ar[0].month 的输入 . 为防止这种情况发生,您需要手动清空输入缓冲区(或使用 fgets 后跟 sscanf 来解析用户输入 - 使用 scanf 进行用户输入时存在许多缺陷) .

    尽管如此,你可以很容易地清空 stdin . 您可以使用_197349进行内联或创建一个短函数,如果您将反复调用它以减少打字,例如:

    /* empty character remaining in stdin */
    void empty_stdin ()
    {
        int c;
        while ((c = getchar ()) != '\n' && c != EOF) {}
    }
    

    当你接受输入时(无论是 scanf 系列函数还是 fgets (或任何其他方法),总是 validate 用户输入 . 你只知道猫可能正在踩键盘 . 另外,总是检查 EOF 表示用户通过Ctrl d或Ctrl z取消输入(在windoze上) . 例如:

    while (1) {     /* obtain valid 'n', compare with using fgets below */
    
            int rtn; /* varaible to save return of scanf -- always validate */
    
            printf ("Enter number of dates to be entered (between 1 & 1000): ");
            if ((rtn = scanf ("%d", &n)) != 1) {   /* if conversion failed */
                if (rtn == EOF) {   /* test for user cancelation of input */
                    fprintf (stderr, "note: user canceled input, exiting.\n");
                    return 0;
                }                   /* otherwise simply an invalid input */
                fprintf (stderr, "error: invalid input.\n");
                goto tryagain;
            }
    
            if (n < 0) {            /* invalid input < 0 */
                fprintf (stderr, "error: invalid input (n < 0).\n");
                goto tryagain;
            }
    
            if (n > MAX_SIZE) {     /* invalid input > MAX_SIZE */
                fprintf (stderr, "error: invalid input (n > %d).\n", MAX_SIZE);
                goto tryagain;
            }
    
            break;      /* if we are here - we have a good value, break */
    
          tryagain:;    /* label for goto to jump over break */
    
            empty_stdin ();   /* empty characters that remain in input buffer */
        }
    

    与使用 fgetssscanf 进行比较,以读取/解析 monthdayyear 输入 . 你可以做一些简单的事情:

    for (i = 0; i < n;) {   /* loop until all elements filled */
    
            char buf[MAX_DAY + 1] = "", ans[MAXC] = "";
    
            /* if fgets return is NULL, EOF encountered */
            if (fgets (buf, MAX_DAY + 1, stdin) == NULL) {
                fprintf (stderr, "note: user canceled input, exiting.\n");
                return 0;
            }
    
            /* parse with sscanf, validate 3 conversion took place */
            if (sscanf (buf, "%11s %u %u", ar[i].month, &ar[i].day, &ar[i].year) != 3)
            {
                fprintf (stderr, "error: invalid input.\n");
                continue;
            }
    
            i++;    /* only increment if valid sscanf conversion took place */
        }
    

    没有必要将 int i 作为参数传递给函数 output ,只需在本地声明它,例如:

    /* output n elements of array of struct date */
    void output (date *ar, int n)
    {
        int i;
    
        printf ("\nOutput sorted by year:\n\n");
    
        for (i = 0; i < n; i++)
            printf ("  %s %d %d\n", ar[i].month, ar[i].day, ar[i].year);
    }
    

    接下来,当您的 sort 函数可以工作时,您可以按年度压缩排序,同时通过使用不等式来避免潜在的溢出:

    /* sort struct date on year */
    int sort (const void *a, const void *b)
    {
        date *date1 = (date *) a;
        date *date2 = (date *) b;
    
        if (date2->year != date1->year)
            return (date1->year > date2->year) - (date1->year < date2->year);
    
        return 0;
    }
    

    最后,如果你分配内存,你有责任保留一个指向块起始的指针,然后在不再需要时保留内存 . 虽然它将在 exit 上释放,但要养成跟踪和释放所分配的所有内存的习惯 . 当你从事更复杂的项目时,良好的习惯会很好地为你服务 .

    如果用户只是点击Enter而不是输入日期,则完全放入并添加退出提示,您可以执行以下操作:

    #include <stdio.h>
    #include <stdlib.h>
    
    /* constants for max chars, max day, max year, max size */
    enum { MAXC = 12, MAX_DAY = 31, MAX_YEAR = 2017, MAX_SIZE = 1000 };
    
    typedef struct {
        char month[MAXC];   /* either make static or allocate separately */
        unsigned day;
        unsigned year;
    } date;
    
    /* empty character remaining in stdin */
    void empty_stdin ()
    {
        int c;
        while ((c = getchar ()) != '\n' && c != EOF) {}
    }
    
    /* sort struct date on year */
    int sort (const void *a, const void *b)
    {
        date *date1 = (date *) a;
        date *date2 = (date *) b;
    
        if (date2->year != date1->year)
            return (date1->year > date2->year) - (date1->year < date2->year);
    
        return 0;
    }
    
    /* output n elements of array of struct date */
    void output (date *ar, int n)
    {
        int i;
    
        printf ("\nOutput sorted by year:\n\n");
    
        for (i = 0; i < n; i++)
            printf ("  %s %d %d\n", ar[i].month, ar[i].day, ar[i].year);
    }
    
    int main (void) {
    
        int i, n;
        date *ar = NULL;
    
        while (1) {     /* obtain valid 'n', compare with using fgets below */
    
            int rtn; /* varaible to save return of scanf -- always validate */
    
            printf ("Enter number of dates to be entered (between 1 & 1000): ");
            if ((rtn = scanf ("%d", &n)) != 1) {   /* if conversion failed */
                if (rtn == EOF) {   /* test for user cancelation of input */
                    fprintf (stderr, "note: user canceled input, exiting.\n");
                    return 0;
                }                   /* otherwise simply an invalid input */
                fprintf (stderr, "error: invalid input.\n");
                goto tryagain;
            }
    
            if (n < 0) {            /* invalid input < 0 */
                fprintf (stderr, "error: invalid input (n < 0).\n");
                goto tryagain;
            }
    
            if (n > MAX_SIZE) {     /* invalid input > MAX_SIZE */
                fprintf (stderr, "error: invalid input (n > %d).\n", MAX_SIZE);
                goto tryagain;
            }
    
            break;      /* if we are here - we have a good value, break */
    
          tryagain:;    /* label for goto to jump over break */
    
            empty_stdin ();   /* empty characters that remain in input buffer */
        }
    
        empty_stdin ();     /* empty characters that remain in input buffer */
    
        /* allocate array of struct ar, n elements */
        if ((ar = malloc (sizeof *ar * n)) == NULL) {
            fprintf (stderr, "error: virtual memory exhausted.\n");
            return 1;
        }
    
        /* provide format instructions */
        printf ("Enter the date (month day year)\n"
                "  format, e.g.:  Jan 18 2017\n\n");
    
        for (i = 0; i < n;) {   /* loop until all elements filled */
    
            char buf[MAX_DAY + 1] = "", ans[MAXC] = "";
    
            printf (" date[%2d] : ", i + 1);    /* prompt for input */
    
            /* if fgets return is NULL, EOF encountered */
            if (fgets (buf, MAX_DAY + 1, stdin) == NULL) {
                fprintf (stderr, "note: user canceled input, exiting.\n");
                return 0;
            }
    
            if (*buf == '\n') { /* if first char is '\n', user just hit enter */
                printf ("no input provided, quit (y/n)? ");
                if (fgets (ans, MAXC, stdin) && (*ans == 'y' || *ans == 'Y'))
                    return 0;
                else if (!*ans) {   /* if ans NULL, EOF encountered */
                    fprintf (stderr, "note: user canceled input, exiting.\n");
                    return 0;
                }
            }
    
            /* parse with sscanf, validate 3 conversion took place */
            if (sscanf (buf, "%11s %u %u", ar[i].month, &ar[i].day, &ar[i].year) != 3)
            {
                fprintf (stderr, "error: invalid input.\n");
                continue;
            }
    
            i++;    /* only increment if valid sscanf conversion took place */
        }
    
        qsort (ar, n, sizeof (date), sort);     /* sort by year */
    
        output (ar, n);     /* output results */
    
        free (ar);      /* free ar - you allocate it, you free it */
    
        return 0;
    }
    

    注意:有很多很多方法可以处理代码的每个部分 . 如果你看一下大部分行的位置,它们会在 validating 输入中使用 . 这只是验证的最低限度 . 理想情况下,将每个 dayyear 的值与最大/最小值进行比较,然后将每个 month 与查找(或散列)表进行比较,以验证每个月是否为有效月份(您还可以使用日期/时间函数,但是留待另一个问题)

    Example Use/Output

    $ ./bin/qsortstruct
    Enter number of dates to be entered (between 1 & 1000): 4
    Enter the date (month day year)
      format, e.g.:  Jan 18 2017
    
     date[ 1] : September 11 2001
     date[ 2] : April 22 2010
     date[ 3] : June 2 1968
     date[ 4] : February 13 1979
    
    Output sorted by year:
    
      June 2 1968
      February 13 1979
      September 11 2001
      April 22 2010
    

    仔细看看,确保您了解正在发生的事情的每个部分,并询问您是否还有其他问题 .

相关问题