您的位置:首页 > 理论基础 > 数据结构算法

转自dream的专栏:scanf与回车的冲突 (其实是字符型数据在输入输出时的特性,getchar()也是这样)

2007-06-21 14:58 302 查看
scanf与回车的冲突
#include <stdio.h>   

main()   

{   

    int a, b;   

    float x, y;   

    char c1, c2;   

    scanf("a=%d b=%d", &a, &b);   

    scanf("%f %e", &x, &y);    

    scanf("%c %c", &c1, &c2);        

    printf("a=%d, b=%d, x=%f, y=%f, c1=%c, c2=%c/n", a, b, x, y, c1, c2);   

}   

运行结果
a=1 b=2↙
1.1 2.2↙
m n↙
a=1,b=2,x=1.100000,y=2.200000,c1=
,c2=m

★ 原因是输入完第二行数据后回车被当作一个字符读入了内存输入缓冲区,所以c1读入了回车符

★ 解决方法:

<方法一> 在读字符函数中首位置加入一个空格

view plaincopy to clipboardprint?

#include <stdio.h>   

main()   

{   

    int a, b;   

    float x, y;   

    char c1, c2;   

    scanf("a=%d b=%d", &a, &b);   

    scanf("%f %e", &x, &y);    

    scanf("%c %c", &c1, &c2);          /*第一个%c前加入一个空格*/       

    printf("a=%d, b=%d, x=%f, y=%f, c1=%c, c2=%c/n", a, b, x, y, c1, c2);   

}    

运行结果
a=1 b=2↙
1.1 2.2↙
m n↙
a = 1, b = 2, x = 1.100000, y = 2.200000, c1 = m, c2 = n

所加入的空格使第二行尾输入的回车符与该空格符对应,第三行输入的第一个字符就被c1读入了

<方法二> 在第三个函数前加一个getchar函数(包含在stdio.h中)  getchar()"吃掉"前面的回车符.

view plaincopy to clipboardprint?

#include <stdio.h>   

main()   

{   

    int a, b;   

    float x, y;   

    char c1, c2;   

    scanf("a=%d b=%d", &a, &b);   

    scanf("%f %e", &x, &y);    

    getchar();            /* getchar() 吃掉前面的回车符 */  

    scanf("%c %c", &c1, &c2);     

    printf("a=%d, b=%d, x=%f, y=%f, c1=%c, c2=%c/n", a, b, x, y, c1, c2);   

}     

结果同上 
 

 

以上转自dream的专栏

在字符的输入时要注意回车等不可见字符也会被scanf,getchar等函数从缓冲区中取得。如果缓冲区里有一堆字符,后面如果有多个getchar(),会把缓冲区里的所有字符全部取光



#include <stdio.h>   
main()   
{     
    char c1, c2, c3, c4,c5;   
  
 printf("please input char/n/n");
 
 c1 = getchar();
 c2 = getchar();
 c3 = getchar();
 c4 = getchar();

 printf("/nc1 = %c, c2 = %c, c3 = %c, c4 = %c/n",c1,c2,c3,c4);

 c5 = getchar();

 printf("/nc5 = %c/n",c5);
 printf("/nasdfas");
}  

输入连续4个回车,输出

c1 =

, c2 =

, c3 =

, c4 =

编程时须注意

以下转自蚂蚁的 C/C++ 标准编程  antigloss的文章

C/C++ 误区二:fflush(stdin)

来源:蚂蚁的 C/C++ 标准编程 作者:antigloss

 

1.       为什么 fflush(stdin) 是错的
 
首先请看以下程序:
 
                   #include <stdio.h>
 
int main( void )
{
    int i;
    for (;;) {
        fputs("Please input an integer: ", stdout);

        scanf("%d", &i);
        printf("%d/n", i);
    }
    return 0;
}
 
这个程序首先会提示用户输入一个整数,然后等待用户输入,如果用户输入的是整数,程序会输出刚才输入的整数,并且再次提示用户输入一个整数,然后等待用户输入。但是一旦用户输入的不是整数(如小数或者字母),假设 scanf 函数最后一次得到的整数是 2 ,那么程序会不停地输出“Please input an integer: 2”。这是因为 scanf("%d", &i); 只能接受整数,如果用户输入了字母,则这个字母会遗留在“输入缓冲区”中。因为缓冲中有数据,故而 scanf 函数不会等待用户输入,直接就去缓冲中读取,可是缓冲中的却是字母,这个字母再次被遗留在缓冲中,如此反复,从而导致不停地输出“Please input an integer: 2”。
 
也许有人会说:“居然这样,那么在 scanf 函数后面加上‘fflush(stdin);’,把输入缓冲清空掉不就行了?”然而这是错的!CC++标准里从来没有定义过 fflush(stdin)。也许有人会说:“可是我用 fflush(stdin) 解决了这个问题,你怎么能说是错的呢?”的确,某些编译器(如VC6)支持用 fflush(stdin) 来清空输入缓冲,但是并非所有编译器都要支持这个功能(linux 下的 gcc 就不支持),因为标准中根本没有定义 fflush(stdin)。MSDN 文档里也清楚地写着fflush on input stream is an extension to the C standard(fflush 操作输入流是对 C 标准的扩充)。当然,如果你毫不在乎程序的移植性,用 fflush(stdin) 也没什么大问题。以下是 C99 fflush 函数的定义:
 
