一 函数scanf的基本含义

  scanf函数是这样定义的:

1
int scanf(格式字符串, 指针列表);

  它的返回值是成功读入的数据项数。如果遇到错误,返回值为0;如果遇到end of fileend of file在Windows系统中用Ctrl+Z代替),返回值为EOFEOF是在<stdio.h>文件中宏定义的一个常量,值为-1)。可以从下例验证该结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 当以空格或换行为分隔输入a b c d后,换行输出0 0 0 0 0;
// 当以空格或换行为分隔输入1 2 3 4后,换行输出1 2 3 4 4;
// 当以空格为分隔输入1 2 ^Z 3 4之后,换行输出1 2 0 0 2(实际上在^Z后输入的数据都不再被读取);
// 当以换行为分隔输入1 2 ^Z ^Z之后,换行输出1 2 0 0 2;
// 当以换行为分隔输入^Z ^Z ^Z时,输出0 0 0 0 -1;
int main()
{
int a = 0;
int b = 0;
int c = 0;
int d = 0;
int num = scanf("%d%d%d%d", &a, &b, &c, &d);
printf("%d %d %d %d %d", a, b, c, d, num);
return 0;
}
我对此存疑,但我目前无法解决这个问题。单击此处查看具体疑问
  • 疑问一:是否输入过有效的内容影响退出时^Z所需次数。
      通过上例可以知道,在以换行为分隔输入时,如果输入过有效内容,2次^Z可以结束输入,否则需要3次。

  • 疑问二:不同的格式字符串可能会导致不同的结果。
      如下例所示。

    1
    2
    3
    4
    5
    6
    7
    8
    int main()
    {
    char ch = 0;
    int a = 0;
    scanf("%c", &ch);
    scanf("%d", &a);
    return 0;
    }

      在逐步调试下发现,以"%c"为格式字符串时需要2次^Z结束输入,而以"%d"为格式字符串时则需要3次^Z
      更进一步的,我做了一些研究来观察是否每一次^Z都有作用。

    • 对于"%c"为格式字符串的情形:
        在逐步调试下发现,如果以换行为分隔输入^Z a,观察到变量ch的值仍为0,这意味着第2次输入^Z无作用。

    • 对于"%d"为格式字符串的情形:
        在逐步调试下发现,如果以换行为分隔输入^Z 1,观察到变量a的值变为1,这意味着第1次输入^Z无作用;如果以换行为分隔输入^Z ^Z 1,观察到变量a的值仍为0,这意味着第3次输入^Z无作用。

      得到这样的结果令人百思不得其解,但我目前的水平还无法解决该问题。先将这样的疑问留在此处,我会在以后的学习过程中寻找契机解决它。如果有知之者,还望在评论区不吝赐教。另:我使用的是64位Windows系统下的Visio Studio 2022专业版。


二 格式字符串的说明

(一)抑制符(跳过符)

  在格式字符串中添加抑制符(*)使得读入的该数据被舍弃。如下例所示。

1
2
3
4
5
6
7
int main()
{
int a = 0;
int b = 0;
int num = scanf("%d%*d%d", &a, &b); //输入1 2 3
printf("%d %d %d", a, b, num); //得到 1 3 2
}

(二)域宽

  在格式字符串中添加十进制整数使以限制读入的数量。如果输入流数量超过域宽,则下次读入时从断点处开始。如下例所示。

1
2
3
4
5
6
7
8
int main()
{
int a = 0;
int b = 0;
scanf("%3d%4d", &a, &b); //输入123456789
printf("%d %d", a, b); //得到123 4567
return 0;
}

  基本整型a读入的域宽是3,即读入123;基本整型b读入时,从断点处开始,即从4开始,其域宽为4,故读入4567

(三)空白字符

  在格式字符串中所添加的空白字符空格(Space)换行符(Newline)制表符(Tab)和不添加的效果一致。如下例所示。

1
2
3
4
5
6
7
8
9
10
11
int main()
{
int a = 0;
int b = 0;
scanf("%d%d", &a, &b); //形式1
//scanf("%d %d", &a, &b); //形式2
//scanf("%d\n%d", &a, &b); //形式3
//scanf("%d\t%d", &a, &b); //形式4
printf("%d %d", a, b); //以上4种形式的结果一致
return 0;
}

  在输入流中所添加的空白字符具有分隔两数据的功能,一般来说只读不存。但当以单字符形式读入时则取消了分隔功能,如下例所示。

1
2
3
4
5
6
7
8
9
int main()
{
char a = 0;
char b = 0;
char c = 0;
scanf("%c%c%c", &a, &b, &c); //输入m (Space) n
printf("%c %c %c", a, b, c); //得到m (Space) n,即a为字符m,b为空格字符,c为字符n
return 0;
}

  这意味着此时的空白字符不再具有分隔作用,而直接如同其他字符一般被读取和存储。

(四)非空白字符

  在输入流中添加非空白字符意味着当在输入流中匹配到相同字符时不保存,未匹配到则停止读入(下次读入从断点处开始),如下例所示。

1
2
3
4
5
6
7
8
int main()
{
int a = 0;
int b = 0;
scanf("%dxy%d", &a, &b); //输入-情况1:123 456;情况2:123xy456
printf("%d %d", a, b); //得到-情况1:123 0; 情况2:123 456
return 0;
}
  • 在情况1下,当读入123并赋变量a后,由于未匹配到字符x,因此停止读取,故变量a123,变量b仍为0
  • 在情况2下,当读入123并赋变量a后,依次匹配并不保存字符x y,然后继续读入456并赋变量b,故变量a123,变量b456

三 字符残留问题

  考虑读入完全的情况,在读入完毕后,数据缓冲区仍残留换行符\n。可以从下例验证该结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int main()
{
char a = 0;
char b = 0;
int i = 0;
//while (i < 2)
//{
// scanf("%c", &a);//i=0:输入a;i=1:(无法输入)
// printf("%c", a);//i=0:得到a;i=1:得到(Newline)
// i++;
//}
while (i < 2)
{
scanf("%c", &a); //i=0:输入a;i=1:b
while (b = getchar() != '\n'); //用以除去换行符
printf("%c\n", a); //i=0:得到a;i=1:得到b
i++;
}
}

  除了采用上述办法解决字符残留问题外,还可以使用fflush(stdin);语句解决之。但是这在高版本的Visual Studio中不适用。