我想让用户输入用空格分隔的数字,然后将每个值存储为数组的元素。目前我有:
while ((c = getchar()) != '\n')
{
if (c != ' ')
arr[i++] = c - '0';
}
但是,当然,每个元素存储一个数字。
如果用户要键入:
10 567 92 3
我希望将值10存储在arr[0]
中,然后将{56}存储在arr[1]
等中。
我应该以某种方式使用scanf
吗?
答案 0 :(得分:32)
有几种方法,具体取决于您希望代码的强大程度。
最直接的方法是将scanf
与%d
转换说明符一起使用:
while (scanf("%d", &a[i++]) == 1)
/* empty loop */ ;
%d
转换说明符告诉scanf
跳过任何前导空格并读取下一个非数字字符。返回值是成功转换和分配的数量。由于我们正在读取单个整数值,因此成功时返回值应为1。
如上所述,这有很多陷阱。首先,假设您的用户输入的数字多于阵列的大小以容纳;如果你很幸运,你会立即获得访问冲突。如果你不是,那么你最终会破坏一些会导致问题的重要事项(缓冲区溢出是一种常见的恶意软件攻击)。
因此,您至少要添加代码以确保不会超出数组的末尾:
while (i < ARRAY_SIZE && scanf("%d", &a[i++]) == 1)
/* empty loop */;
到目前为止很好。但现在假设您的用户在输入中使用非数字字符,例如12 3r5 67
。如上所述,循环将12
分配给a[0]
,3
分配给a[1]
,然后它会在输入流中看到r
,返回0并退出没有保存任何内容a[2]
。这里有一个微妙的bug进入 - 即使没有任何内容被分配给a[2]
,表达式i++
仍然会被评估,所以你想你已经为{{{ 1}}即使它包含垃圾值。因此,您可能希望暂时增加a[2]
,直到您知道自己已成功阅读:
i
理想情况下,您想完全拒绝while (i < ARRAY_SIZE && scanf("%d", &a[i]) == 1)
i++;
。我们可以在数字后面立即读取字符并确保它是空格;如果不是,我们拒绝输入:
3r5
如果我们获得两次成功的转换,我们确保#include <ctype.h>
...
int tmp;
char follow;
int count;
...
while (i < ARRAY_SIZE && (count = scanf("%d%c", &tmp, &follow)) > 0)
{
if (count == 2 && isspace(follow) || count == 1)
{
a[i++] = tmp;
}
else
{
printf ("Bad character detected: %c\n", follow);
break;
}
}
是一个空白字符 - 如果不是,我们会打印错误并退出循环。如果我们获得1次成功转换,则表示输入数字后面没有字符(意味着我们在数字输入后点击了EOF)。
或者,我们可以将每个输入值作为文本读取并使用follow
进行转换,这也可以让您捕获相同类型的问题(我的首选方法):
strtol
但等等还有更多!
假设您的用户是那些喜欢在您的代码中输入令人讨厌的输入的扭曲QA类型之一“只是为了看看会发生什么”并输入#include <ctype.h>
#include <stdlib.h>
...
char buf[INT_DIGITS + 3]; // account for sign character, newline, and 0 terminator
...
while(i < ARRAY_SIZE && fgets(buf, sizeof buf, stdin) != NULL)
{
char *follow; // note that follow is a pointer to char in this case
int val = (int) strtol(buf, &follow, 10);
if (isspace(*follow) || *follow == 0)
{
a[i++] = val;
}
else
{
printf("%s is not a valid integer string; exiting...\n", buf);
break;
}
}
这样的数字,显然 大到适合任何标准整数类型。信不信由你,123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
不会对此嗤之以鼻,最终会将某些存储到scanf("%d", &val)
,但这又是一个你可能想要完全拒绝的输入。
如果每行只允许一个值,这就相对容易防范;如果有空间,val
将在目标缓冲区中存储换行符,因此如果我们在输入缓冲区中没有看到换行符,那么用户输入的内容比我们准备处理的时间长:
fgets
如果你想允许每行多个值,例如'10 20 30',那么这会变得有点困难。我们可以回去从输入中读取单个字符,并对每个字符进行完整性检查(警告,未经测试):
#include <string.h>
...
while (i < ARRAY_SIZE && fgets(buf, sizeof buf, stdin) != NULL)
{
char *newline = strchr(buf, '\n');
if (!newline)
{
printf("Input value too long\n");
/**
* Read until we see a newline or EOF to clear out the input stream
*/
while (!newline && fgets(buf, sizeof buf, stdin) != NULL)
newline = strchr(buf, '\n');
break;
}
...
}
欢迎来到C中令人惊叹的交互式输入世界。
答案 1 :(得分:0)
对代码进行细微更改:只有在阅读空格时才会增加i
:
while ((c = getchar()) != '\n')
{
if (c != ' ')
arr[i] = arr[i] * 10 + c - '0';
else
i++;
}
当然,最好使用scanf
:
while (scanf("%d", &a[i++]) == 1);
假设您在阵列中有足够的空间。另外,请注意上面的while
以;
结尾,一切都在循环条件内完成。
事实上,应该检查每个返回值。
答案 2 :(得分:0)
scanf
返回成功扫描的项目数。
试试下面的代码:
#include <stdio.h>
int main()
{
int arr[500];
int i = 0;
int sc = 0; //scanned items
int n = 3; // no of integers to be scanned from the single line in stdin
while( sc<n )
{
sc += scanf("%d",&arr[i++]);
}
}