如何使用 C 中的整数对带有字符串的结构进行排序?

How to sort a struct with strings using its integers in C?

我正在用 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] = '[=10=]';  
        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

如您所见,它似乎 "sort" 不知何故。然而,这远非我需要完成的。 我已经尝试修改 compare() 函数,但没有成功。 我可能在这里遗漏了什么?

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

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

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

您确实在 tab->amnt 中拥有正确数量的元素,但起始指针 &tab->gold 并未指向 table 的开头,而是指向 table 的中间第一个元素,所以 qsort 无法正常工作。


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

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

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

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

最好把printTable改成

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

并用

调用它
printTable(tab, N);

readTable 相同。


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

tab->country = arrayAlloc(100);

您正在预分配第一个数组成员中 country 指针指向的 space,但无论如何 readTable 都会为 [=24= 分配 space ] strdup 所有数组成员。


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

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

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

首先,如果您的 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] = '[=13=]';  
        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

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

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

其次,您使用 tab->amnt = N,它等同于 tab[0].amnt = N,即它只为第一个条目设置 amnt。如果您随后对数组进行排序,则第一个条目可能会被另一个未将 amnt 设置为特定值的条目替换。所以你的 print-函数,它再次依赖于 amnt 在第一个元素中,将算错。

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

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