【问题标题】:C Program Using Structures To Re-arrange List使用结构重新排列列表的 C 程序
【发布时间】:2016-05-08 08:37:56
【问题描述】:

第一次 C 编程课并开始学习编程。我目前正在学习如何在 C 中使用结构,这是一项有助于理解此过程的学习任务。

程序应接受地址输入并按邮政编码从最低邮政编码到最高邮政编码组织它们。

下面的程序和输入/输出是我用来学习这个主题的程序之一。我想通过这个程序使用文件重定向将数据转储到文件中。例如:

ConsoleApplication1.exe < input.txt > testout.txt

现在所有转储到测试文件中的是:

Processed 18 sets of data.

显然是因为我在“takedate”函数中有 printf 语句来测试该过程。

我的问题是,获取输入、处理它然后将输出按邮政编码顺序转储到文件中的最佳方式是什么?任何人都可以推荐一个可以添加的函数/语句来实现这一点吗?

非常感谢您为使该程序正常运行所提供的帮助、时间和指导!

//header file initiating other headers
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct Person {
    char name[50];
    char address[50];
    char citystate[30];
    char zip[10];
} Person;

int takedata (Person * arr[]);
void sortdata (Person * arr[], int noElements);

int main (void) {

    //Intiate program
    int noElements = 0;
    Person *arr[50];

    //call input function
    //input(work, &count);
    takedata (arr);

    //sort files
    //call function
    //swap(work, &count);
    sortdata (arr, &noElements);

    //call output function
    //output(work, &count);

    //end program
    return 0;
}

void sortdata (Person * arr[], int noElements)
{
    /* temporary pointer to Person data type to aid with swapping */
    //Person * tempptr = (Person *)malloc(sizeof(Person));

    int i, j, compare;
    Person *tempptr = NULL;

    for (i = 0; i <= (noElements - 1); i++); {
        for (j = (i + 1); j <= noElements; j++) {
            compare = strcmp (arr[i]->zip, arr[j]->zip);
            if (compare > 0) {
                printf ("attempted sort %d times.\n", j);
                /* stores value in index i for array inside of temporary pointer  */
                tempptr = arr[i];
                arr[i] = arr[j];
                arr[j] = tempptr;
            }
        }
    }
}

int takedata (Person * arr[])
{
    /* counter variable */
    int i = 0;
    char teststring[25];

    while ((gets (teststring)) != NULL && i < 50) {

        /* dynamically allocates memory for each index of the array */
        arr[i] = (Person *) malloc (sizeof (Person));

        /* takes in data from user/ input file */
        strcpy (arr[i]->name, teststring);
        gets (arr[i]->address);
        gets (arr[i]->citystate);
        gets (arr[i]->zip);

        i++;
    }

    printf ("Processed %d sets of data.\n\n", i);

    return (i - 1);
}

正在使用的输入数据:

A1, A220294 Lorenzana Dr
Woodland Hills, CA
91364
B1, B2
19831 Henshaw St
Culver City, CA
94023
C1, C2
5142 Dumont Pl
Azusa, CA
91112
D1, D2
20636 De Forest St
Woodland Hills, CA
91364
A1, A2
20294 Lorenzana Dr
Woodland Hills, CA
91364
E1, E2
4851 Poe Ave
Woodland Hills, CA
91364
F1, F2
20225 Lorenzana Dr
Los Angeles, CA
91111
G1, G2
20253 Lorenzana Dr
Los Angeles, CA
90005
H1, H2
5241 Del Moreno Dr
Los Angeles, CA
91110
I1, I2
5332 Felice Pl
Stevenson Ranch, CA
94135
J1, J2
5135 Quakertown Ave
Thousand Oaks, CA
91362
K1, K2
720 Eucalyptus Ave 105
Inglewood, CA
89030
L1, L2
5021 Dumont Pl
Woodland Hills, CA
91364
M1, M2
4819 Quedo Pl
Westlake Village, CA
91362
I1, I2
5332 Felice Pl
Stevenson Ranch, CA
94135
I1, I2
5332 Felice Pl
Stevenson Ranch, CA
94135
N1, N2
20044 Wells Dr
Beverly Hills, CA
90210
O1, O2
7659 Mckinley Ave
Los Angeles, CA
90001

