scanf()是C语言标准库中的函数,用于从标准输入流stdin中按照指定的格式读取数据,并将其存储到由额外参数指向的位置。调用scanf()函数时,需要提供格式字符串作为第一个参数,该字符串包含了读取数据的格式信息。额外参数应是指向已分配对象的指针,其类型由格式字符串中的格式说明符指定。

函数原型

<pre data-lang="cpp">int scanf(const char * restrict format,...);

函数scanf()是一个通用输入子程序,从标准输入流stdin(通常是指键盘输入)中读取内容,并将它们保存在相应地址的变量中。在调用scanf()函数之前,通常需要包含头文件stdio.h,通过 #include <stdio.h> 提前声明。

参数

格式字符串 format:指定了输入数据的格式。该字符串包含了格式说明符,用于解析输入中的数据,并根据这些说明符将数据存储到后续参数所指向的位置。

关键字restrict:表示指针format所指向的内存区域在函数作用域内不会被其他指针以其他直接或间接的方式修改。

可变参数列表 ...:表示函数接受的参数数量不定,参数的类型和数量取决于函数的实际调用方式以及开发者的设计,这些参数用于存储根据格式字符串指定格式解析后的数据。

返回值

返回值类型为int。

返回值表示函数执行成功读取的数据项的数量,在读取数据时遇到’文件结束’或读取错误时返回EOF。

功能示例

<pre data-lang="cpp">#include <stdio.h>              int main() {                 int age;                 float height;                char name[50];               printf("请输入您的年龄、身高(米)和姓名:");        scanf("%d%f%s", &age, &height, &name);         printf("您的姓名是:%s,年龄是:%d岁,身高是:%.2f米\n", name, age, height);                   return 0;                } 

在该示例中,scanf() 函数根据格式字符串 %d %f %s从标准输入中读取一个整数、一个浮点数和一个字符,然后分别将它们存储到 age、height和name变量中。在scanf()函数中,每个格式说明符后面都有一个相应类型的参数,这些参数通常是使用取地址操作符 & 获取的变量的地址。因此,&age表示整数变量age的地址,&height表示浮点数变量height的地址,而 &name则表示字符串变量 name的地址。

注意:在scanf()函数中,当格式说明符连续出现时,如%d%d%d,输入时不能用逗号分隔,只能使用空白字符(例如空格、制表符或回车键)来分隔输入的整数数据。

举例来说,要求 %d%d%d 这样的格式字符串时,输入数据应该是像这样的格式:“2 3 4”或“2(tab)3(回车)4”。如果格式字符串中包含逗号(,),例如 %d,%d,%d,则在输入数据时必须使用逗号来分隔整数值,其格式为:“2,3,4”。

格式指令说明

在scanf()函数中,格式字符串format包含一系列格式指令:

1、 * :指示scanf()在读取输入数据时将其丢弃,而不会将其存储到任何变量中。例如,如果格式字符串为 %*d%d,则 scanf 将跳过第一个整数输入,但会将第二个整数输入存储到对应的变量中。这样,第一个整数值将被忽略,而第二个整数值将被读取并处理。

2、 域宽:以一个非零的十进制整数形式表示该格式指令最多读入的字符数。例如,%9s 表示要读取的字符串最多包含9个字符(不包括字符串结尾的空字符'\0')。

3、 格式说明符:

|| ||

4、 长度修饰符与格式说明符的搭配使用情况:

|| ||

例如,%hd用于读取short int类型的整数,%lf用于读取double类型的浮点数,%ld用于读取long int类型的整数。

注意:如果长度修饰符与格式说明符不匹配则引起未定义的行为。

空白和非空白字符

空白字符

指在输入流中表示空白的字符,包括空格(space)、制表符(tab)和换行符。

在scanf()函数中,空白字符的作用是使函数在读取输入时跳过一个或多个空白字符,而不将其计入输入的数据中。这样可以帮助忽略用户输入中的不必要的空格、制表符或换行符,使输入更加灵活。

非空白字符

即除空白字符外的其他字符。当 scanf()函数在遇到一个非空白字符后,会忽略之前的空白字符,并从这个非空白字符开始进行输入操作。

注意事项

1、 格式串按照从左到右的顺序,将格式说明符依次与变量表中的变量匹配。

2、 用于保存读入值的变量必须是指针,即相应变量的地址。

3、 虽然空格、制表符和新行符在 scanf() 函数中被视为域分隔符,但在单字符读取操作中,它们会被当作普通字符处理。

