如 cmets 中所述,您的代码中存在大量错误。从安全的角度来看,永远不要使用gets。鉴于存在的安全风险,它已从 C11 中的 C 标准中删除,没有弃用。
虽然不是错误,但 C 的标准编码风格避免了 CamelCase 变量,而支持所有 小写。参见例如NASA - C Style Guide, 1994
在 C 中,qsort 是库提供的事实上的排序例程。除非您只是想折磨自己,否则您应该使用qsort 对zip 上的指针数组进行排序,并且您的sortdata 例程可以完全删除。使用qsort 唯一需要编写的代码就是一个整数比较函数,这样qsort 就会知道如何对指针进行排序。由于您想要对指针列表进行排序,因此您需要认识到比较函数的每个输入都是一个指向 struct person 指针的指针,这意味着您将取消引用两次。 qsort 对 zip 进行排序的简短比较函数可能是:
/** 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
...
使用/输出示例
这里是排序后的指针数组的快速打印结果,按排序顺序显示name 和zip:
$ ./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
...