你有两个误解,你正在努力解决。首先scanf() 不以任何方式修改存储(为了讨论的目的省略了非标准的"%a",后来重命名为"%m" 说明符)。其次,您忘记提供 length + 1 存储字符以确保 null-terminating 字符的空间。
在您的声明中“例如,如果他们写“詹姆斯”,我需要malloc((sizeof(char)*5)” - 不,不,您需要malloc (6) 为James\0 提供空间。另请注意,sizeof (char) 定义为 1,应省略。
关于如何读取字符串,您通常希望避免使用scanf(),即使使用scanf(),除非您正在读取空格分隔的单词,否则您不想使用@ 987654332@ 转换说明符,一旦遇到空格就停止读取,从而无法读取"James Bond"。此外,在您致电scanf() 后,您还会遇到stdin 中未读内容的问题。
使用"%s" 读取时,'\n' 字符留在stdin 中未读。如果使用不忽略前导空格的输入函数(即任何面向字符或面向行的输入函数),这是一个陷阱,它将在您下次尝试阅读时咬住您。这些陷阱以及与scanf() 使用相关的许多其他缺陷是鼓励新C 程序员使用fgets() 来读取用户输入的原因。
使用足够大的缓冲区(如果没有,则使用简单的循环)fgets() 将在每次调用时消耗一整行输入,确保该行中没有任何未读的内容。唯一需要注意的是fgets() 读取并在其填充的缓冲区中包含尾随'\n'。您只需通过调用strcspn() 来修剪尾随换行符(它还可以同时为您提供字符串的长度)
如上所述,解决“我不知道我有多少个字符?”的一种方法问题是使用固定大小的缓冲区(字符数组),然后重复调用fgets(),直到在数组中找到'\n'。这样,您可以通过确定读入固定大小缓冲区的字符数来为该行分配最终存储空间。如果您的固定大小缓冲区是 10 并且您有要读取的 100 字符并不重要,您只需在循环中调用 fgets() 直到您读取的字符数小于完整的固定大小缓冲区值得。
现在理想情况下,您将调整临时固定大小缓冲区的大小,以便您的输入适合第一次,从而消除循环和重新分配的需要,但如果猫踩到键盘 - 您就被覆盖了。
我们来看一个例子,功能类似于 CS50 的get_string() 函数。它允许用户为用户提供提示,并为结果读取和分配存储空间,返回一个指向已分配块的指针,其中包含用户负责在完成后调用 free() 的字符串。
#define MAXC 1024 /* if you need a constant, #define one (or more) */
char *getstr (const char *prompt)
{
char tmp[MAXC], *s = NULL; /* fixed size buf, ptr to allocate */
size_t n = 0, used = 0; /* length and total length */
if (prompt) /* prompt if not NULL */
fputs (prompt, stdout);
while (1) { /* loop continually */
if (!fgets (tmp, sizeof tmp, stdin)) /* read into tmp */
return s;
tmp[(n = strcspn (tmp, "\n"))] = 0; /* trim \n, save length */
if (!n) /* if empty-str, break */
break;
void *tmpptr = realloc (s, used + n + 1); /* always realloc to temp pointer */
if (!tmpptr) { /* validate every allocation */
perror ("realloc-getstr()");
return s;
}
s = tmpptr; /* assign new block to s */
memcpy (s + used, tmp, n + 1); /* copy tmp to s with \0 */
used += n; /* update total length */
if (n + 1 < sizeof tmp) /* if tmp not full, break */
break;
}
return s; /* return allocated string, caller responsible for calling free */
}
上面,MAXC 个字符的固定大小缓冲区用于读取用户的输入。一个连续循环调用fgets() 将输入读入缓冲区tmp。 strcspn() 被称为tmp 的索引以查找不包括'\n' 字符(不包括'\n' 的输入的长度)和nul-terminates 的字符数该长度的字符串用 nul-terminating 字符 '\0' 覆盖 '\n' 字符(这只是普通的旧 ASCII 0)。长度保存在n 中。如果删除 '\n' 后该行为空,则无需执行任何操作,该函数将返回当时 s 中的任何内容。
如果存在字符,则 临时 指针用于 realloc() 存储新字符 (+1)。验证realloc() 成功后,将新字符复制到存储的末尾,并将缓冲区中字符的总长度保存在used 中,用作字符串开头的偏移量。重复此过程,直到您用完要读取的字符并返回包含该字符串的分配块(如果未输入任何字符,则返回 NULL)
(注意:您可能还希望将指向size_t 的指针作为参数传递,该参数可以在返回之前更新为最终长度,以避免再次计算返回字符串的长度——留给你)
在查看示例之前,让我们将调试输出添加到函数中,以便告诉我们总共分配了多少字符。只需在返回前在下方添加printf(),例如
}
printf (" allocated: %zu\n", used?used+1:used); /* (debug output of alloc size) */
return s; /* return allocated string, caller responsible for calling free */
}
一个简短的例子,循环读取输入,直到在一个空行上按下 Enter 导致程序在释放所有内存后退出:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* insert getstr() function HERE */
int main (void) {
for (;;) {
char *s = getstr ("enter str: ");
if (!s)
break;
puts (s);
putchar ('\n');
free (s);
}
}
使用/输出示例
MAXC1024 没有机会循环,除非猫踩在键盘上,所以所有输入都被读入 tmp,然后分配存储空间以准确保存每个输入:
$ ./bin/fgetsstr
enter str: a
allocated: 2
a
enter str: ab
allocated: 3
ab
enter str: abc
allocated: 4
abc
enter str: 123456789
allocated: 10
123456789
enter str:
allocated: 0
将MAXC 设置为2 或10 也可以。唯一改变的是循环重新分配存储并将临时缓冲区的内容复制到最终存储的次数。例如。使用MAXC 10,用户不会知道以下区别:
$ ./bin/fgetsstr
enter str: 12345678
allocated: 9
12345678
enter str: 123456789
allocated: 10
123456789
enter str: 1234567890
allocated: 11
1234567890
enter str: 12345678901234567890
allocated: 21
12345678901234567890
enter str:
allocated: 0
在上面你已经强制while (1) 循环对每个10 字符或更多字符的字符串执行两次。因此,当您想将MAXC 设置为一些合理的大小以避免循环时,考虑到在大多数 x86 或 x86_64 计算机上至少有 1M 的函数堆栈,1K 缓冲区就可以了。如果您正在为存储空间有限的微控制器编程,您可能希望减小尺寸。
虽然您也可以为tmp 分配,但实际上没有必要,而且使用固定大小的缓冲区对于坚持标准-C 来说非常简单。如果您有可用的 POSIX,那么getline() 已经为您拥有的任何大小输入提供了自动分配功能。这是fgets() 的另一个很好的替代方案——但 POSIX 不是标准 C(尽管它被广泛使用)
另一个不错的选择是简单地循环使用getchar() 一次读取一个字符,直到达到'\n' 或EOF。在这里,您只需为s 分配一些初始大小,比如2 或8,并跟踪used 的字符数,然后在used == allocated 时将分配的大小加倍并继续。您可能希望分配存储块,因为您不想为添加的每个字符分配realloc()(我们将省略讨论为什么今天使用mmaped malloc() 比过去不那么正确)
查看一下,如果您还有其他问题,请告诉我。