c - C程序使用结构重新排列列表

标签 c function pointers data-structures structure

第一节C编程课,开始学习编程。我目前正在学习如何在C语言中使用结构,这是一项帮助理解这一过程的学习任务。
程序应该接受地址的输入,并按zipcode从最低zipcode到最高zipcode进行组织。
下面的程序和输入/输出是我用来学习本课程的程序之一。我想用这个程序使用文件重定向将数据转储到一个文件中。例如:

ConsoleApplication1.exe < input.txt > testout.txt

现在转储到测试文件中的所有内容是:
Processed 18 sets of data.

显然是因为printf语句,我在‘takedate’函数中测试了这个过程。
我的问题是,什么是获取输入、处理输入、然后以zipcode顺序将输出转储到文件中的最佳方法?有谁能推荐一个可以添加的函数/语句来实现这个目的吗?
非常感谢您的帮助,时间和指导,使这个计划的工作!
//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

最佳答案

如注释中所述,代码中有大量错误。从安全的角度来看,永远不要使用gets。考虑到所带来的安全风险,它在C11中从C标准中删除,没有受到抨击。
虽然不是错误,但C的标准编码风格避免了CamelCase变量,而采用了所有小写形式。参见例如NASA - C Style Guide, 1994
在C中,qsort是库提供的实际排序例程。除非您只是想折磨自己,否则应该使用qsortzip上的指针数组进行排序,并且可以完全删除您的sortdata例程。使用qsort只需要编写一个整数比较函数,以便qsort知道如何对指针排序。由于要对指针列表进行排序,需要认识到compare函数的每个输入都是指向struct person的指针,这意味着您将取消两次引用。qsort排序的简短比较函数可以是:

/** 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);
}

(注意:虽然zip强制转换看起来不稳定,但它只是一个反射,您正在处理其值不会更改的常量指针。您可以忽略(person * const *)修饰符,并将cast写为const,这在视觉上更有意义。)
您基于(person **)的指针数组的整个类型被缩减为:
    qsort (arr, nelem, sizeof *arr, cmpzip);

(使用C-library提供的排序例程比自己滚动更不容易出错…)
接下来,你的代码可能会在50个不同的地方中的一个失败,你不会有任何线索。你会一直盲目地读写,直到出现SEGFAULT(或其他错误)。如果您对zip的呼叫失败了怎么办?学习验证后续步骤所依赖的每个步骤,并验证所有用户输入。(我将在这里插入关于不加大小写的注释——这完全没有必要)验证示例:
在您呼叫malloc时:
    if (!(nelem = takedata (arr, fp))) { /* read addresses */
        fprintf (stderr, "error: no elements read.\n");
        return 1;
    }

malloc验证分配中:
        /* 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);
        }

在可能的情况下,添加额外的验证,比如检查以确保main是所有数字,例如。
        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);            
            }

我还想指出一些其他的问题,但老实说,我迷失了方向。纵观所有的评论,他们涵盖了大量的问题。
最后,把所有的片段放在一起,从一个文件中读取数据(在修复第一行之后),你可以做类似于下面的事情。确保您理解代码的每一行、每一个字符在做什么,如果没有,请询问。代码将从命令行中指定为第一个参数的文件中读取数据(如果未给定参数,则默认从takedata中读取)。
#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
...

示例使用/输出
下面是按排序顺序显示takedatazip的指针排序数组的快速打印:
$ ./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)

内存使用/错误检查
在动态分配内存的任何代码中,对于任何分配的内存块,您都有两个职责:(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
 ...

关于c - C程序使用结构重新排列列表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37097871/

相关文章:

c++ - 使用valgrind获取clock_gettime的汇编代码?

c - 试图理解 : invalid operands to to binary expression, C

javascript - 工作表自定义函数 - 数组?或范围?有什么不同?

r - 在 R 中返回多个修改参数的最佳方法?

c++ - VS 2017 for C语言报错

argc 在 POSIX 系统上可以为零吗?

jquery - 动态点击功能,自动生成ID

c - 不从 C 中的结构打印字符串

c - c中的字符串指针

c - 这是 malloc 和 free 的可接受使用吗? (C)