例如,如果对输入流 "x y" 使用 scanf("%c%c%c",&a,&b,&c),结果会是:字符 'x' 存储在变量 a 中,空格 ' ' 存储在变量 b 中,字符 'y' 存储在变量 c 中。

4、 注意控制串中的其他字符,包括空格、制表符和新行符,会被用于匹配和丢弃输入流中的字符,匹配到的字符都会被忽略。

例如,对于输入流 "10t20" 调用 scanf("%dt%d",&x,&y),结果:整数 10 存储在变量 x 中,整数 20 存储在变量 y 中,字符 't' 因为在控制串中,因此被忽略。

5、 ANSI C 标准向scanf()函数引入了一种名为扫描集(scanset)的新特性。扫描集定义了一个字符集合,scanf()可以从输入中读取符合集合中字符的内容,并将其赋值给相应的字符数组。扫描集由一对方括号中的字符定义,左方括号前需加上百分号。例如,下面的扫描集允许 scanf() 读取字符 A、B 和 C: %[ABC]。

在使用扫描集时,scanf()会连续读取集合中的字符,直到遇到不在集合中的字符为止,然后将这些字符放入相应的字符数组中,并以 null 结尾形成字符串。

另外,使用^字符可以表示补集,即除了指定集合中的字符之外的其它字符。例如,扫描集 %[^abc],^ 字符位于方括号的开头,表示构建了字符 a、b 和 c 的补集,scanf()将接收除了 a、b 和 c 之外的所有字符。

对于许多实现来说,用连字符可以说明一个范围(ISO C99标准没有规定)。例如, %[A-Z] 表示 scanf() 接收字母 A 到 Z。

注意:扫描集是区分大小写的。因此,如果需要区分大小写字符,应该分别指定大写和小写字母。

高版本的 Visual Studio 编译器中,scanf()被认为是不安全的,被弃用,应当使用scanf_s()代替scanf()。

6、 Scanf()函数中没有类似printf的精度控制。如:scanf("%5.2f",&a);是非法的。不能企图用此语句输入小数为2位的实数。

问题举例

问题一

如何让scanf()函数正确接受有空格的字符串?如: I love you!

<pre data-lang="cpp">#include <stdio.h>               int main() {                  char str[80];                 scanf("%s", str);                printf("%s", str);               return 0;                 } 

输入:

<pre data-lang="cpp">I love you!

输出:

<pre data-lang="cpp">I

上述程序并不能达到预期目的。因为scanf()扫描到"I"后面的空格就认为对str的扫描结束(空格没有被扫描),并忽略后面的" love you!"。改动上面的程序来验证一下:

<pre data-lang="cpp">#include <stdio.h>                #include <windows.h>                int main(void){                  char str[80], str1[80], str2[80];            scanf("%s",str);/*此处输入:I love you!*/          printf("%s\n",str);                Sleep(5000);/*这里等待5秒,告诉你程序运行到什么地方*/       scanf("%s",str1);/*这两句无需你再输入,是对stdin流再扫描*/      scanf("%s",str2);/*这两句无需你再输入,是对stdin流再扫描*/      printf("%s\n",str1);                printf("%s\n",str2);                return 0;                  }  

输入:

<pre data-lang="cpp">I love you!

输出:

<pre data-lang="cpp">Iloveyou!

由上述代码可知,残留的信息 love you是存在于stdin流中,而不是在键盘缓冲区中。scanf() 函数可以借助%[]格式控制符,程序如下所示:

<pre data-lang="cpp">#include <stdio.h>               int main() {                  char str[80];                 scanf("%[^\n]", str); /*scanf("%s",str);不能接收空格符*/     printf("%s\n", str);               return 0;                 } 

问题二

如何解决键盘缓冲区残余信息问题?

<pre data-lang="cpp">#include <stdio.h>               int main() {                  int a;                   char c;                  while(c != ‘N’){                   scanf("%d", a);                scanf("%c", c);                printf("a=%dc=%c\n", a,c);               }                    return 0;                 } 

scanf("%c", &c);语句不能正常接收字符,原因是在输入字符时,按下回车键会在输入缓冲区中留下一个换行符'\n',而scanf("%c", &c);会读取并处理这个换行符,导致无法正确接收字符。

解决方案:在两个scanf()函数之间加入getchar()来处理这个换行符。

问题三

如何处理输入类型与格式化字符串不匹配导致stdin流的阻塞?

