如何使 scanf 选择性地忽略其转换说明符之一?
How to make that scanf is optionally ignoring one of its conversion specifiers?
char value1[10];
int value2;
int value3 = 0;
if (!scanf("%s %d %d", &value1[0], &value2, &value3)) {
scanf("%s %d", &value1[0], &value2);
}
;
我正在尝试使用 scanf
插入 3 个值,但如果收到 2 个值,它将取而代之。我找不到任何关于如何使用 scanf
的答案。我一直在尝试使用 fgets
但标准输入中的值仍然存在。
char value1[10]; int value2; int value3 = 0;
if (!scanf("%s %d %d", &value1[0], &value2, &value3)) { scanf("%s %d", &value1[0], &value2); } ;
一件小事:不要使用 &value1[0]
,请使用 value1
。数组将衰减为指向其第一个元素的指针,理论上,您要将字符串存储在 value1
中。
其次,您只需调用 scanf
一次并查看其 return 值。这将告诉您读入了多少个值。在这种情况下,您并不关心,因此您可以使用:
if (scanf("%s %d %d", value1, &value2, &value3) < 2)
{ /* error handling */ }
else // we read in 2 or 3 entries, life is good
{ /* success handling */ }
您不能 scanf()
多次。当第二个 scanf()
执行时,数据不再在输入缓冲区中。
char value1[10];
int value2, value3;
int n = scanf("%9s%d%d", value1, &value2, &value3);
// scanf() returns the number of assignments it made, or EOF in case of failure
switch (n) {
default: fprintf(stderr, "scanf failure\n"); exit(EXIT_FAILURE);
case 0: strcpy(value1, "default"); /* fall-through */
case 1: value2 = 42; /* fall-through */
case 2: value3 = -1; /* fall-through */
case 3: break;
}
scanf("%s %d %d", &value1[0], &value2, &value3)
问题是 scanf()
无法忽略第三个转换说明符。它仍然继续尝试捕获第三个参数 value3
.
的十进制输入
首先将整个输入捕获为字符串,然后将此字符串的内容拆分为每个自己的对象可能是更好的选择。
fgets()
在接受用户输入时比 scanf()
安全一点,所以我将使用 fgets()
.
char* fgets ( char * str, int num, FILE * stream );
使用 fgets()
您需要定义要读取的字符数 (num
),这是维护安全性的一个很好的功能,但在这种情况下我们不知道用户是什么对于十进制输入请求,可能会输入整数。如果我们将数字指定为更少的字符,则其余数字将留在 stdin
.
一种解决方法是在对 fgets()
.
的调用中指定以十进制表示法表示类型 int
的整数可能的总位数
对于 64 位架构上的总数“2,147,483,647”,这些将是 10 位数字;对于 32 位架构上的总数“32,767”,这将是 5 位数字。我现在选择 64 位的情况。
所以10
(value1)+10
(value2)+10
(value3)+2
spacevalue1和value2之间的字符value2 和 value3 + 字符串的终止 [=30=]
= 33
个字符。请注意,fgets()
还会读取输入的换行符 \n
(但我们稍后很容易将其从字符串中丢弃)因此我们甚至还需要一个字符;总共 34
个字符:
char buffer[34];
fgets(buffer,sizeof(buffer),stdin);
之后我们需要证明我们在buffer
中存储的字符串中有多少内容。我们可以通过计算 space 个字符来隐式证明它:
unsigned int mark;
for(unsigned int i = 0; i < (sizeof(buffer)/sizeof(buffer[0]) - 1)); i++)
{
if(buffer[i] == ' ')
mark++;
}
之后我们需要transfer/convert使用sscanf()
将buffer
中的字符串中的各个内容片段transfer/convert到相应的自身对象中。如果我们现在在字符串中有 2 个 space 字符,我们可以在 sscanf
命令中使用 3 个转换说明符,否则我们有适当的 sscanf()
s:
if(mark == 2)
{
sscanf(buffer,"%s %d %d", value1, &value2, &value3);
}
else if(mark == 1)
{
sscanf(buffer,"%s %d", value1, &value2);
}
else if(mark == 0)
{
sscanf(buffer,"%s", value1);
}
else
{
printf("The input entered is not valid!\n");
printf("Please try again!\n");
}
那么整个代码是:
#include <stdio.h>
#include <string.h>
int main()
{
char value1[10];
int value2;
int value3;
unsigned int mark;
char buffer[34];
for(;;)
{
fgets(buffer,sizeof(buffer),stdin);
buffer[strcspn(buffer, "\n")] = 0;
mark = 0;
for(unsigned int i = 0; i < ((sizeof(buffer)/sizeof(buffer[0]) - 1)); i++)
{
if(buffer[i] == ' ')
mark++;
}
if(mark == 2)
{
sscanf(buffer,"%s %d %d", value1, &value2, &value3);
break;
}
else if(mark == 1)
{
sscanf(buffer,"%s %d", value1, &value2);
break;
}
else if(mark == 0)
{
sscanf(buffer,"%s", value1);
break;
}
else
{
printf("The input entered is not valid!\n");
printf("Please try again!\n");
}
}
printf("value1 = %s\n", value1);
if(mark == 1 || mark == 2)
printf("value2 = %d\n", value2);
if(mark == 2)
printf("value3 = %d\n", value3);
return 0;
}
为了灵活地解析输入,首选 fgets()
来获取输入行,而 sscanf()
来解析该输入。这允许在必要时重复扫描同一输入。
要使用 fgets()
,需要一个输入缓冲区。 fgets()
函数接受一个描述该输入缓冲区大小的参数,并从输入流中读取最多少一个字符。如果用户输入大于此限制,额外的输入将保留在输入流中。这可能会导致以后的 IO 操作出现问题,因此健壮的代码应该处理这些额外的字符(通过读取并使用或丢弃它们)。不需要健壮性的更简单的代码可以简单地选择一个大容量的输入缓冲区,不太可能被受信任的用户负担过重。下面的代码选择 BUFSIZE
的缓冲区大小为 1024,这应该有足够的额外空间,即使用户在输入末尾无意中键入了太多字符。还要注意,当它有 space 时,标记行尾的 \n
字符将从输入流中读取并存储在输入缓冲区中。这个换行符通常需要从缓冲区中删除,但在涉及 sscanf()
的情况下,通常不需要进一步的操作,因为换行符不应干扰扫描。
sscanf()
函数(事实上,所有fscanf()
家族函数)响应%n
指令,写入从输入中读取的字符数,直到出现此指令转换为提供的整数变量。对于 sscanf()
,这实际上意味着当在格式字符串中遇到 %n
时,读取的最后一个字符之后的字符索引存储在这个提供的整数变量中。
下面的代码通过首先执行 num_assigned = sscanf(buffer, "%9s %d%n", value1, &value2, &scan_loc)
来使用此 %n
功能。这里扫描了始终预期的前两个值,并将读取到此处的字符数存储在 scan_loc
中。 num_assigned
变量保存了 sscanf()
赋值的次数,应该是 2。否则代码会继续尝试解析输入字符串的其余部分。这里,buffer
指向buffer[]
数组的第一个字符,buffer + scan_loc
指向sscanf()
[=67读取的最后一个字符后的字符 =].也就是说,对 sscanf()
的第二次调用从第一次调用停止的地方开始。如果第二次调用成功对 value3
进行赋值,则 num_assigned
递增。
请注意,sscanf()
可以 return 0 或 -1 时未进行分配,具体取决于具体情况。出于这个原因,像 num_assigned += sscanf(buffer + scan_loc, "%d", &value3)
这样的代码是不好的,因为未能进行分配可能会导致 递减 num_assigned
。另请注意,当提供正确大小的输入缓冲区时,fgets()
不易受缓冲区溢出问题的影响。如果 buffer
是一个数组,那么 sizeof buffer
将始终提供正确的大小(如果 buffer
是一个指针,则必须通过其他方式获得大小)。但是,sscanf()
在使用 %s
指令时仍然容易发生缓冲区溢出。在使用 %s
(或 %[]
scanset 指令,就此而言)时始终指定最大字段宽度,以避免输入大于预期时缓冲区溢出。 [=47=]
终止符 always 由 sscanf()
使用 %s
指令编写,因此要腾出空间,请使用 %9s
作为数组,例如 char value1[10]
可容纳 10 个字符。
#include <stdio.h>
#include <stdlib.h> // for exit() and EXIT_FAILURE
#define BUFSIZE 1024
int main(void) {
char value1[10];
int value2 = 0;
int value3 = 0;
char buffer[BUFSIZE]; // generous user input buffer
int scan_loc = 0; // scan location in buffer
int num_assigned = 0; // number of values assigned by sscanf()
// fetch user input: parse only if fgets is successful
if (fgets(buffer, sizeof buffer, stdin) != NULL) {
// attempt to make first two assignments
num_assigned = sscanf(buffer, "%9s %d%n", value1, &value2, &scan_loc);
if (num_assigned == 2) {
// success: attempt to make third assignment
int final_assignment = sscanf(buffer + scan_loc, "%d", &value3);
if (final_assignment == 1) {
++num_assigned;
}
} else {
fprintf(stderr, "Input error: %d values read\n", num_assigned);
exit(EXIT_FAILURE);
}
printf("value1: %s, value2: %d", value1, value2);
if (num_assigned == 3) {
printf(", value3: %d\n", value3);
} else {
putchar('\n');
}
}
return 0;
}
示例执行:
$ ./a.out
test2 42
value1: test2, value2: 42
$ ./a.out
test3 12 34
value1: test3, value2: 12, value3: 34
char value1[10];
int value2;
int value3 = 0;
if (!scanf("%s %d %d", &value1[0], &value2, &value3)) {
scanf("%s %d", &value1[0], &value2);
}
;
我正在尝试使用 scanf
插入 3 个值,但如果收到 2 个值,它将取而代之。我找不到任何关于如何使用 scanf
的答案。我一直在尝试使用 fgets
但标准输入中的值仍然存在。
char value1[10]; int value2; int value3 = 0;
if (!scanf("%s %d %d", &value1[0], &value2, &value3)) { scanf("%s %d", &value1[0], &value2); } ;
一件小事:不要使用 &value1[0]
,请使用 value1
。数组将衰减为指向其第一个元素的指针,理论上,您要将字符串存储在 value1
中。
其次,您只需调用 scanf
一次并查看其 return 值。这将告诉您读入了多少个值。在这种情况下,您并不关心,因此您可以使用:
if (scanf("%s %d %d", value1, &value2, &value3) < 2)
{ /* error handling */ }
else // we read in 2 or 3 entries, life is good
{ /* success handling */ }
您不能 scanf()
多次。当第二个 scanf()
执行时,数据不再在输入缓冲区中。
char value1[10];
int value2, value3;
int n = scanf("%9s%d%d", value1, &value2, &value3);
// scanf() returns the number of assignments it made, or EOF in case of failure
switch (n) {
default: fprintf(stderr, "scanf failure\n"); exit(EXIT_FAILURE);
case 0: strcpy(value1, "default"); /* fall-through */
case 1: value2 = 42; /* fall-through */
case 2: value3 = -1; /* fall-through */
case 3: break;
}
scanf("%s %d %d", &value1[0], &value2, &value3)
问题是 scanf()
无法忽略第三个转换说明符。它仍然继续尝试捕获第三个参数 value3
.
首先将整个输入捕获为字符串,然后将此字符串的内容拆分为每个自己的对象可能是更好的选择。
fgets()
在接受用户输入时比 scanf()
安全一点,所以我将使用 fgets()
.
char* fgets ( char * str, int num, FILE * stream );
使用 fgets()
您需要定义要读取的字符数 (num
),这是维护安全性的一个很好的功能,但在这种情况下我们不知道用户是什么对于十进制输入请求,可能会输入整数。如果我们将数字指定为更少的字符,则其余数字将留在 stdin
.
一种解决方法是在对 fgets()
.
int
的整数可能的总位数
对于 64 位架构上的总数“2,147,483,647”,这些将是 10 位数字;对于 32 位架构上的总数“32,767”,这将是 5 位数字。我现在选择 64 位的情况。
所以10
(value1)+10
(value2)+10
(value3)+2
spacevalue1和value2之间的字符value2 和 value3 + 字符串的终止 [=30=]
= 33
个字符。请注意,fgets()
还会读取输入的换行符 \n
(但我们稍后很容易将其从字符串中丢弃)因此我们甚至还需要一个字符;总共 34
个字符:
char buffer[34];
fgets(buffer,sizeof(buffer),stdin);
之后我们需要证明我们在buffer
中存储的字符串中有多少内容。我们可以通过计算 space 个字符来隐式证明它:
unsigned int mark;
for(unsigned int i = 0; i < (sizeof(buffer)/sizeof(buffer[0]) - 1)); i++)
{
if(buffer[i] == ' ')
mark++;
}
之后我们需要transfer/convert使用sscanf()
将buffer
中的字符串中的各个内容片段transfer/convert到相应的自身对象中。如果我们现在在字符串中有 2 个 space 字符,我们可以在 sscanf
命令中使用 3 个转换说明符,否则我们有适当的 sscanf()
s:
if(mark == 2)
{
sscanf(buffer,"%s %d %d", value1, &value2, &value3);
}
else if(mark == 1)
{
sscanf(buffer,"%s %d", value1, &value2);
}
else if(mark == 0)
{
sscanf(buffer,"%s", value1);
}
else
{
printf("The input entered is not valid!\n");
printf("Please try again!\n");
}
那么整个代码是:
#include <stdio.h>
#include <string.h>
int main()
{
char value1[10];
int value2;
int value3;
unsigned int mark;
char buffer[34];
for(;;)
{
fgets(buffer,sizeof(buffer),stdin);
buffer[strcspn(buffer, "\n")] = 0;
mark = 0;
for(unsigned int i = 0; i < ((sizeof(buffer)/sizeof(buffer[0]) - 1)); i++)
{
if(buffer[i] == ' ')
mark++;
}
if(mark == 2)
{
sscanf(buffer,"%s %d %d", value1, &value2, &value3);
break;
}
else if(mark == 1)
{
sscanf(buffer,"%s %d", value1, &value2);
break;
}
else if(mark == 0)
{
sscanf(buffer,"%s", value1);
break;
}
else
{
printf("The input entered is not valid!\n");
printf("Please try again!\n");
}
}
printf("value1 = %s\n", value1);
if(mark == 1 || mark == 2)
printf("value2 = %d\n", value2);
if(mark == 2)
printf("value3 = %d\n", value3);
return 0;
}
为了灵活地解析输入,首选 fgets()
来获取输入行,而 sscanf()
来解析该输入。这允许在必要时重复扫描同一输入。
要使用 fgets()
,需要一个输入缓冲区。 fgets()
函数接受一个描述该输入缓冲区大小的参数,并从输入流中读取最多少一个字符。如果用户输入大于此限制,额外的输入将保留在输入流中。这可能会导致以后的 IO 操作出现问题,因此健壮的代码应该处理这些额外的字符(通过读取并使用或丢弃它们)。不需要健壮性的更简单的代码可以简单地选择一个大容量的输入缓冲区,不太可能被受信任的用户负担过重。下面的代码选择 BUFSIZE
的缓冲区大小为 1024,这应该有足够的额外空间,即使用户在输入末尾无意中键入了太多字符。还要注意,当它有 space 时,标记行尾的 \n
字符将从输入流中读取并存储在输入缓冲区中。这个换行符通常需要从缓冲区中删除,但在涉及 sscanf()
的情况下,通常不需要进一步的操作,因为换行符不应干扰扫描。
sscanf()
函数(事实上,所有fscanf()
家族函数)响应%n
指令,写入从输入中读取的字符数,直到出现此指令转换为提供的整数变量。对于 sscanf()
,这实际上意味着当在格式字符串中遇到 %n
时,读取的最后一个字符之后的字符索引存储在这个提供的整数变量中。
下面的代码通过首先执行 num_assigned = sscanf(buffer, "%9s %d%n", value1, &value2, &scan_loc)
来使用此 %n
功能。这里扫描了始终预期的前两个值,并将读取到此处的字符数存储在 scan_loc
中。 num_assigned
变量保存了 sscanf()
赋值的次数,应该是 2。否则代码会继续尝试解析输入字符串的其余部分。这里,buffer
指向buffer[]
数组的第一个字符,buffer + scan_loc
指向sscanf()
[=67读取的最后一个字符后的字符 =].也就是说,对 sscanf()
的第二次调用从第一次调用停止的地方开始。如果第二次调用成功对 value3
进行赋值,则 num_assigned
递增。
请注意,sscanf()
可以 return 0 或 -1 时未进行分配,具体取决于具体情况。出于这个原因,像 num_assigned += sscanf(buffer + scan_loc, "%d", &value3)
这样的代码是不好的,因为未能进行分配可能会导致 递减 num_assigned
。另请注意,当提供正确大小的输入缓冲区时,fgets()
不易受缓冲区溢出问题的影响。如果 buffer
是一个数组,那么 sizeof buffer
将始终提供正确的大小(如果 buffer
是一个指针,则必须通过其他方式获得大小)。但是,sscanf()
在使用 %s
指令时仍然容易发生缓冲区溢出。在使用 %s
(或 %[]
scanset 指令,就此而言)时始终指定最大字段宽度,以避免输入大于预期时缓冲区溢出。 [=47=]
终止符 always 由 sscanf()
使用 %s
指令编写,因此要腾出空间,请使用 %9s
作为数组,例如 char value1[10]
可容纳 10 个字符。
#include <stdio.h>
#include <stdlib.h> // for exit() and EXIT_FAILURE
#define BUFSIZE 1024
int main(void) {
char value1[10];
int value2 = 0;
int value3 = 0;
char buffer[BUFSIZE]; // generous user input buffer
int scan_loc = 0; // scan location in buffer
int num_assigned = 0; // number of values assigned by sscanf()
// fetch user input: parse only if fgets is successful
if (fgets(buffer, sizeof buffer, stdin) != NULL) {
// attempt to make first two assignments
num_assigned = sscanf(buffer, "%9s %d%n", value1, &value2, &scan_loc);
if (num_assigned == 2) {
// success: attempt to make third assignment
int final_assignment = sscanf(buffer + scan_loc, "%d", &value3);
if (final_assignment == 1) {
++num_assigned;
}
} else {
fprintf(stderr, "Input error: %d values read\n", num_assigned);
exit(EXIT_FAILURE);
}
printf("value1: %s, value2: %d", value1, value2);
if (num_assigned == 3) {
printf(", value3: %d\n", value3);
} else {
putchar('\n');
}
}
return 0;
}
示例执行:
$ ./a.out
test2 42
value1: test2, value2: 42
$ ./a.out
test3 12 34
value1: test3, value2: 12, value3: 34