【问题讨论】:

  • 我什至还没有看过你的代码,但无论如何都反对提供可以阅读的代码(具有格式/缩进),提供测试输入,示例输出。这实际上比我们通常得到的要多得多:(
  • 输入的第一个数据损坏。
  • @Martin“取消投票”还是“点赞”?
  • 我不确定我是否理解这里的问题。您已经有了该程序的大纲,并且除了您的output 函数之外,它似乎大部分都是编码的。步骤很简单:(1)将数据读入数组(2)对数组进行排序(3)打印出排序后的数组。您是否在问如何遍历数组以打印其内容?你尝试过什么?

标签: c function pointers data-structures structure


【解决方案1】:

如 cmets 中所述,您的代码中存在大量错误。从安全的角度来看,永远不要使用gets。鉴于存在的安全风险,它已从 C11 中的 C 标准中删除,没有弃用

虽然不是错误,但 C 的标准编码风格避免了 CamelCase 变量,而支持所有 小写。参见例如NASA - C Style Guide, 1994

在 C 中,qsort 是库提供的事实上的排序例程。除非您只是想折磨自己,否则您应该使用qsortzip 上的指针数组进行排序,并且您的sortdata 例程可以完全删除。使用qsort 唯一需要编写的代码就是一个整数比较函数,这样qsort 就会知道如何对指针进行排序。由于您想要对指针列表进行排序,因此您需要认识到比较函数的每个输入都是一个指向 struct person 指针的指针,这意味着您将取消引用两次。 qsortzip 进行排序的简短比较函数可能是:

/** comparison function for qsort of pointers on zip */
int cmpzip (const void *a, const void *b)
{
    person *ap = *(person * const *)a;
    person *bp = *(person * const *)b;

    return strcmp (ap->zip, bp->zip);
}

(注意:虽然(person * const *) 转换可能看起来很奇怪,但这只是反映您正在处理其值不会改变的常量指针。您可以忽略const 修饰符并把演员写成(person **),这样在视觉上更有意义。)

基于zip 的整个指针数组被简化为:

    qsort (arr, nelem, sizeof *arr, cmpzip);

(使用 C 库提供的排序例程比滚动自己的排序例程更不容易出错...)

接下来,您的代码可能会在 50 个不同的地方之一失败,而您将毫无头绪。您将一直盲目地阅读和写作,直到发生 SEGFAULT(或其他错误)。如果您拨打malloc 失败怎么办?学习验证后续步骤所依赖的每个步骤,并验证所有用户输入。 (我将在此处添加关于不封装 malloc 的注释——这完全没有必要)验证示例:

main 上致电takedata

    if (!(nelem = takedata (arr, fp))) { /* read addresses */
        fprintf (stderr, "error: no elements read.\n");
        return 1;
    }

takedata 验证分配:

        /* allocate/validate memory for struct */
        if (!(arr[i] = malloc (sizeof *arr[i]))) {
            fprintf (stderr, "error: virtual memory exhausted.\n");
            exit (EXIT_FAILURE);
        }

验证每次读取:

        /* read/validate address from stdin */
        if (!fgets (buf, MAXC, fp)) {
            fprintf (stderr, "error: read failure, arr[%d]->address.\n", i);
            exit (EXIT_FAILURE);
        }

在可能的情况下添加其他验证,例如检查以确保您的 zip 是所有数字,例如

        if (!fgets (buf, MAXC, fp)) { /* zip */
            fprintf (stderr, "error: read failure, arr[%d]->zip.\n", i);
            exit (EXIT_FAILURE);
        }
        len = (size_t)rmcrlf (buf);  /* trims newline and returns length */
        char *p = buf;
        for (; *p; p++)
            if (*p < '0' || '9' < *p) { /* validate zip all numeric */
                fprintf (stderr, "error: invalid zip, arr[%d]->zip '%s'.\n",
                        i, buf);
                exit (EXIT_FAILURE);            
            }

我还想指出许多其他问题,但老实说,我迷失了方向。查看所有的 cmets,它们涵盖了大部分问题。

最后,将所有部分放在一个从文件中读取数据的示例中(在修复第一行之后),您可以执行类似于以下的操作。确保您了解代码的每一行、每个字符都在做什么,如果没有,请询​​问。代码将从指定为命令行第一个参数的文件中读取数据(如果没有给出参数,则默认从stdin)。

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

/* constants for max zip, citystate, (structs, name, addr), buf */
enum { MAXZ = 10, MAXCS = 30, MAXS = 50, MAXC = 128 };

typedef struct person {
    char name[MAXS];
    char address[MAXS];
    char citystate[MAXCS];
    char zip[MAXZ];
} person;

size_t takedata (person **arr, FILE *fp);
int rmcrlf (char *s);
int cmpzip (const void *a, const void *b);

int main (int argc, char **argv) {

    size_t i, nelem = 0;    /* nelem cannot be negative */
    person *arr[MAXS];      /* C-style avoids mixed case */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
        return 1;
    }

    if (!(nelem = takedata (arr, fp))) { /* read addresses */
        fprintf (stderr, "error: no elements read.\n");
        return 1;
    }
    if (fp != stdin) fclose (fp);   /* close file if not stdin */

    printf ("Processed %zu sets of data.\n\n", nelem);

    /* sort array of pointers on zip */
    qsort (arr, nelem, sizeof *arr, cmpzip);

    for (i = 0; i < nelem; i++) { /* print output free memory */
        printf (" name : %s  (%s)\n", arr[i]->name, arr[i]->zip);
        free (arr[i]);
    }

    return 0;
}

