我想让用户输入用空格分隔的数字,然后将每个值存储为数组的元素.目前我有:
while ((c = getchar()) != '\n')
{
if (c != ' ')
arr[i++] = c - '0';
}
Run Code Online (Sandbox Code Playgroud)
但是,当然,每个元素存储一个数字.
如果用户要输入:
10 567 92 3
Run Code Online (Sandbox Code Playgroud)
我想要存储值10 arr[0],然后567 arr[1]等.
我应该以scanf某种方式使用吗?
Joh*_*ode 32
有几种方法,具体取决于您希望代码的强大程度.
最直接的方法是使用scanf与%d转换符:
while (scanf("%d", &a[i++]) == 1)
/* empty loop */ ;
Run Code Online (Sandbox Code Playgroud)
该%d转换符告诉scanf跳过任何前导空格,并宣读了下一非数字字符.返回值是成功转换和分配的数量.由于我们正在读取单个整数值,因此成功时返回值应为1.
如上所述,这有许多陷阱.首先,假设您的用户输入的数字多于阵列的大小以容纳; 如果你很幸运,你会立即获得访问冲突.如果你不是,你最终会破坏一些重要的东西,以后会引起问题(缓冲区溢出是一种常见的恶意软件攻击).
因此,您至少要添加代码以确保不会超过数组的末尾:
while (i < ARRAY_SIZE && scanf("%d", &a[i++]) == 1)
/* empty loop */;
Run Code Online (Sandbox Code Playgroud)
目前很好.但是现在假设你的用户在输入中使用非数字字符,比如12 3r5 67.按照规定,环将分配12给a[0],3给a[1],那么它会看到r在输入流中,但不保存任何东西,返回0并退出a[2].这是一个微妙的bug进入的地方 - 即使没有任何内容被分配a[2],表达式i++仍然会被评估,所以你会认为你已经分配了一些内容,a[2]即使它包含一个垃圾值.所以你可能想要继续增加,i直到你知道你有一个成功的阅读:
while (i < ARRAY_SIZE && scanf("%d", &a[i]) == 1)
i++;
Run Code Online (Sandbox Code Playgroud)
理想情况下,你想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;
}
}
Run Code Online (Sandbox Code Playgroud)
如果我们得到两次成功的转换,我们确保follow是一个空格字符 - 如果不是,我们打印错误并退出循环.如果我们获得1次成功转换,则表示输入数字后面没有字符(意味着我们在数字输入后点击了EOF).
或者,我们可以将每个输入值作为文本读取并用于strtol进行转换,这也允许您捕获相同类型的问题(我的首选方法):
#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;
}
}
Run Code Online (Sandbox Code Playgroud)
但等等还有更多!
假设你的用户是谁喜欢在代码中引发厌恶那些输入扭曲QA类型之一"只是为了看看会发生什么",并进入了一些像123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890这显然过大,以适应任何标准的整数类型.信不信由你,scanf("%d", &val)不会牦牛这一点,并会倒闭存储的东西来val,但同样是你可能想直接拒绝输入.
如果你只允许每行一个值,这就相对容易防范; fgets如果有空间,将在目标缓冲区中存储换行符,因此如果我们在输入缓冲区中没有看到换行符,那么用户输入的内容比我们准备处理的时间长:
#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;
}
...
}
Run Code Online (Sandbox Code Playgroud)
如果你想允许每行多个值,例如'10 20 30',那么这会变得有点困难.我们可以回去从输入中读取单个字符,并对每个字符进行完整性检查(警告,未经测试):
...
while (i < ARRAY_SIZE)
{
size_t j = 0;
int c;
while (j < sizeof buf - 1 && (c = getchar()) != EOF) && isdigit(c))
buf[j++] = c;
buf[j] = 0;
if (isdigit(c))
{
printf("Input too long to handle\n");
while ((c = getchar()) != EOF && c != '\n') // clear out input stream
/* empty loop */ ;
break;
}
else if (!isspace(c))
{
if (isgraph(c)
printf("Non-digit character %c seen in numeric input\n", c);
else
printf("Non-digit character %o seen in numeric input\n", c);
while ((c = getchar()) != EOF && c != '\n') // clear out input stream
/* empty loop */ ;
break;
}
else
a[i++] = (int) strtol(buffer, NULL, 10); // no need for follow pointer,
// since we've already checked
// for non-digit characters.
}
Run Code Online (Sandbox Code Playgroud)
欢迎来到C中令人惊叹的互动输入世界.
| 归档时间: |
|
| 查看次数: |
45497 次 |
| 最近记录: |