【问题标题】:How to sort a struct with strings using its integers in C?如何使用 C 中的整数对带有字符串的结构进行排序?
【发布时间】:2017-06-14 11:36:10
【问题描述】:

我正在用 C 编写一个程序,该程序应该根据奥运会的金牌数对国家进行排名。

代码如下:

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

typedef struct _Table {
    char *country;
    int amnt, gold, silver, bronze;
} Table;

char *arrayAlloc(int size) {
    char *array;
    array = (char *) malloc(sizeof(char) * size);
    return array;
}

void readTable(Table *ptr) {
    char buffer[100], *cpyPtr, *savePtr;
    for (int i = 0; i < ptr->amnt; ++i) {
        fgets(buffer, sizeof(buffer), stdin);
        buffer[strlen(buffer) - 1] = '\0';  
        ptr[i].country = strtok_r(buffer, " ", &savePtr);
        ptr[i].country = strdup(ptr[i].country);            
        cpyPtr = strtok_r(NULL, " ", &savePtr);
        ptr[i].gold = strtol(cpyPtr, &cpyPtr, 10);
        cpyPtr = strtok_r(NULL, " ", &savePtr);
        ptr[i].silver = strtol(cpyPtr, &cpyPtr, 10);
        cpyPtr = strtok_r(NULL, " ", &savePtr); 
        ptr[i].bronze = strtol(cpyPtr, &cpyPtr, 10);
    }

}

void printTable(Table *ptr) {
    for (int i = 0; i < ptr->amnt; ++i) {
        printf("%s %d %d %d\n", ptr[i].country, ptr[i].gold,ptr[i].silver, ptr[i].bronze);
    }
}

int compare(const void *p, const void *q) {
    int l = ((Table *)p)->gold;
    int r = ((Table *)q)->gold; 
    return (r - l);
}


int main(int argc, char const *argv[]) {
    int N;      // Amount of lines
    scanf("%d", &N);
    getchar();
    Table tab[N];
    tab->amnt = N;
    tab->country = arrayAlloc(100);

    readTable(tab);

    qsort(&tab->gold, tab->amnt, sizeof(Table), compare);

    printTable(tab);

    free(tab->country);
    return 0;
}

输入示例:

4
BRA 3 4 5
USA 23 76 34
CHN 23 54 12
GER 10 20 23

预期输出:

USA 23 76 34
CHN 23 54 12
GER 10 20 23
BRA 3 4 5

我得到了什么:

BRA 23 54 12
GER 23 76 34
CHN 3 4 5
USA 10 20 23

如您所见,它似乎以某种方式对其进行“排序”。然而,这远不是我需要完成的。 我已经尝试修改 compare() 函数,但没有成功。 我可能在这里错过了什么?

【问题讨论】:

  • 变量tab 是一个数组。您应该排序的是 this 数组,而不是第一个元素 gold 成员。表达式tab-&gt;gold 等于tab[0].gold
  • 您的minimal reproducible example 充满了错误,您的问题变得太宽泛了。
  • 谢谢@Stargateur,下次我将按照此链接中的说明进行操作。

标签: c struct qsort


【解决方案1】:
qsort(&tab->gold, tab->amnt, sizeof(Table), compare);

前两个参数应该是指向数组的指针 (Table tab[N];) 和数组中元素的数量 (N),即:

qsort(tab, N, sizeof(Table), compare);

您确实在tab-&gt;amnt 中有正确数量的元素,但是起始指针&amp;tab-&gt;gold 没有指向表格的开头,而是在第一个元素的中间,所以qsort 不能正常工作。


另外,你有这样的循环函数:

void printTable(Table *ptr) {
    for (int i = 0; i < ptr->amnt; ++i) {

这意味着您在其第一个成员中携带数组的大小。这看起来很奇怪,这意味着除了数组的第一个成员之外,数组成员 'amnt' 是多余的。

但如果数组被排序,第一个数组成员就不再是第一个,ptr-&gt;amnt 不再包含正确的大小。

最好把printTable改成

void printTable(Table *ptr, unsigned size) {
    for (int i = 0; i < size; ++i) {

并用

调用它
printTable(tab, N);

readTable 也一样。


我也不确定这样做的目的是什么:

tab->country = arrayAlloc(100);

您在第一个数组成员中预分配了country 指针指向的空间,但无论如何readTablecountrystrdup 分配空间给所有数组成员。


您可能还需要考虑检查用户输入的值或N,或者通过malloctab 分配内存。现在tab分配在栈上,如果用户输入一些大的数字导致栈溢出,可能会导致程序崩溃。

另外,由于您不检查readTablestrtok_r 调用的返回值,如果用户输入格式错误,程序将崩溃。

【讨论】:

  • 还有其他问题
  • @Stargateur,好吧,他们有点工作,只要排序没有。但你是对的,我只先查看了排序步骤,这似乎是基于示例输出的问题。
  • 谢谢@ilkkachu,在您提出修改建议后它运行良好。另外,我不知道我不能以这种方式通过 ptr->amnt 。非常感谢!
【解决方案2】:

有几个问题;但主要的两个如下:

qsort(&amp;tab-&gt;gold, tab-&gt;amnt, sizeof(Table), compare) 中,您不传递数组的开头(必要时),而是传递指向数组中第一个元素的数据成员的指针。这意味着传递的指针与数组的开头有一个特定的偏移量,并且几乎指向第一个元素的“中间”。由于qsort 将相对于您提供给qsort 的指针交换元素(即所提供大小的内存块),因此每个对象可能会“分裂”并产生意外和奇怪的结果(很可能也会产生未定义的行为)。

其次,你使用tab-&gt;amnt = N,它相当于tab[0].amnt = N,即它只为第一个条目设置amnt。如果然后对数组进行排序,则第一个条目可能会被另一个条目替换,该条目没有将amnt 设置为特定值。因此,您的 print-function 再次依赖于 amnt 在第一个元素中,将计算错误。

所以写qsort(tab, N, sizeof(Table), compare)并在元素之外管理数组的大小,即引入一个单独的参数size,然后将其传递给print-function。

也许还有其他问题,但这两个对我来说是最可疑的。

【讨论】:

    【解决方案3】:

    您的代码中存在不止一个问题。

    首先,如果您的tab 动态分配会更好。

    Table* tab = malloc(sizeof(Table)*N);
    

    可变长度数组仅受 c99 及更高版本支持,如果定义了 __STDC_NO_VLA_,某些 C11 实现可能也不支持它们(检查 C11 标准 6.10.8.3)

    其次,在tab 上迭代的所有内容都必须知道tab 的大小,即N,您必须传递,例如

    void readTable(Table *ptr, const int N) {
        char buffer[100], *cpyPtr, *savePtr;
        for (int i = 0; i < N; ++i) {...
    

    最后qsort需要调用如下:

     qsort(tab, N,sizeof(Table), compare);
    

    以下将产生预期的输出(请注意,我没有检查任何其他问题):

    typedef struct _Table {
        char *country;
        int amnt, gold, silver, bronze;
    } Table;
    
    char *arrayAlloc(int size) {
        char *array;
        array = (char *) malloc(sizeof(char) * size);
        return array;
    }
    
    void readTable(Table *ptr, const int N) {
        char buffer[100], *cpyPtr, *savePtr;
        for (int i = 0; i < N; ++i) {
            fgets(buffer, sizeof(buffer), stdin);
            buffer[strlen(buffer) - 1] = '\0';  
            ptr[i].country = strtok_r(buffer, " ", &savePtr);
            ptr[i].country = strdup(ptr[i].country);            
            cpyPtr = strtok_r(NULL, " ", &savePtr);
            ptr[i].gold = strtol(cpyPtr, &cpyPtr, 10);
            cpyPtr = strtok_r(NULL, " ", &savePtr);
            ptr[i].silver = strtol(cpyPtr, &cpyPtr, 10);
            cpyPtr = strtok_r(NULL, " ", &savePtr); 
            ptr[i].bronze = strtol(cpyPtr, &cpyPtr, 10);
    
        }
    
    }
    
    void printTable(Table *ptr, const int size) {
        for (int i = 0; i < size; ++i) {
            printf("%s %d %d %d\n", ptr[i].country, ptr[i].gold,ptr[i].silver, ptr[i].bronze);
        }
    }
    
    int compare(const void *p, const void *q) {
    
        return ((Table *)p)->gold<((Table *)q)->gold;
    }
    
    
    int main(int argc, char const *argv[]) {
        int N;      // Amount of lines
        scanf("%d", &N);
        getchar();
         Table* tab = malloc(sizeof(Table)*N);
    
    
        readTable(tab,N);
        printTable(tab,N);
        qsort(tab, N,sizeof(Table), compare);
    
        printTable(tab,N);
    
        free(tab);
        return 0;
    }
    

    输出:

    BRA 3 4 5
    USA 23 76 34
    CHN 23 54 12
    GER 10 20 23
    
    USA 23 76 34
    CHN 23 54 12
    GER 10 20 23
    BRA 3 4 5
    

    【讨论】:

    • 为什么要动态分配tab
    • 考虑qsort(tab, N, sizeof *tab, compare);。无需回头查看tab 定义来查看sizeof(Table) 是否正确。还有Table* tab = malloc(sizeof *tab * N);,就像代码使用对象的大小而不是fgets(buffer, sizeof(buffer), stdin);中的类型的大小一样
    • 顺便说一句,buffer[strlen(buffer) - 1] = '\0'; 可以被用户输入 null 字符 作为fgets() 读取的第一个字符来利用。建议buffer[strcspn(buffer, "\n")] = '\0';
    • 我个人认为sizeof与显式类型一起使用时代码会清晰得多。关于动态分配,我已经编辑了答案。感谢您的建议。
    猜你喜欢
    • 2017-09-07
    • 1970-01-01
    • 2016-09-13
    • 2021-07-30
    • 2015-06-22
    • 2021-07-23
    • 1970-01-01
    • 1970-01-01
    • 2010-10-21
    相关资源
    最近更新 更多