size_t takedata (person **arr, FILE *fp)
{
    if (!arr || !fp) {  /* validate arr and fp are non-NULL */
        fprintf (stderr, "takedata() error: invalid parameter.\n");
        return 0;
    }
    int i = 0;
    char buf[MAXC] = "";

    while (i < MAXS && fgets (buf, MAXC, fp)) {
        /* remove newline get length */
        size_t len = (size_t)rmcrlf (buf);

        /* allocate/validate memory for struct */
        if (!(arr[i] = malloc (sizeof *arr[i]))) {
            fprintf (stderr, "error: virtual memory exhausted.\n");
            exit (EXIT_FAILURE);
        }
        strncpy (arr[i]->name, buf, len + 1); /* copy buf to name */

        /* read/validate address from stdin */
        if (!fgets (buf, MAXC, fp)) {
            fprintf (stderr, "error: read failure, arr[%d]->address.\n", i);
            exit (EXIT_FAILURE);
        }
        len = (size_t)rmcrlf (buf);
        strncpy (arr[i]->address, buf, len + 1);

        if (!fgets (buf, MAXC, fp)) { /* citystate */
            fprintf (stderr, "error: read failure, arr[%d]->citystate.\n", i);
            exit (EXIT_FAILURE);
        }
        len = (size_t)rmcrlf (buf);
        strncpy (arr[i]->citystate, buf, len + 1);

        if (!fgets (buf, MAXC, fp)) { /* zip */
            fprintf (stderr, "error: read failure, arr[%d]->zip.\n", i);
            exit (EXIT_FAILURE);
        }
        len = (size_t)rmcrlf (buf);
        char *p = buf;
        for (; *p; p++)
            if (*p < '0' || '9' < *p) { /* validate zip all numeric */
                fprintf (stderr, "error: invalid zip, arr[%d]->zip '%s'.\n",
                        i, buf);
                exit (EXIT_FAILURE);            
            }
        strncpy (arr[i]->zip, buf, len + 1);

        i++;
    }

    return (i);
}

/** remove newline or carriage-return from 's'.
 *  returns new length, on success, -1 if 's' is NULL.
 */
int rmcrlf (char *s)
{
    if (!s) return -1;
    if (!*s) return 0;
    char *p = s;
    for (; *p && *p != '\n' && *p != '\r'; p++) {}
    *p = 0;

    return (int)(p - s);
}

/** comparison function for qsort of pointers on zip */
int cmpzip (const void *a, const void *b)
{
    person *ap = *(person * const *)a;
    person *bp = *(person * const *)b;

    return strcmp (ap->zip, bp->zip);
}

输入文件

您输入的第一行是固定的,例如

$ cat dat/addr.txt
A1, A2
20294 Lorenzana Dr
Woodland Hills, CA
91364
B1, B2
19831 Henshaw St
Culver City, CA
94023
C1, C2
...

使用/输出示例

这里是排序后的指针数组的快速打印结果,按排序顺序显示namezip