<pre data-lang="cpp">#include <stdio.h>               int main() {                  int a=0, b=0, c=0, ret=0;             ret = scanf("%d%d%d ", &a, &b, &c,);           printf ("第一次读入数量:%d\n", ret);          ret = scanf("%c%d%d ", &a, &b, &c,);          printf("第二次读入数量:%d\n", ret);           return 0;                 }  

输入:

<pre data-lang="cpp">1 b 2 

输出:

<pre data-lang="cpp">第一次读入数量:1 

第一个scanf("%d%d%d ", &a, &b, &c)试图读取三个整数,但输入中第二个元素为b(非整数),因此只成功读取一个整数1,返回值为1。b和2仍留在输入缓冲区中。

输入:

<pre data-lang="cpp">6

输出:

<pre data-lang="null">第二次读入数量:3 

第二个scanf("%c%d%d ", &a, &b, &c)读取一个字符和两个整数。它首先从缓冲区读取字符b赋给变量a(ASCII值98),然后读取缓冲区中的整数2,以及新输入的整数6。

解决方案:在scanf()函数后使用fflush(stdin)函数清空输入缓冲区以避免这类问题。修改后的程序如下:

<pre data-lang="null">#include <stdio.h>               int main() {                  int a=0, b=0, c=0, ret=0;              ret = scanf("%d%d%d ", &a, &b, &c,);           fflush(stdin);                 printf ("第一次读入数量:%d\n", ret);           ret = scanf("%c%d%d ", &a, &b, &c,);           fflush(stdin);                 printf("第二次读入数量:%d\n", ret);           return 0;                 } 

输入:

<pre data-lang="cpp">1 b 2 

输出:

<pre data-lang="null">第一次读入数量:1 

fflush(stdin)函数清空输入缓冲区,移除未处理的数据。

输入:

<pre data-lang="null">1 3 6 

输出:

<pre data-lang="null">第二次读入数量:3 

由于缓冲区已清空,新的输入完整被第二个scanf()处理。

问题四

如何处理scanf()函数误输入造成程序死锁或出错?

<pre data-lang="cpp">#include <stdio.h>               int main() {                  int a, b, c;                 scanf("%d%d", &a, &b);              c = a + b;                  printf("%d+%d=%d\n", a, b, c);            return 0;                 } 

当程序接受正确的输入值a和b时,能够顺利执行并输出结果。

一旦用户输入非整数或格式不正确的数据,scanf()可能无法正确解析输入,导致程序不能继续前进或错误计算。

解决方法:利用scanf()函数的返回值来判断成功读取的变量数量。如果scanf()函数没有成功读取所有预期的变量,其返回值将小于请求的变量数量。

以下示例展示了如何通过循环和错误处理来确保正确输入:

<pre data-lang="cpp">#include <stdio.h>               int main() {                  int a, b, c;                 while(scanf("%d%d", &a, &b) != 2){             while( getchar() != '\n'); /*清空输入缓冲区*/        printf("输入无效,请输入两个整数并用空格分离。\n")      }                     c = a + b;                  printf("%d+%d=%d\n", a, b, c);             return 0;                 }  

补充:fflush(stdin)这个方法在GCC下不可用(在VC6.0下可以),可以使用while( getchar() != '\n')代替fflush(stdin)进行缓存清理。

发展

scanf()函数作为C语言中的标准输入函数,在早期的编程中扮演着重要的角色。它的设计初衷是为了方便从标准输入流中读取各种数据类型,并将其赋值给对应的变量。随着C语言的发展,scanf()函数逐渐成为了C编程中常用的输入方式之一。

然而,随着编程语言的发展和用户需求的变化,scanf()函数也暴露出了一些不足之处。其中包括对输入格式的严格要求、容易产生输入错误导致程序异常等问题。特别是在面对复杂的输入场景时,scanf()函数的使用变得繁琐而不灵活。

在C++语言中,尽管scanf()函数仍然存在并可用,但由于C++提供了更加强大和易用的输入输出库iostream,大多数程序员更倾向于使用cin()和cout()进行输入输出操作。cin()提供了更友好的接口和更好的错误处理机制,使得输入操作更加简便和安全。

综上所述,尽管scanf()函数在C语言和一些特定的编程场景中仍然被广泛使用,但随着编程语言的发展和用户需求的变化,它的地位逐渐被更加现代化和便捷的输入方式所取代。

来源: 百度百科

内容资源由项目单位提供