首先,别担心 - 开始 C 的挫折是正常的 :)
既然您说您是初学者,我已经写了一个很长的答案,解释了您可能想要进行的其他一些改进。对不起,如果我涵盖了一些你已经知道的事情。这是一个摘要:
- 您需要为
char*
s 分配一些空间来指向(这是导致崩溃的原因)
- 确保检查 malloc 的返回值
- 确保要求
scanf()
只读取字符串中可以容纳的尽可能多的字符。
- 无需从 malloc 转换返回值。
- 记住
free()
你已经 malloc-ed 的任何东西。
您需要为char*
s 分配一些空间来指向
在 C 中,achar*
表示“指向 char 的指针”。char*
通常用于字符串,因为您可以像数组一样索引指针 - 例如,假设:
char *a = "Hello";
然后, a[1]
表示“在这种情况下,char
指向 char 之后的第一个;a
'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()
读取字符串中可以容纳的尽可能多的字符。
请注意,一旦您在 中分配了 10 个字符fName
,scanf()
可能会读取太多字符。"%Ns"
您可以通过写入其中 N 是字符串中的最大字符数(对于末尾的空终止符减去 1)来明确告诉 scanf 限制。所以,如果你分配了 10 个字符,那么你应该写:
scanf("%9s", contact->fName);
无需从 malloc 转换返回值。
最后一点 -你不需要在 C 中转换 malloc 的返回值,所以我可能会写:
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()
可能是一种痛苦。
所以,除了上面的总结:
- 对字符串的长度使用#define 可以避免问题,并使以后更容易更改代码。
fgets()
比 更容易使用,并且与使用 a作为字符串长度scanf()
更兼容。#define