现在的位置: 首页 > 综合 > 正文

黑马程序员——iOS开发——c语言——scanf函数详细讲解

2017年11月26日 ⁄ 综合 ⁄ 共 15256字 ⁄ 字号 评论关闭

-------Java培训、Android培训、iOS培训、.Net培训、期待与您交流!------


scanf的使用详解


1、简介

scanf函数是标准的格式化输入函数,也就是从标准输入设备(键盘) 读取输入的信息。在stdio.h中声明,因此要使用scanf函数,必须加入
#import <stdio.h>。使用scanf时候时,需要传入变量地址作为参数,且scanf函数会等待标准输入设备(键盘)输入数据,并将输入的数据赋值给变量地址对应的变量。且返回成功赋值的数据项,如果遇到错误或遇到end of file,返回值为EOF
scanf是从标准输入(stdin)缓冲区中读取输入的数据)


2、基本用法

(1)、语法格式:


scanf(“格式说明字符串”,变量地址);(变量地址必须有效,并且与格式说明符的顺序一致)

如: 


(2)、执行过程分析


使用scanf函数时,传入的参数,第一个是格式说明字符串,其它是一一对应的变量地址参数。执行scanf函数的时候,会等待标准设备输入,并不会往后执行代码。输入完毕后,敲一下回车键,目的是告诉scanf函数我们已经输入完毕了,scanf函数会将输入的值赋一一对应的赋值给后面的地址对应的变量。


如:  int age;

        scanf(“%d”, &age);

执行到scanf函数的时候,会等待输入。第一个参数是格式说明字符串,%d,说明要求用户输入十进制形式的一个整数。这里注意第二个参数不是age变量,而是age变量的地址&age,(&是c语言中的地址运算符。可以获取变量地址)。当用户输入完一个整数后,敲一下回车键。scanf函数会把输入的数据传递给地址对应的变量。并返回赋值成功个数。这样就执行完毕scanf这函数,继续后面代码的执行。


(3)注意点:


1》输入数据时候,遇以下情况结束一个数据的输入


*有间隔符合的情况下,以输入的间隔符为结束一个数据的输入的标识

    (有间隔符合输入数据的时候必须添加间隔符号,否则报错,

注意在有间隔符号的情况下,它们之间如果加入空格会造成的影响)

*无间隔符合的情况下,以tab、空格、回车为结束一个数据输入的标准

*达到数据的宽度

*输入非法数据


2》scanf函数接收输入数据时,遇以下情况结束一个变量的赋值:(不是结束该scanf函数)


  *读到自定义的间隔符

  *默认情况下,读取到空白符,如tab、空格、回车。

  *达到制定数据的宽度

  *读到到非法输入,函数直接返回(后面的其它格式符都不会)

3》scanf函数结束情况


  *每一个格式符均有对应的数据,然后按回车后结束


4》读到非法数据的处理方式:


完成输出,按回车键盘后。scanf函数将输入的数据赋值给后面对应位置的变量的时候,如果一遇到不匹配的就会直接返回,不再给后面变量赋值。如果第一个就不匹配,整个scanf函数中的,变量都不会赋值,或因缓冲区问题,赋错误值。


5》开头空白符号无效(char类型除外)


当一次多个变量赋值的时候,开头就使用空格、tab、回车,无效,只有在输入数据后面使用它们才会作为间隔或结束符合使用(char除外,对应char它们都是有效输入数据)


3、格式说明字符串详解


(1)、一般结构:

%[*][宽度][F/N][h/l]类型字符

由三部分组成

  1》格式符(格式化说明符)

  2》空白符

  3》非空白符