int fflush(FILE *stream);

 
如果 stream 指向输出流或者更新流(update stream),并且这个更新流
最近执行的操作不是输入,那么 fflush 函数将把这个流中任何待写数据传送至
宿主环境(host environment)写入文件。否则,它的行为是未定义的。

原文如下:

int fflush(FILE *stream);

If stream points to an output stream or an update stream in which
the most recent operation was not input, the fflush function causes
any unwritten data for that stream to be delivered to the host environment
to be written to the file; otherwise, the behavior is undefined.

 
其中,宿主环境可以理解为操作系统或内核等。
 
    由此可知,如果 stream 指向输入流(如 stdin),那么 fflush 函数的行为是不确定的。故而使用 fflush(stdin)  是不正确的,至少是移植性不好的。
 

 
2.       清空输入缓冲区的方法
 
 虽然不可以用 fflush(stdin),但是我们可以自己写代码来清空输入缓冲区。只需要在 scanf 函数后面加上几句简单的代码就可以了。

        /* C 版本 */
        #include <stdio.h> 

        int main( void )
        {
            int i, c;
              for ( ; ; )
            {
                fputs("Please input an integer: ", stdout);
                scanf("%d", &i);
             if ( feof(stdin) || ferror(stdin) )
                { /* 如果用户输入文件结束标志(或文件已被读完), */
                  /* 或者发生读写错误,则退出循环               */
           
                    /* do something */
                    break;
                }
                /* 没有发生错误,清空输入流。                 */
                /* 通过 while 循环把输入流中的余留数据“吃”掉 */
                while ( (c = getchar()) != '/n' && c != EOF ) ;
                /* 使用 scanf("%*[^/n]"); 也可以清空输入流, */
               /* 不过会残留 /n 字符。                          */
               printf("%d/n", i);
            }

             return 0;
        }

        /* C++ 版本 */
        #include <iostream>
        #include <limits> // 为了使用numeric_limits
 
     using std::cout;
        using std::endl;
        using std::cin;
        using std::numeric_limits;
        using std::streamsize;
 
     int main()
        {
            int value;
            for ( ; ; )
            {
                cout << "Enter an integer: ";
                cin >> value;
                if ( cin.eof() || cin.bad() )
                { // 如果用户输入文件结束标志(或文件已被读完),
                  // 或者发生读写错误,则退出循环

                 // do something
                    break;
                }
                // 读到非法字符后,输入流将处于出错状态
                // 为了继续获取输入,首先要调用 clear 函数
                // 来清除输入流的错误标记,然后才能调用
                // ignore 函数来清除输入流中的数据。
                cin.clear();
                // numeric_limits<streamsize>::max() 返回输入缓冲的大小。
                // ignore 函数在此将把输入流中的数据清空。
                // 这两个函数的具体用法请读者自行查询。
                cin.ignore( numeric_limits<streamsize>::max(), '/n' );

                cout << value << '/n';
            }

         return 0;
        }

参考资料
ISO/IEC 9899:1999 (E) Programming languages— C 7.19.5.2 The fflush function

The C Programming Language 2nd Edition By Kernighan & Ritchie
ISO/IEC 14882(1998-9-01)Programming languages — C++

 我现在的问题是

按照这样说,当输入整数2后回车时,实际输入的是2和回车符,如果在scanf("%d", &i);后加入c = getchar();可以输出c是回车,也就是说,即使你输入的是整数,缓冲区中也有一个回车符,为什么这个回车符没有构成同样的影响呢

 

以下转自朱群英、孙云翻译的c A&Q

13.24 既然 fflush() 不能, 那么怎样才能清除输入呢? 这取决于你要做什么。如果你希望丢掉调用 scanf() (参见问题 12.16 - 12.17) 之后所剩下的换行符和未预知的输入, 你可能需要重写你的  scanf() 或者换掉它, 参见问题 12.18。或者你可以用下边这样的代码吃掉一行中多余的字符

while((c = getchar()) != '/n' && c != EOF)
/* 丢弃 */ ;

你也可以使用 curses 的 flushinp() 函数。

没有什么标准的办法可以丢弃标准输入流的未读取字符, 即使有, 那也不够, 因为未读取字符也可能来自其它的操作系统级的输入缓冲区。如果你希望严格丢弃多输入的字符 (可能是预测发出临界提示), 你可能需要使用系统相关的技术; 参加问题 19.1 和 19.2

 

 

还有点其它的

 

以下转自0602的专栏输入输出流为什么要用缓冲区?

