这里发生了许多不正确的事情,但也涉及到一些微妙的问题,这些问题在尝试填充结构时不会很明显。
首先,您声明一个由struct details(其中5 个)组成的全局数组。不要那样做。虽然合法,但您只需在全局范围内声明 struct,然后在 main() 内声明每个实例,并将结构的副本或指向结构的指针作为参数传递给代码中需要的任何函数它。
其次,您将d 声明为read_person 的本地地址,然后在最后返回d 以分配回main()。这很好,......但是......明白为什么它很好。当您声明d 时,struct details 的所有成员都具有自动存储类型,并且每个成员的存储都在此时完全定义。无需在函数中的任何位置调用 malloc。当您在最后返回 d 时,struct assignment 允许函数返回结构,并在 main() 中分配并返回所有值。
最后在main(),你调用read_person();,但是没有分配返回值或者以任何方式使用d中存储的值。
不用创建一个全局结构数组,只需声明结构本身,例如:
#define MAXNM 32 /* if you need a constant, #define one (or more) */
/* (don't skimp on buffer size) */
struct details {
char forename[MAXNM];
char surname[MAXNM];
};
然后对于您的 read_person(void) 函数,消除对 malloc 的调用并简单地执行以下操作:
struct details read_person (void)
{
struct details d;
printf ("Enter your forename: ");
fgets (d.forename, MAXNM, stdin);
d.forename[strcspn(d.forename, "\n")] = 0; /* trim \n from end */
printf("Enter your surname : ");
fgets(d.surname, MAXNM, stdin);
d.surname[strcspn(d.surname, "\n")] = 0; /* trim \n from end */
return d;
}
(注意:您不希望在每个名称的末尾留下行尾'\n',因此您需要用 nul 覆盖尾随 '\n' -character '\0'(或等效的0)。虽然有多种方法可以做到这一点,但使用strcspn 可能是最可靠和最简单的方法之一。strcspn 返回排除集中未包含的字符串中的字符数。因此,只需让您的排除集包含行尾"\n",它就会返回其中的字符数直到 '\n' 的字符串,然后您只需将其设置为 0)
(另请注意:在struct details read_person (void) 中使用void 来指定read_person 不带任何参数。在C 中,如果您只是将() 留空,则该函数需要一个不确定参数个数)
然后在main() 中分配返回值并以某种方式使用它,例如
int main (void) {
struct details person = read_person();
printf ("\nname: %s, %s\n", person.forename, person.surname);
return 0;
}
您的另一种选择是在main() 中声明您的结构并将指针传递给您的read_person 函数以进行填充。只需在main() 中声明一个结构,然后将结构的地址传递给read_person,但请注意,使用指向结构的指针,您使用-> 运算符而不是@987654361 来访问成员@。例如你可以这样做:
#include <stdio.h>
#include <string.h>
#define MAXNM 32 /* if you need a constant, #define one (or more) */
/* (don't skimp on buffer size) */
struct details {
char forename[MAXNM];
char surname[MAXNM];
};
void read_person (struct details *d)
{
printf ("Enter your forename: ");
fgets (d->forename, MAXNM, stdin);
d->forename[strcspn(d->forename, "\n")] = 0; /* trim \n from end */
printf("Enter your surname : ");
fgets(d->surname, MAXNM, stdin);
d->surname[strcspn(d->surname, "\n")] = 0; /* trim \n from end */
}
int main (void) {
struct details person;
read_person (&person);
printf ("\nname: %s, %s\n", person.forename, person.surname);
return 0;
}
最后,既然你确实包含了mailloc,那么你不妨学习一下如何使用它来为结构分配存储空间,以及每个forename 和surname,以便两者都使用正确的保存输入名称的字节数,仅此而已。在为任何内容分配存储时,您对分配的任何内存块有 3 个责任:(1) 在使用内存块之前始终验证分配是否成功,(2) 总是为内存块保留一个指向起始地址的指针,因此,(3) 当不再需要它时可以释放。
这将在您动态分配存储的任何位置添加一些重复但重要的代码行。例如,在这种情况下,您为结构和结构内的forename 和surname 动态分配,您的结构声明和函数可能是:
struct details {
char *forename;
char *surname;
};
struct details *read_person (void)
{
char buf[MAXNM];
size_t len;
struct details *d = malloc (sizeof *d); /* allocate storage */
if (d == NULL) { /* validate allocation succeeds */
perror ("malloc-d");
return NULL;
}
printf ("Enter your forename: ");
fgets (buf, MAXNM, stdin);
len = strcspn(buf, "\n");
buf[len] = 0;
d->forename = malloc (len + 1); /* allocate */
if (d->forename == NULL) { /* validate */
perror ("malloc-d->forename");
free (d);
return NULL;
}
memcpy (d->forename, buf, len + 1);
printf ("Enter your surname : ");
fgets (buf, MAXNM, stdin);
len = strcspn(buf, "\n");
buf[len] = 0;
d->surname = malloc (len + 1); /* allocate */
if (d->surname == NULL) { /* validate */
perror ("malloc-d->surname");
free (d->forename);
free (d);
return NULL;
}
memcpy (d->surname, buf, len + 1);
return d;
}
(注意:使用memcpy而不是strcpy。您已经用strcspn扫描了字符串的结尾以获取字符串中的字符数和然后在该点对字符串进行 nul 终止。无需再次使用 strcpy 扫描字符串结尾,只需复制字符数(+1 以同时复制 nul-terminating 字符)与memcpy。
试试看你能不能理解为什么free()函数会包含在上面的函数中,它的作用是什么。
将一个动态分配的完整示例放在一起,您可以执行类似于以下的操作:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define MAXNM 1024
struct details {
char *forename;
char *surname;
};
struct details *read_person (void)
{
char buf[MAXNM];
size_t len;
struct details *d = malloc (sizeof *d);
if (d == NULL) {
perror ("malloc-d");
return NULL;
}
printf ("Enter your forename: ");
fgets (buf, MAXNM, stdin);
len = strcspn(buf, "\n");
buf[len] = 0;
d->forename = malloc (len + 1);
if (d->forename == NULL) {
perror ("malloc-d->forename");
free (d);
return NULL;
}
memcpy (d->forename, buf, len + 1);
printf ("Enter your surname : ");
fgets (buf, MAXNM, stdin);
len = strcspn(buf, "\n");
buf[len] = 0;
d->surname = malloc (len + 1);
if (d->surname == NULL) {
perror ("malloc-d->surname");
free (d->forename);
free (d);
return NULL;
}
memcpy (d->surname, buf, len + 1);
return d;
}
int main (void) {
struct details *person = read_person();
if (person != NULL) { /* validate the function succeeded */
printf ("\nname: %s, %s\n", person->forename, person->surname);
free (person->forename);
free (person->surname);
free (person);
}
return 0;
}
(注意:所有内存都在程序退出之前被释放。了解内存会在退出时自动释放,但如果你养成了始终关注动态相关的 3 个责任的习惯分配的内存,以后随着代码大小的增长,您将永远不会遇到内存泄漏问题。)
使用/输出示例
所有示例都产生相同的输出。一个例子是:
$ ./bin/struct_name3
Enter your forename: Samuel
Enter your surname : Clemens
name: Samuel, Clemens
内存使用/错误检查
您必须使用内存错误检查程序来确保您不会尝试访问内存或写入超出/超出分配块的边界,尝试读取或基于未初始化的值进行条件跳转,最后,以确认您释放了已分配的所有内存。
对于 Linux,valgrind 是正常的选择。每个平台都有类似的内存检查器。它们都易于使用,只需通过它运行您的程序即可。
$ valgrind ./bin/struct_name3
==14430== Memcheck, a memory error detector
==14430== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==14430== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==14430== Command: ./bin/struct_name3
==14430==
Enter your forename: Samuel
Enter your surname : Clemens
name: Samuel, Clemens
==14430==
==14430== HEAP SUMMARY:
==14430== in use at exit: 0 bytes in 0 blocks
==14430== total heap usage: 3 allocs, 3 frees, 31 bytes allocated
==14430==
==14430== All heap blocks were freed -- no leaks are possible
==14430==
==14430== For counts of detected and suppressed errors, rerun with: -v
==14430== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
始终确认您已释放已分配的所有内存并且没有内存错误。
处理结构细节数组的 qsort
在处理完您使用struct details 的所有初始问题后,我差点忘记您最初的问题与qsort 有关。 qsort 使用简单,您只需传递数组、要排序的成员数、每个成员的大小和一个比较函数,该函数根据第一个元素是否排序在前、是否等于或返回 -1, 0, 1在传递给函数的第二个元素之后排序。
您使用qsort 的唯一职责是编写compare 函数。虽然qsort 的新用户在看到函数声明时通常会翻白眼:
int compare (const void *a, const void *b) { ... }
其实很简单。 a 和 b 只是指向要比较的数组元素的指针。因此,如果您有一个 数组 struct details。 a 和 b 只是 指向 struct details 的指针。您只需编写 compare 函数即可将 a 和 b 转换为适当的类型。
要对forename 进行排序,您的比较函数可以是:
int compare_fore (const void *a, const void *b)
{
const struct details *name1 = a,
*name2 = b;
int rtn = strcmp (name1->forename, name2->forename); /* compare forename */
if (rtn != 0) /* if forenames are different */
return rtn; /* return result of strcmp */
/* otherwise return result of strcmp of surname */
return strcmp (name1->surname, name2->surname); /* compare surname */
}
要对surname 进行排序,您需要:
int compare_sur (const void *a, const void *b)
{
const struct details *name1 = a,
*name2 = b;
int rtn = strcmp (name1->surname, name2->surname);
if (rtn != 0)
return rtn;
return strcmp (name1->forename, name2->forename);
}
然后在main() 中,您只需声明一个struct details 数组并调用qsort,例如
int main (void) {
struct details person[MAXS];
for (int i = 0; i < MAXS; i++)
person[i] = read_person();
qsort (person, MAXS, sizeof *person, compare_fore);
puts ("\nSorted by forename:\n");
for (int i = 0; i < MAXS; i++)
printf (" %s, %s\n", person[i].forename, person[i].surname);
qsort (person, MAXS, sizeof *person, compare_sur);
puts ("\nSorted by surname:\n");
for (int i = 0; i < MAXS; i++)
printf (" %s, %s\n", person[i].forename, person[i].surname);
return 0;
}
或者,把它放在一个完整的例子中:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXS 5 /* if you need a constant, #define one (or more) */
#define MAXNM 32 /* (don't skimp on buffer size) */
struct details {
char forename[MAXNM];
char surname[MAXNM];
};
int compare_fore (const void *a, const void *b)
{
const struct details *name1 = a,
*name2 = b;
int rtn = strcmp (name1->forename, name2->forename); /* compare forename */
if (rtn != 0) /* if forenames are different */
return rtn; /* return result of strcmp */
/* otherwise return result of strcmp of surname */
return strcmp (name1->surname, name2->surname); /* compare surname */
}
int compare_sur (const void *a, const void *b)
{
const struct details *name1 = a,
*name2 = b;
int rtn = strcmp (name1->surname, name2->surname);
if (rtn != 0)
return rtn;
return strcmp (name1->forename, name2->forename);
}
struct details read_person (void)
{
struct details d;
printf ("\nEnter your forename: ");
fgets (d.forename, MAXNM, stdin);
d.forename[strcspn(d.forename, "\n")] = 0; /* trim \n from end */
printf("Enter your surname : ");
fgets(d.surname, MAXNM, stdin);
d.surname[strcspn(d.surname, "\n")] = 0; /* trim \n from end */
return d;
}
int main (void) {
struct details person[MAXS];
for (int i = 0; i < MAXS; i++)
person[i] = read_person();
qsort (person, MAXS, sizeof *person, compare_fore);
puts ("\nSorted by forename:\n");
for (int i = 0; i < MAXS; i++)
printf (" %s, %s\n", person[i].forename, person[i].surname);
qsort (person, MAXS, sizeof *person, compare_sur);
puts ("\nSorted by surname:\n");
for (int i = 0; i < MAXS; i++)
printf (" %s, %s\n", person[i].forename, person[i].surname);
return 0;
}
使用/输出示例
$ ./bin/struct_name4
Enter your forename: Mickey
Enter your surname : Mouse
Enter your forename: Minnie
Enter your surname : Mouse
Enter your forename: Samuel
Enter your surname : Clemens
Enter your forename: Mark
Enter your surname : Twain
Enter your forename: Walt
Enter your surname : Disney
Sorted by forename:
Mark, Twain
Mickey, Mouse
Minnie, Mouse
Samuel, Clemens
Walt, Disney
Sorted by surname:
Samuel, Clemens
Walt, Disney
Mickey, Mouse
Minnie, Mouse
Mark, Twain
(注意: 因为Mickey 和Minnie 都有姓氏Mouse,对于按surname 排序,然后它们进一步按forname 排序,所以有一个正确的上面列表中的规范排序)
现在,希望我们已经解决了您问题的所有方面。如果您还有其他问题,请仔细查看并告诉我。