首先,别担心 - 以 C 开头的挫折是正常的 :)
既然你说你是初学者,我已经写了一个很长的答案,解释了你可能想要做的一些其他改进。对不起,如果我涵盖了一些你已经知道的事情。总结如下:
- 您需要为
char*s 分配一些空间以指向(这是导致崩溃的原因)
- 确保检查 malloc 的返回值
- 确保要求
scanf() 只读取字符串中可以容纳的字符数。
- 无需从 malloc 转换返回值。
- 记得
free() 任何你已经 malloc 编辑过的东西。
您需要为char*s 分配一些空间以指向
在 C 中,char* 表示“指向字符的指针”。 char* 通常用于字符串,因为您可以像索引数组一样索引指针 - 例如,假设:
char *a = "Hello";
那么,a[1] 表示“a 指向的字符之后的第一个char,在本例中为'e';
你有这个代码:
contactInfo *contact;
contact = (contactInfo *) malloc (sizeof(contactInfo));
此时,您已经声明了一个指向contactInfo 结构的指针,并为其分配了正确大小的内存。但是,结构内的指针目前不指向任何东西——所以当你的程序调用scanf() 时会崩溃。您还需要为即将阅读的字符分配空间,例如:
contact->fName = malloc(sizeof(char) * 10);
将为 10 个字符分配空间。您需要为结构中的每个 char* 执行此操作。
我不想让你太担心的几个旁白:
确保检查 malloc 的返回值
现在回到正轨 - 您还应该检查来自 malloc() 的返回值:
contact->fName = malloc(sizeof(char) * 10);
if(contact->fName == NULL) {
// Allocation failed
}
在某些情况下,您可能能够从失败的分配中恢复(例如,尝试
再次分配,但要求更少的空间),但开始:
contact->fName = malloc(sizeof(char) * 10);
if(contact->fName == NULL) {
printf(stderr,"Allocation of contact->fName failed");
exit(EXIT_FAILURE);
}
应该没问题。许多程序员会为malloc() 编写一个包装器,为他们进行错误检查,这样他们就不必再担心了。
确保您只要求scanf() 读取字符串中可以容纳的尽可能多的字符。
请注意,一旦您在 fName 中分配了 10 个字符,scanf() 可能会读取太多字符。您可以通过写入"%Ns" 来明确告诉 scanf 限制,其中 N 是字符串中的最大字符数(最后的空终止符减去 1)。所以,如果你分配了 10 个字符,那么你应该写:
scanf("%9s", contact->fName);
无需从 malloc 转换返回值。
最后一点 - you don't need to cast the return value of malloc in C,所以我可能会写:
contact = malloc (sizeof(contactInfo));
记住free() 你分配的任何东西
您可能已经这样做了,但是每次您malloc() 任何事情时,请确保在完成后您的代码中有相应的free()。这告诉操作系统它可以取回内存。所以,如果你有什么地方
contact = malloc (sizeof(contactInfo));
稍后,当您处理完该联系人后,您将需要以下内容:
free(contact);
避免内存泄漏。
一旦你释放了一些东西,你就不能再访问它了。因此,如果您在联系人中分配了字符串,则必须首先释放它们:
free(contact->fName); // doing this in the other order might crash
free(contact);
关于免费的一些事情要记住:
-
你不能两次释放任何东西。为避免这种情况,一个好的做法是编写:
if(contact != NULL) free(contact);
contact = NULL;
如果你这样写,那么你还需要在创建它们时将所有指针初始化为 NULL。当您创建其中包含指针的结构时,一种简单的方法是使用calloc() 而不是malloc() 创建结构,因为calloc() 返回的内存始终为零。
当您的程序退出时,所有内存都会释放回操作系统。这意味着您技术上不需要free() 在程序的整个生命周期内都存在的东西。但是,我建议养成释放所有分配的习惯,否则你会忘记重要的一天。
进一步改进
正如评论者在另一个答案中指出的那样,使用幻数(代码中硬编码的数字)通常是不好的做法。在我上面给你的例子中,我把“10”硬编码到程序中作为字符串的大小。但是,最好执行以下操作:
#define FNAME_MAX_LENGTH 10
然后再去:
malloc(sizeof(char) * FNAME_MAX_LENGTH);
这样做的好处是,如果你需要在任何地方改变字符串的大小,你可以只在一个地方改变它。它还可以防止您不小心在一个地方输入 100 或 1,从而导致潜在的严重且难以发现的错误。
当然,既然您已经有了一个#define 的长度,您需要更新我们指定长度的scanf() 调用。但是,由于 scanf() 需要长度 - 1,您将无法使用 #define 指定长度(至少,不是以任何可读的方式)。
因此,您可能对fgets() 感兴趣,它读取到指定长度 -1(或直到行尾 - 以先到者为准)。然后你可以这样做:
fgets(contact->fName,FNAME_MAX_LENGTH,stdin);
而不是scanf() 调用。进行此更改的另一个充分理由是scanf() 可以是kind of a pain。
所以,除了上面的总结:
- 对字符串的长度使用#define 可以避免出现问题,并使以后更改代码更容易。
-
fgets() 比 scanf() 更易于使用,并且更适合使用 #define 作为字符串长度。