这个问题我实在重读C++基本IO类的时候才认真考虑的(哎,当初上学时吃下去的东西,现在才开始销化)。我相信有很多人对这个问题一个非常清楚了,但是我也相信有很多人和我一样没有考虑过这个问题。现在我想把我的理解发表出来,欢迎大家批评。

    我想以一个例子说明,比如我想把一篇文章以字符序列的方式输出到计算机显示器屏幕上,那么我的程序内存作为数据源而显示器驱动程序作为数据目标,如果数据源直接对数据目标发送数据的话。数据目标获得第一个字符,便将它显示。然后从端口读取下一个字符,可是这时就不能保证数据源向端口发送的恰好是第二个字符(也许是第三个,而第二个已经在数据目标显示时发送过了)。这样的话就不能保证输出的数据能完整的被数据目标所接受并处理。
 
 
      为了解决这个问题,我们需要在数据源与数据目标中间放置一个保存完整数据内容的区域,那就是dangdangdangdang----“缓冲区”。这样的话, 数据源可以不考虑数据目标正在处理哪部分数据,只要把数据输出到缓冲区就可以了,数据目标也可以不考虑数据源的发送频率,只是从缓冲区中依次取出下一个数据。从而保证了数据发送的完整性,同时也提高了程序的效率。
 
以下转自wayne 我的陋室我做主c语言学习零碎整理(1):缓冲区

觉得学习一门语言有三个方面:一是语言本身的学习:语法,函数,特性等等;二是系统或计算机底层方面的了解:计算机的组成原理,操作系统额概念等等;三是算法和数据结构的学习.我想如果这三个方面学到融会贯通的话,就可以称为高手了吧.

我的整理中主要是第一和第二个方面,至于算法和数据结构,等到大三有时间的时候再去深入学习一下.(买了<算法导论>半年了,还没看多少,惭愧ing).

1,为什么要用缓冲区?
如:
  int c;
  FILE *istream,ostream;
  while( (c = getc(istream)) != EOF)
      putc(c,ostream);
getc和putc时以单个字节为单位的.
 而一次读一个字节,效率很低。大部分的效率提高是通过减少系统调用的次数得到的。当进行一个系统调用时,程序和内核之间模式切换的代价时相当大的,通常来说,如果需要获得非常高的效率,就要小心翼翼的减少程序中系统的调用次数。

2,什么是缓冲区?
标准I/O库针对这一情况实现了一个完美的缓冲区机制来避免效率的降低。首先,第一次getc调用将导致系统调用read将BUFSIZ个字符读入缓冲区(BUFSIZ是在<stdio.h>中定义的常量),该缓冲区是由标准I/O库创建的(缓冲区依然处于用户地址空间中)。但是getc只返回第一个字符,接着getc实际上是从缓冲区读入的字符。putc的机制一样。

3.关于getchar的缓冲区

举个例子吧.

#include<stdio.h>//原意是显示输入的两个字符
main()
{
int c;
c=getchar();
printf("%c/n", c);
c=getchar();
printf("%c/n", c);
return 0;
}
这个程序并不能得到正确的输出.因为getchar的缓冲区问题。
为了说明这一点,请看下面的程序。

#include<stdio.h>
main()
{
int c;
c=getchar();//这时,缓冲区中有c和回车符
printf("%c/n", c);//输出c
printf("%d/n",getchar()); // 这里将输出回车的ASCII的值,是10
c=getchar();
printf("%c/n", c);
printf("%d/n",getchar());//输出10

}
问题是在与:在输入一个字符后我们有敲了一个回车,问题出来了,在第一个程序中,第二个printf语句输出的不是是回车。

改为这样就可以了
#include<stdio.h>
main()
{
int c;
c=getchar();
printf("%c/n", c);
getchar(); //把回车符清掉
c=getchar();
printf("%c/n", c);
return 0;
}

这样做更可以说明问题,更好理解。
#include<stdio.h>
main()
{
int c;
c=getchar();
printf("%c/n",c);
c=getchar();
printf("%d/n",c);
printf("%c/n",getchar());
return 0;
}//对于这种问题,最好的方法是把输入的字符(包括回车符)写在纸上这样就容易出错。

4,linux里面的一个程序

学操作系统的时候,编了一个关于进程的小程序,不想也遇到了缓冲区的问题.

程序如下:

#include<stdio.h>
void createprocess(){
        int t;
        printf("123");
        t = fork();
        if(t != -1){
                if(t == 0)  printf("b");
                if(t > 0)    printf("a");
        }
}
                                                                               
int main(){
        createprocess();
}
结果是123a123b
为什么123会打印两次呢?
fork在打印123的语句下面啊. 

这就是我当初的疑问.后来再csdn上面发了个帖子问了一下,才明白,是缓冲区的问题.

printf出现在fork前,似乎应该出现一次,但由于没有换行符,或者用fflush将缓冲区内容输出,所以在fork时,“123”没有输出,还在缓冲区内,它的内容也被子进程所复制。父进程和子进程的缓冲区内,都有“123”。(谢谢 nichotilikai(lk) 的讲解.)

我是初学者,上面的内容也许会有错误或写的不清楚的地方,希望大家多多给我提意见,谢谢!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