$ ./bin/addrstruct <dat/addr.txt
Processed 18 sets of data.

 name : K1, K2  (89030)
 name : O1, O2  (90001)
 name : G1, G2  (90005)
 name : N1, N2  (90210)
 name : H1, H2  (91110)
 name : F1, F2  (91111)
 name : C1, C2  (91112)
 name : J1, J2  (91362)
 name : M1, M2  (91362)
 name : A1, A2  (91364)
 name : D1, D2  (91364)
 name : A1, A2  (91364)
 name : E1, E2  (91364)
 name : L1, L2  (91364)
 name : B1, B2  (94023)
 name : I1, I2  (94135)
 name : I1, I2  (94135)
 name : I1, I2  (94135)

内存使用/错误检查

在您编写的动态分配内存的任何代码中,对于分配的任何内存块,您都有 2 个责任:(1) 始终保留指向起始地址的指针内存块,因此,(2) 当不再需要它时可以释放

您必须使用内存错误检查程序来确保您没有超出/超出分配的内存块,尝试读取或基于未初始化的值进行跳转,最后确认您已释放所有您分配的内存。

$ valgrind ./bin/addrstruct <dat/addr.txt
==28762== Memcheck, a memory error detector
==28762== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==28762== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==28762== Command: ./bin/addrstruct
==28762==
Processed 18 sets of data.

 name : K1, K2  (89030)
 name : O1, O2  (90001)
 ...
 name : I1, I2  (94135)
 name : I1, I2  (94135)
==28762==
==28762== HEAP SUMMARY:
==28762==     in use at exit: 0 bytes in 0 blocks
==28762==   total heap usage: 18 allocs, 18 frees, 2,520 bytes allocated
==28762==
==28762== All heap blocks were freed -- no leaks are possible
==28762==
==28762== For counts of detected and suppressed errors, rerun with: -v
==28762== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 1 from 1)

始终确认所有堆块都已释放——不可能有泄漏,同样重要的是错误摘要:0 个上下文中的 0 个错误

在看似简短的排序指针数组中总是包含许多语言的具体细节,这些指针数组指向结构,类型示例。这就是教授们喜欢它们的原因——也是为什么你应该确保你理解每一个谜题的原因。如果您有任何问题,请查看并告诉我。

完全格式化的输出

如果您想调整输出并添加结构的每个字段并整理格式,您可以执行类似于以下的操作。只需将上面代码中的输出替换为:

printf (" %-6s    %-22s    %-20s    %s\n", "Name",
        "Address", "City State", "Zip");
printf (" ------    ----------------------    "
        "--------------------    -----\n");
for (i = 0; i < nelem; i++) { /* print output free memory */
    printf (" %-6s    %-22s    %-20s    %s\n", arr[i]->name,
            arr[i]->address, arr[i]->citystate, arr[i]->zip);
    free (arr[i]);
}

对于格式化输出,例如:

$ ./bin/addrstruct <dat/addr.txt

Processed 18 sets of data from 'stdin'.

 Name      Address                   City State              Zip
 ------    ----------------------    --------------------    -----
 K1, K2    720 Eucalyptus Ave 105    Inglewood, CA           89030
 O1, O2    7659 Mckinley Ave         Los Angeles, CA         90001
 G1, G2    20253 Lorenzana Dr        Los Angeles, CA         90005
 N1, N2    20044 Wells Dr            Beverly Hills, CA       90210
 ...

【讨论】:

    【解决方案2】:

    函数:main()有一个变量noElements,初始化为0

    函数:takeData()为读取的项目数保留一个本地计数器,但该值永远不会返回到main()中的变量

    然后 main() 将该值 0 传递给 sortData() 作为要排序的元素数。当然,如果计数为 0,sortData() 函数什么也不做。

    为什么变量:noElements 将其地址传递给sortData()?函数:sortData() 永远不会更改该变量。所以代码应该简单地传递noElements 的内容,这就是函数:sortData() 所期望的,而不是地址。

    在发布的代码中没有显示struct Personarr[] 数组的内容。

    【讨论】:

      猜你喜欢
      • 2022-09-28
      • 1970-01-01
      • 2015-01-09
      • 2019-09-18
      • 2019-10-23
      • 2015-09-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多