none
关于scanf函数处理输入的问题 RRS feed

  • 问题

  • 我写了一段c程序,用scanf读入多个整数进行操作:
    	while(scanf("%d", &a) != EOF)
    	{
                  ;
    	}
    但是如果我输入的是“1000 50 ^Z”的话,程序陷入死循环,scanf会一直返回 1;

    如果我输入“1000 50”然后回车,再输入"^Z",程序会按设计终止。

    这二者有什么区别吗? 按理来说scanf会过滤掉 回车,换行,空格,二者应该没什么区别啊?
    2010年1月13日 2:29

答案

  • 我们来看一下scanf 的源代码:

    int __cdecl scanf (
            const char *format,
            ...
            )
    {
            va_list arglist;
            va_start(arglist, format);
            return vscanf(_input_l, format, NULL, arglist);
    }
    
    
    
    int __cdecl vscanf (
            INPUTFN inputfn,
            const char *format,
            _locale_t plocinfo,
            va_list arglist
            )
    /*
     * stdin 'SCAN', 'F'ormatted
     */
    {
            int retval;
    
            _VALIDATE_RETURN( (format != NULL), EINVAL, EOF);
    
            _lock_str2(0, stdin);
            __try {
    
            retval = (inputfn(stdin, format, plocinfo, arglist));
    
            }
            __finally {
                _unlock_str2(0, stdin);
            }
    
            return(retval);
    }
    
    
    
    
    int __cdecl _input_l(_Inout_ FILE * _File, _In_z_ __format_string const unsigned char *, _In_opt_ _locale_t _Locale, va_list _ArgList);
    
    
    由上面代码可知scanf 只不过是一张皮,scanf 一个族系的函数都是通过实际调用 vscanf 来实现功能的,而vscanf 又是通过调用 _input_l 函数来实现功能的。 我们知道windows 的IO 是通过文件技术实现的,不管是输入设备还是输出设备,操作系统都将它抽象成一个文件,通过对文件的读写实现对数据的输入和输出。  通过查询stdin的定义:
    #define stdin  (&__iob_func()[0])
    _CRTIMP FILE * __cdecl __iob_func(void);

    我们可以知道,_input_l 函数正是将键盘输入写入了stdin 创建一个临时的交换文件的缓冲区的。

    说道读取文件,你上面的问题就好揭示了。 第一、"1000 50 ^z" 是一个整体,是一个有效的字符串,其本身并不含有文件结尾的含义。因为你不能说“这个人不讲礼貌,分手了也不说再见”,这句话和“再见” 这个词的含义是一样的。
     第二、单独的^Z 可被认作文件的结尾也就是你判断条件中的EOF。 因此,单独输入^Z 是可以结束循环的。
    • 已标记为答案 Ryen.Lee 2010年1月13日 8:11
    2010年1月13日 5:33
    版主

全部回复

  • 我们来看一下scanf 的源代码:

    int __cdecl scanf (
            const char *format,
            ...
            )
    {
            va_list arglist;
            va_start(arglist, format);
            return vscanf(_input_l, format, NULL, arglist);
    }
    
    
    
    int __cdecl vscanf (
            INPUTFN inputfn,
            const char *format,
            _locale_t plocinfo,
            va_list arglist
            )
    /*
     * stdin 'SCAN', 'F'ormatted
     */
    {
            int retval;
    
            _VALIDATE_RETURN( (format != NULL), EINVAL, EOF);
    
            _lock_str2(0, stdin);
            __try {
    
            retval = (inputfn(stdin, format, plocinfo, arglist));
    
            }
            __finally {
                _unlock_str2(0, stdin);
            }
    
            return(retval);
    }
    
    
    
    
    int __cdecl _input_l(_Inout_ FILE * _File, _In_z_ __format_string const unsigned char *, _In_opt_ _locale_t _Locale, va_list _ArgList);
    
    
    由上面代码可知scanf 只不过是一张皮,scanf 一个族系的函数都是通过实际调用 vscanf 来实现功能的,而vscanf 又是通过调用 _input_l 函数来实现功能的。 我们知道windows 的IO 是通过文件技术实现的,不管是输入设备还是输出设备,操作系统都将它抽象成一个文件,通过对文件的读写实现对数据的输入和输出。  通过查询stdin的定义:
    #define stdin  (&__iob_func()[0])
    _CRTIMP FILE * __cdecl __iob_func(void);

    我们可以知道,_input_l 函数正是将键盘输入写入了stdin 创建一个临时的交换文件的缓冲区的。

    说道读取文件,你上面的问题就好揭示了。 第一、"1000 50 ^z" 是一个整体,是一个有效的字符串,其本身并不含有文件结尾的含义。因为你不能说“这个人不讲礼貌,分手了也不说再见”,这句话和“再见” 这个词的含义是一样的。
     第二、单独的^Z 可被认作文件的结尾也就是你判断条件中的EOF。 因此,单独输入^Z 是可以结束循环的。
    • 已标记为答案 Ryen.Lee 2010年1月13日 8:11
    2010年1月13日 5:33
    版主
  • 感谢您的解答。 看C运行库的实现实在是头大啊。MS把C运行库开源了吗?

    我还有点小困惑,如果从文件的观点来理解,对于一般的文本文件来说,系统是如何判断EOF的呢? 以前我一直以为是系统自动在文件末加了一个EOF 结束符。看来应该不是这么简单,
    EOF不能当成普通的字符来理解。

    我现在的理解是, 对于scanf函数来说, 只有在EOF结束符处于输入缓冲区的当前首个字符, 这次scanf调用才能返回EOF。应该和你的意思是一样的。
    (我试验了下 输入"^Z 20 30",程序返回;输入"      ^Z" 死循环)
    2010年1月13日 8:11
  • 在文件中eof之前有文件结束标志0xff。而并不是文件中最后一个有效字符后面直接跟eof。这也是为什么用eof判断结束文件会出问题。
    你可以看一下这个thread
    http://social.msdn.microsoft.com/Forums/en/vclanguage/thread/7c4330b2-5a0f-4298-81c9-4d1b146e7a4d

    麻烦把正确答案设为解答。
    2010年1月14日 1:53
    版主
  • 依照你的第一个范例中的输入^z, 这个把你当作是一个回车, 然而如果你在这个输入后面再加一个^z, 就会跳出循环 i.e “1000 50 ^Z^Z”

    2012年2月28日 2:24