(2)、三个部分的详解:


    1》格式符(格式化说明符


<strong>%a                 读入一个浮点值(仅C99有效)   
%A                 同上  
%c                 读入一个字符
%d                 读入十进制整数  
%i                 读入十进制,八进制,十六进制整数  
%o                 读入八进制整数
%x                 读入十六进制整数  
%X                 同上 
%c                 读入一个字符  
%s                 读入一个字符串  
%f                 读入一个浮点数 
%F                 同上  
%e                 同上  
%E                 同上  
%g                 同上  
%G                 同上  
%p                 读入一个指针  
%u                 读入一个无符号十进制整数 
%n                 至此已读入值的等价字符数  
%[]                扫描字符集合  
%%                 读%符号</strong>


            修饰符

         *:星号 ,用于空读一个数据。

              百分号(%)与格式符之间的星号(*)表示读指定类型的数据但不保存

        L/l 长度修饰符               输入"长"数据

      h 长度修饰符                 输入"短"数据

      W 整型常数                   指定输入数据所占宽度

           hh,ll同上h,l但仅对C99有效


 
2》空白符

空白字符,会使scanf函数读操作中略去输入中一个或多个空白符,空白符可以是空格、tab、回车


   
3》非空白符

非空白字符,会使scanf函数读操作中剔除与这个非空白字符相同的字符。但在输入时必须输入这些字符。否则就会出错 。一般作为间隔符合使用。


 (2)、使用例子:


1》常用比较难的格式符例子:


*%f:用于接收或输出一个小数  

               

printf(“<%f>",12.3456789);

输出为:<12.345679>

总结:默认输出6位小数,且如果原数据有第七位小数,四舍五入给第六位


float a;

scanf(“%f",&a); // 接收一个小数

printf(“<%f>",a); //输出接收的数据

输入:3.1234567  (注意有7位小数)

输出:3.123457


*%e的使用:%e是以指数形式输出或读起一个小数

             例如:1.256e2 就是1.256*10的2次方  

<strong>printf("%e",1123.45678); 
输出:1.123457e+03 // 默认输出格式
//总结:默认输出6位小数。如果有第七位小数,四舍五入给第六位

float a;
scanf(“%e",&a);
printf(“%e",a);
//输入:123.456789 ,输出 1.234568e+02
</strong>


         

2》宽度修饰符号的常用例子:%m.nX——》x代表格式符


                * %m.nf/e——》m控制数据的整个长度,n控制小数的位数, n与m之间采用“.”作为分隔符号

                     
与printf函数一起使用
: 

m控制整个输出数据的长度,如果m大于输出的数据长度,在数据前面使用空格填充,如果m小于默认输出数据的长度,m失效,而n控制小数的长度。其它格式:%mf/e、%.nf/e、%m.nf/e
printf(“%.5f",5.123456789); ——》n是5表示输出5位小数
输出:5.12346
printf(“%4f",5.123456789); ——》m长度为4,小于默认输出数据的整体长,无效,使用默认输出
输出:5.123457 (默认输出长度是,保证小数是6位,对于f而言)
printf("<%9f>",5.123456789); —》m大于默认输出的长度,前面使用空格填充
输出:< 5.123457>
printf(“<%9.4f>”,5.123456789);——》同时使用m和n,之间使用.间隔
输出:<   5.1235>



                     与scanf函数一起使用:(没有精度控制) 

只能有%mf/e这种格式,不能有%m.nf/e 或 %.nf/e,里面的“.”符号无效。但能控制读取的位数,m如果小于输入数,直接截取m位读取进去,如果m大于输入数,按原数读
例子:
float a;
scanf(“%2f”,&a); // m是2,只读取前2位数,
printf(“%f",a);
输入:123.4567 ——》m是2,只读取前2位数
输出:12.000000

scanf(“%.3f",&a); // 报严重警告”.”是个无效符号(Invalid conversion specifier ‘.’)
scanf(“%3.3e",&a); 也同样警告

 

3》%m.nd的使用


printf(“<%.9d>”,1234567); //.n格式:n如果大于整数长度,默认使用0
// 输出:<001234567>
printf(“<%.4d>”,1234567); //.n格式:如果n小于其长度,n失效
// 输出:<1234567>
printf(“<%9d>”,1234567); //  m格式,如果大于整数的长度,使用空格填充
// 输出:<  1234567>
printf("<%4d>",1234567); //如果m小于其长度,m失效
// 输出:<1234567>  
printf(“<%9.9d>",1234567); // 都相同的情况下,使用.n的效果
// 输出:<001234567>
printf(“<%9.8d>",1234567); // 先采用前面的9,再采用后面的.8格式
// 输出:< 01234567>


int a;
scanf(“%3d",&a); //m小于输入数,只截取m长度的数
printf("<%d>",a); 
//输入:1234567
//输出:<123>
int b;
scanf(“%8d",&b); //当m大于输入数,m失效
printf(“<%d>",b);
//输入:123
//输出:<123>
scanf(“%.4d",a); // 不可以,报严重警告:点”.”无效符号<strong style="font-family: Helvetica; letter-spacing: 0px;">                             </strong>

     *scanf中没有精度控制


  

(4)、注意点


1》不可使用%f接收double类型的数据 ,会报下面的警告


       
默认情况下,a、f、e 和 g 告诉 scanf() 为 float 分配数据。 如果将 L / l放在这些修饰符的前面,则 scanf() 为 double 分配数据。使用 L 就是告诉 scanf(),接收数据的变量
long double 型变量。小写l,表示普通的double类型

注意大写L,也是不可以接收double,而是long double类型 


正确写法  

      

 long double a;

 scanf(“%Lf",&a); // L修饰表示long 

 printf(“<%Lf>”,a);// 输出也要使用L

double a;

scanf(“%lf",&a); // 使用小写l,表示普通doule


2》参数是变量的地址,使用&取地址符(数组除外,不用&)     

int num = 0;
scanf("%d", num);
printf("%d", num);
printf("请输入数字\n");


上面错误,修改为:

<strong>int num = 0;
printf("请输入数字\n");
scanf("%d", &num);
printf("%d", num);</strong>

3》如果只是定义一个指针,没有初始化,也就是没有指向具体数组。不可以拿来直接和scanf使用,原因:只是定义和分配了指针本身变量,而没有分配具体存储内容的空间(也就是没有任何指向指向)


        如:

        char *name; 

          scanf(“%s”, name);  是错误的,需要先指向具体数组


4、扫描字符集合的使用:%[] 


   
(1)、基本认识,只能使用在

    1》 基本了解:

扫描集(scanset),是scanf后来添加的一个新的特性。

扫描集定义一个字符集合,只有在扫描集范围内的字符,才会被scanf读取并赋值给对应的字符数组。一旦遇到非法数据,立刻返回


如:

scanf(“%[abc]”,str); ——》表示只由满足abc这三个字符才能输入str里面


    2》常用特殊符号:

^,放在首位,表示除了后面定义的之外的字符

    如:

scanf(“%[^abc]”, str);  ——》表示除了abc之外的字符都可以赋值给str数组


:表示一个范围,如:

scanf(“%[a-z]”,str);——》表示a到z范围的小写都可以赋值给str数组

(注意只是小写,扫描集合区分大小写.%[a-zA-Z]表示大小写都可以)



   (2)、常用技巧


1》限制接收的数据


  *只接收a-z和A-Z的字母,scanf(“%[a-zA-Z]”,str);

 

2》接收带空格的字符串——》scanf("%[^\n]",
name);

<strong>                   
#include <stdio.h>
int main(int argc, const char * argv[])
{
    char name[20];
    printf("请输入字符串\n");
    scanf("%[^\n]", name);
    printf("%s\n", name);
    return 0;
}</strong>


请输入字符串

hello world!入)

hello world!    (输出)


 分析%[^\n]这个扫描集:

^表示非、除了的意思,^\n表示除了“\n”都可以被scanf读取并赋值给字符数组。


3》stdin的缓冲区清空——》scanf(“%*[^\n]%*c”);清除一段文字(后面详细说)


 (3)、注意点


1》因为扫描集,赋值的时候会以字符串的形式赋值给对应的字符数组,可使用%s直接输出,结果。而不用像c语音的数组遍历那样(所有注意输入的长度长度,不要多于数组元素个数) 

<strong>#include <stdio.h>
#include <string.h>
int main(int argc, const char * argv[]){
    char str[10];
    printf("请输字符串\n");
    scanf("%[a-zA-Z]", str);
    printf("str1=%s\n", str); // 可这样直接输出
    printf(“输入的字符长度:%zd\n",strlen(str)); 
    for(int i = 0; i<strlen(str);i++){ 
        printf("str[%d]=%c\n", i, str[i]);
    }
        return 0;
}

/*
输入:aaaaaaaaaaaaaaaa
输出:str1=aaaaaaaaaaaaaaaa
  输入的字符长度:16
str[0]=a,str[1]=a,str[2]=a,str[3]=a,str[4]=a,
  str[5]=a, str[6]=a,str[7]=a,str[8]=a,str[9]=a,
             str[10]=a,str[11]=a,str[12]=a,str[13]=a,str[14]=a,str[15]=a,
*/</strong>

2》%[]对间隔符的问题  

     

<strong>//空白符,也会当作内容读取(类似给char赋值),如果字符集中没有空白符号,就会不匹配而返回

#include <stdio.h>
int main(int argc, const char * argv[]){
    char str1[10], str2[10];
    printf("请输字符串\n");
    scanf("%[a-zA-Z]%[a-zA-Z]", str1, str2); // 这里没有任何分隔符,所以str2永远也不会符合值的
    printf("str1=%s\n", str1);
    printf("str2=%s", str2);
    return 0;
}

/*
输入:aaa□□bbbb 
输出:str1=aaa
     str2=
分析:
扫描集合的使用中,会和单个字符赋值的时候,对空格符合的处理是一样的,就是会把它们也当作赋值内容的一分,而不是类似%d等类型符那样”忽略它们“。在读到a后面的一个□,会在str1的扫描集内比对,结果不满足其范围,这样scanf就相当于读取到非法数据,直接返回。这样就不会给str2赋值。
也有同学可能认为,如果分开写,这样“空白格符”会作为默认的间隔符使用吗?回答依然把“空白符”作为可赋值内容的一部分使用(只要存在了stdin的缓存问题)
scanf("%[a-zA-Z]%[a-zA-Z]", str1, str2); (分开写成下面方式,也是无效的)
scanf("%[a-zA-Z]", str1);scanf("%[a-zA-Z]", str2);
*/</strong>

<strong>// 如果定义的分隔符,在扫描集内,就会使间隔符失效。赋值发生错误
#include <stdio.h>
int main(int argc, const char * argv[])
{
    char str1[10], str2[10];
    printf("请输字符串\n");
    scanf("%[a-zA-Z,],%[a-zA-Z,]", str1, str2); // 这里两个扫描集,以“,”分隔
    printf("str1=%s\n", str1);
    printf("str2=%s", str2);
    return 0;
}
/*
输入:aaa,bbb   
输出:str1=aaa,bbb 
    str2=
结果分析,scanf对str1赋值操作中,把“,”当作自己的普通字符,赋给str1。这样stdin中的”,“就被scanf读取出来。而后面找不到与“,”这个分隔符匹配的字符,会造成匹配不到“,”这个字符的错误,scanf会直接返回。
,
// 如果分隔符,不在扫描集内,输入的时候必须写分隔符
#include <stdio.h>
int main(int argc, const char * argv[]){
    char str1[10], str2[10];
    printf(“请输字符串,使用#作为分隔符合\n”);
    scanf("%[a-zA-Z]#%[a-zA-Z]", str1, str2); // 采用#作为分隔符合
    printf("str1=%s\n", str1);
    printf("str2=%s", str2);
    return 0;
}
/*
输入:aaabbb#cccddd  (使用#分隔) 
输出:str1=aaabbb
     str2=cccddd
分析:
使用#作为分隔符合,就必须要遵守。如果输入别的输入符合,或不满足的字符,scanf会自动返回,不会再继续赋值:
输入:aabb*cc#dd   (scanf只要读取到不满足格式的字符,会立刻返回,而不会继续赋值)
输出:str1=aabb
str2=
分开写 scanf("%[a-zA-Z]#%[a-zA-Z]", str1, str2);也会有上面效果
scanf("%[a-zA-Z]#", str1);
scanf("%[a-zA-Z]", str2)
(注意分汉字和英文情况输入)
*/
</strong>

5、一次给多个变量赋值:


(1)、简介:


格式说明符可以由多个格式符组成,每个格式符都对应一个相同类型变量地址。scanf函数可以把输入的数据,根据格式符,依次赋值给相同类型的变量。默认使用tab、空格、回车作为间隔,也可以自己定义间隔符合如“,”、“-”、“#”等。但是要注意最后还是以回车键作为输入结束


(2)、常用例子:


1》使用scanf函数接收3个数


*默认中间不使用任何间隔符合 

<strong>int a= 1 ,b= 1, c =1;
 scanf("%d%d%d",&a,&b,&c);       
 printf("%d,%d,%d",a,b,c);</strong>


注意:输入的时候,中间使用空格、tab、回车作为间隔符合,直到输入完三个数据

然后按回车,结束输入,并赋值

*使用自己定义的间隔符合 

<strong> int a= 1 ,b= 1, c =1;
        
 scanf(“%d,%d,%d”,&a,&b,&c);
        
 printf("=%d,%d,%d=",a,b,c);
</strong>

注意:我们在输入数据的时候,就必须带上“,”作为数据间的间隔,如果使用其它间隔符合,会造成赋值的错误,如输入:11,12,13 ,然后回车就会把数据依次赋值

    或3,□4,□5 ↙(输入a,b,c的值)

            3,□□□4,□5 ↙(输入a,b,c的值)(把%d,看作一个整体

  不可:3□□□,4□,5


        2》接收3个字符数据:(会有些莫名的错误——》把空格、tab、回车作为数据赋值,不以它们为分割符,以对应关系和字符个数结束判断依据) 

<strong> char a ,b, c;

 scanf(“%c%c%c",&a,&b,&c);

 printf("<%c,%c,%c>",a,b,c);
</strong>

注意:这里我们是默认没有间隔符,但是因为scanf在给char类型的数据赋值的是,会把空格、tab、回车或其它任何符合如“,”、“#”等都当作一个char数据赋值给后面的地址参数对应的变量。所以这里特别注意,如果我们输入数据的时候,以空格、tab、回车等作为间隔的时候,实际上是把它们赋值给了后面的变量地址对应的变量,这样我们的数据就会混乱,

                                        

输入: a回车b回车——》当第二个回车的时候,就会当作输入结束

输出:<a,

  ,b>


       

分析:这里会把输入的第一回车当作,第二个字符数据。当我们输入完三个字符数据后,我们再按回车的时候,系统会认为我们已经输入完了三个字符数据,从而结束输入,并赋值变量,把a赋值给变量a,回车赋值给b,把b赋值给b变量。(注意输入结束点,就是输入数据个数和格式符个数一致了)


正确输入:直接输入连续的字符,以回车结束


输入:abc回车

输出:  <a,b,c>


            所以,接收多个字符数据的时候,如果没有默认的分割符。我就直接输入对应个数的连续字符即可:


*使用间隔符 

<strong>char a ,b, c;
        
scanf("%c,%c,%c",&a,&b,&c);
        
printf(“<%c,%c,%c>”,a,b,c);</strong>

输入:a,b,c     输出:a,b,c


注意一个细节:如果我们是在输入法是汉字的情节下,按亮caps lock键,我们看到的控制台输入的是小写,但输出的是大写。因为这是一个大写的键。所以切换为英文格式,这样我们看到输入是小写,输出的也是小写


难点:如果我们使用多个scanf接收数据的时候,前面遗留的数据会造成后面对字符数据接收的错误影响,所以一般还要在最后添加fflush(stdin);这个函数,清空输入流,后面介绍



6、接收字符串(c语言中必须使用字符数组作为接收变量,且要确定元素个数)


(1)、%s的基本使用——》不可接收带空格的字符串


1》单个%s的基本使用——》不需要使用&     



  

   

注意1:

%s只是接收一个字符串,如果我们输入字符串的时候,中间有空格、tab,这两个默认的间隔符,其后面的字符不会赋值给地址对应的数组变量,而是当作无用数据给截去。且只有按回车键就会立刻结束输入

开始输入 tab、空格、回车,都是被scanf给忽略(先这样理解)


注意2:这样接收的字符串,不能包含空格。



2》多个%s的同时使用   

<strong>char name1[10];
char name2[10];
    
scanf("%s%s", name1, name2);
    
printf("%s,%s", name1, name2);
//输入:abcdefg  hijklmn  (这里使用空格间隔)
//输出:abcdefg,hijklmn</strong>


*接收多个字符串 ——》默认采用tab、空格、回车作为间隔符

(2)、使用扫描集,来接收字符串,上面已经介绍

*注意把空白符当作其赋值内容的一部分。而%s会把空白符合当作间隔符使用

7、难点问题


 (1)、stdin中的缓冲区问题——》存在于多个scanf操作中(如果是单个scanf操作只会直接返回不影响)


1》单个scanf函数,不存在缓存问题,只是不匹配,就直接返回


<strong>// 一个scanf,就算是为多个数据读取赋值操作,遇到错误信息会直接返回,而不会存在stdin的缓存现象,而引起的错误

int age;
char c;
scanf(“%d,%c”, &age,&c);
printf("%d,%c", age, c);
/*
输入:a回车
输出: 0,
分析:scanf函数首先读取到的字符a,和首个格式符%d进行匹配,结果不匹配直接返回。而没有给变量c赋值
*/
</strong>


<strong>// 修改为多个scanf,这个时候就会有缓存引起问题

   int age;
    char c;
    scanf("%d", &age);
    scanf("%c",&c);
    printf("%d,%c", age, c);
/*
输入:a回车
输出: 0,a
分析:第一个sanf函数读取stdin数据,首先读到字符a,和其格式符号%d匹配,结果不匹配,直接返回,结束第一个scanf函数。但是字符”a“依然存在在stdin缓存中,当第二scanf执行的时候,stdin中有数据,scanf会直接执行,读取数据和%c匹配,满足匹配,把字母a赋值给了变量a(下面详解)
*/
</strong>

2》多个scanf函数,存在stdin缓存问题(会把stdin里面残留的数据和后面类型匹配并赋值)——》scanf("%*[^\n]");和scanf(“%*[^\n]%*c”);的使用和区别  

          注意看下面的错误情况:


<strong>// 不安全的写法,注意容易出错的地方
#include <stdio.h>
int main(int argc, const char * argv[]){
    
    int age = 1;
    char name[10];
    
    scanf("%d", &age);
    scanf("%s", name);
    
    printf(“age =%d,name=%s", age, name);
    return 0;
}/*
输入:abce回车
输出:age=1,name=abce
分析:
第一个scanf函数在执行的时候,从stdin中读取的数据是字符串,和%d类型不匹配,直接返回,结束函数
第二个scanf函数执行的是,查询到stdin 中有缓存的数据,会自动的执行(而不是等我们再次输出然后回车操作),读取到字符串abce和其类型符%s匹配,把从stdin中读取的字符串赋值给name,并在stdin缓存中清除读取到到内容

改错:我们只要在第一个scanf执行完毕后,清空在stdin中的缓存即可,两种方式
1、fflush(stdin)——》存在移植性问题,在vc6中有效。但是在xcode5中无效,不建议使用
2、使用自己编写的代码清空缓存
       scanf(“%*[^\n]"); 清除一段字符串
   scanf(“%*[^\n]%*c”);清除一段字符串和其后面的空白符号——》用在后面是为%c或字符集赋值的操作(它们把空白字符当作内容处理)*/</strong>


修改为如下正确写法:

<strong>//去除缓存的正确写法
#include <stdio.h>
int main(int argc, const char * argv[]){
    int age ;
    char name[10];
    
    scanf("%d", &age);
    scanf("%*[^\n]");
    scanf("%s", name);
    
    printf("%d,%s", age, name);

    return 0;
}
/*
输入:abc(回车)
           def(回车)

输出:0,def
分析,前的abc已经被scanf(“%*[^\n]”);,把stdin中的abc给清除了,没了影响了。但是这个时候stdin中还有abc输入完后的按的回车,在stdin中会出现对应的’\n“符号。但是%s默认以它为空白符号,不当作数据处理,所以无影响。但是如果后面是%c或字符集、%[],就必须在消除字符串后,再消除回车符号,使用scanf(“%*[^\n]%*c”);
*/

</strong>


  scanf(“%*[^\n]%*c”); 清除回车的情况:

<strong>//清除回车符号的重要性
#include <stdio.h>
int main(int argc, const char * argv[]){
    int age ;
    char c;
    
    scanf("%d", &age);
    scanf("%*[^\n]");
    scanf("%c", &c);
    
    printf(“<%d,%c>, age, c);

    return 0;
}
/*
输入:abc (回车)
输出:<0,
     >
不能为c输入数据的,出错分析:
第一个scanf,读取的字符串和%d不匹配直接返回,字符串依然存在再stdin中
然后第二个scanf函数,读取出了前面一段字符串,但不赋值,消除了abc字符串的缓存影响。但是再stdin中依然存在回车符号\n,
    第三个scanf执行,stdin里有数据\n,读取并赋值,把\n赋值给c了

正确分析:
把第二个scanf修改为
    scanf(“%*[^\n]%*c”);,就能为c输入字符数据
输入:abc (回车)
     d
输出:<0,d>

注意:我们每击打一下"Enter"键,向缓冲区发去一个“回车”(/r),一个“换行"(/n),
*/
</strong>



习题一:避免因错误输入造成的死循环 

<strong>// 注意错误原因
#include <stdio.h>
int main()
{
     int a;
     int b;
     do{
        printf("请输入数字:\n");
        scanf("%d",&a);
        scanf("%d",&b);
        printf("a=%d, b=%d\n",a,b);
    }while(b!=11);
    printf("结束输入");
}
/*
输入:a (回车)
结果:死循环

分析:
stdin中一直有缓存数据a,导致scanf会直接读取并赋值操作,但是数据一直不匹配,所以a又一直存在在stdin的缓存中,导致死循环
*/
</strong>


<strong>// 修改后的正确版本
#include <stdio.h>
int main()
{
     int a;
     int b;
     do{
        printf("请输入数字:\n");
        scanf("%d",&a);
        scanf("%*[^\n]");
        scanf("%d",&b);
        scanf("%*[^\n]");
        printf("a=%d, b=%d\n",a,b);
    }while(b!=11);
    printf("结束输入");
}
/*
分析:
scanf(“%*[^\n]”);会把前一个stdin中的不匹配的除了\n以外的数据都清除了。且%d不会把空格符号作为数据读取和赋值
*/
</strong>


习题二、无法正确的输入字符数据情况(比例题2更多的判断,跟加安全)  

<strong>// 不安全的写法
#include <stdio.h>
int main()
{
     int a;
     char b;
     do{
        printf("请输入:\n");
        scanf("%d",&a);
        scanf("%c",&b);
        printf("<a=%d, b=%c,b=%d>\n",a,b,b);
   }while(b!=’n');

    printf("结束输入");
}
/*
输入:11 (回车)
输出:<a=11, b=
,b=10>
           请输入数字:

分析:回车赋值给b了

正确输入方式:(很容易输入出错)
输入:11c
回车:<a=11, b=c,b=99>
    
*/
<pre name="code" class="csharp">// 正确写法( scanf("%*c");;)
#include <stdio.h>
int main()
{
     int a;
     char b;
     do{
        printf("请输入:\n");
        int num = scanf("%d",&a);
         
         if(1 == num ){
             scanf("%*[ \t\n]”);
      //scanf(" “); 不安全
         }else{
           scanf("%*[^\n]%*c");
         }
         
        scanf("%c",&b);
        scanf("%*[^\n]%*c");
        printf("<a=%d, b=%c,b=%d>\n",a,b,b);
    }while(b!='n');
    printf("结束输入");
}
/*
输入:11 (回车)
c
或:aa (回车)
    c
或 11 (tab)(回车)
   c
或 11 (空格)(tab)c
结果都会为,b赋值为c
 */

</strong>

习题三:使用 getchar() 清除所有缓存    

<strong>int main(){
    int i, c;
    
    for (;;) { // 无限循环
        // 表示一直的数据输入,不管是否匹配,stdin缓存区都会有数据残留
        if ( scanf("%d", &i) != EOF ) { //如果不按ctrl +d就会一直输入数据
            // 循环如果c不是\n或文件最后,就一直使用getchar读取除缓冲区
            while ( (c=getchar()) != '\n' && c != EOF );
        }
        printf("%d\n", i);
    }
    return 0;
}
</strong>



习题四、用“空格符”来处理缓冲区残余信息  

<strong>// 不安全写法
#include <stdio.h>
int main()
{
    char str[4];
    
    for(int i=0; i<5; i++){
    
        scanf("%c",&str[i]);
    }
    printf("%s",str);
    
    return 0;
}
/*
输入: a b c d e(回车)
输出: a b c
或
输入:a(回车)
b(回车)
c(回车)
输出:a
b
c
*/
</strong>

<strong>// 正确写法
#include <stdio.h>
int main()
{
    char str[4];
    
    for(int i=0; i<5; i++){
    
        scanf(" %c",&str[i]);
    }
    
    printf("%s",str);
    
    return 0;
}
/*
分析
 scanf(" %c",&str[i]);会把所以的空格符号都给读取出来,清空stdin的缓存
 和:scanf("%*[ \t\n]”);效果一样

输入:a b c d e(回车)
输出;abcde
*/
</strong>

(2)、多个%d赋值中,回车的不同情况(输入非法数据,回车立刻结束输入) 

<strong>//多个%d赋值,输入数据的时候,回车符号的特殊情况
    int age;
    int age1;
    int age2;
    char c;
    scanf("%d%d%c", &age1, &age2,&c);
    
    printf(“%d,%d,%c", age1, age2,c);
// 或
    int age1;
    int age2;
    scanf("%d", &age1);
    scanf("%d", &age2);

/*
分析:
如果是匹配的数据,回车、空格、tab键都会当作间隔符号使用(多个%d的情况下)
输入:11回车          或输入 11 12 c
  12回车
输出:11,12             输出:11,12      
分析:这里因为是匹配数据,所以回车可以当作间隔符号使用,但是这里注意char类型接收的类型中包括所有空白符号,所以char变量c不是没有输出,而是输出的是“\n”(注意理解)


如果输入的是不匹配数据,空格和tab还是可以输入,而回车会直接导致执行完毕,而不再是间隔符(多个%d的情况下)
输入:a □11□□□□ c回车
输出:1,2,
分析:输入的时候采用空格和tab键为间隔符合,当我们认为输入完三个数据的时候,按回车,结束输出。执行scanf函数读取并赋值。但是读取的第一个数据是a,而类型符是%d不匹配直接返回,结束scanf函数
输入:a回车
输出:1,2,
分析:如果我们输入的是不匹配的数据,如果按回车,会直接执行scanf完毕。而不再以回车键为分隔符合,然后输入另外的数据。和上面的第一种情况注意区分

*/
</strong>



8、经典代码总结——》在实现功能的基础上,精简代码、保证数据安全性


 (1)、接收用户输入两个数,并计算它们的和值

(这个功能很好实现,难点在于如何保证用户如果输入不正确的数据,程序能够判断出并给出相应的处理1、不输出错误数据,2、保证继续循环告诉用户输入数据,而不是只执行一次,3、保证不死循环)  


<strong>#include <stdio.h>
int main()
{
    int a,b; // 接收数据的变量
    int count; // scanf返回的成功项
    char isErro = 'N'; //提示语音的判断
    while(count != 2){
        
        if('N' ==isErro){
            printf("请输入两个数(使用逗号隔开)\n");
        }else if('Y' == isErro){
            printf("输入错误数据,请重新输入两个数(使用逗号隔开)\n");
        }

        count = scanf("%d,%d", &a, &b);
        scanf("%*[^\n]%*c"); // 清除stdin中的缓存数据(如果是给%c和%[]赋值,需要更多的判断)

        if(2 == count){ //如果两个变量数赋值成功
            int sum = a+b;
            printf("和是:%d\n",sum);
        }else{
            isErro = 'Y';
        }
    }
    return 0;
}
</strong>


<strong>// 精简的代码
#include <stdio.h>
int main()
{
        int a,b,c; /*计算a+b*/
        while(scanf("%d,%d",&a,&b)!=2)scanf("%*[^\n]%*c");
        c=a+b;
        printf("%d+%d=%d",a,b,c);
                                                           
    return 0;
}
</strong>




补充:

对EOF的基本认识

while(scanf(“%d”,&n)!=EOF));

标题那段代码的意思是,输入Ctrl+z终止循环(这是在Windows下,在Unix环境下是Ctrl+d)。如果你输入字符a,而循环体里又没有getchar之类读字符的函数,就会死循环,因为a会一直留在输入缓冲区中。要想在输入错误的情况下终止把 !=EOF 去掉就行了,即成功输入的个数为0的情况下推出循环。



抱歉!评论已关闭.