【汇总】scanf、getchar、getch、getche、getc、fgetc、gets、fgets、getline

时间:2021-01-01 01:51:59

结论:最好用fgets()函数输入字符串,getchar()输入字符。


1.  scanf()函数stdio.h

    原型:int scanf(char *format,...]);

    头文件:stdio.h

    参数:format 用 restrict 修饰。format 指向的控制串由以下三类字符组成:

              格式说明符:%加转换字符;%后加入*,表示读入的数据不赋值给任何变量,不需要为此格式符指定地址参                                    数;%[] 表示扫描[ ]中的字符集合,可以指定要扫描的字符或者用补集符号^指定不扫描的字                                    符,用连字符 - 可以说明一个范围,当扫描到第一个非范围内字符时停止此次scanf

              空白符:空白字符会使scanf()函数在读操作中略去输入中的一个或多个空白字符。空白符可以是空格                                      (space)、制表符(tab)和新行符(newline)。

              非空白符:一个非空白字符会使 scanf() 函数在读入时剔除掉与这个非空白字符相同的字符。


    返回值:scanf() 函数返回成功赋值的数据项的个数,读到文件末尾或者出错时则返回EOF(0xff = -1)。


    程序的输入都建有一个输入缓冲区,即stdin缓冲区。一次输入过程是这样的,当一次键盘输入结束时会将输入的数据存入输入缓冲区,scanf() 是从输入缓冲区中获取数据,获取数据时如果遇到回车、空格、TAB或非法数据则会停止,认为一次获取数据过程结束,第二个 scanf() 会继续从输入缓冲区获取数据。所以有时候当缓冲区中有残留数据时,scanf() 函数会直接取得这些残留数据而不会请求键盘输入。

      scanf()读取的换行符被转换为null值,做为字符数组的最后一个字符,来结束字符串。输入类型与格式化字符串不匹配导致stdin流的阻塞。

    因此,当scanf函数出现行为异常时,可以试试清除stdin缓冲区内的残留数据
        (1)利用函数fflush(stdin) 
        注意:C标准规定 fflush()函数是用来刷新输出(stdout)缓存的。对于输入(stdin),它是没有定义的,                         ANSI C中并没有将这个函数作为标准的一部分,并不是所有编译器都支持这个函数。所以,这样并不具                     备可移植性。 

        (2)手动取出缓冲区里的残留数据 
        scanf("%[^\n]",str); 或者while((c=getchar())!='\n'&&c!=EOF); 但是这种方法只能用于缓冲区非空的情况。


    使用scanf函数进行输入,必须指定输入的数据的类型和格式,不仅繁琐复杂,而且很容易出错.C++保留scanf只是为了和C兼容,以便过去用C语言写的程序可以在C++的环境下运行。C++的编程人员都愿意使用cin进行输入,很少使用scanf



2.  getchar()函数

    原型:int getchar(void);

    头文件:stdio.h

    返回值:getchar是一个宏,它定义为getc(stdin),有一个int型的返回值,用于返回stdin输入流的下一个字符的ASCII码,如出错返回-1,且将用户输入的字符回显到屏幕。


    getchar()函数的执行采用了行缓冲。程序调用getchar时,程序就等着用户按键,用户输入的字符被存放在输入缓冲区中,直到用户按回车为止(回车字符也放在缓冲区中,会被下个getchar()读取)。当用户键入回车之后,getchar才开始从stdin流中每次读入一个字符。如果用户在按回车之前输入了不止一个字符,其他字符会保留在输入缓存区中,等待后续getchar调用读取。也就是说,后续的getchar调用不会等待用户按键,而直接读取缓冲区中的字符,直到缓冲区中的字符读完为后,才重新等待用户按键。


    通过键盘按下的回车(ENTER)键,会产生2个字符,分别是回车符 '\r' 和换行符 '\n'回车符'/r'(CR:carriage return:倒车)使光标回到这行的首部,然后换行符'/n'(new line)使光标再换行。对于getchar函数来说,按下回车键以后,首先得到回车符,那个getchar函数结束了, 但是还存在一个换行符,可以被后面的输入函数获取。所以如果用getchar()来做判断的时候,最好再写一次getchar()清除缓冲区的'/n'。



3.  getch()函数和getche()函数

    原型:int getch(void) 和 int getche(void)

    头文件:conio.h,conio.h不是C标准库中的头文件。Micorsoft 和 Borland的 C编译器提供了conio.h,用来创建控制台文本用户界面。一般在Windows系统下安装了VS、VC等,就可以包含conio.h头文件。但是一般在Unix、Linux系统中,/usr/include/中都没有这个头文件。

    返回值:用户输入的ASCII码,出错返回-1。


    getch与getchar基本功能相同,差别是getch不是从输入缓冲区而是直接从键盘获取数据,输入一个字符,立刻返回一个字符,不需要按回车 。输入的字符不会回显在屏幕上。getch()并非标准C中的函数,不存在C语言中。所以在使用的时候要注意程序的可移植性。国内C语言新手常常使用getch()来暂停程序且不知道此函数来源,建议使用getchar()。


    在Linux中,终端输入在缺省情况下是被“一锅端”的,也就是说整行输入是被一起处理的。通常,这是一种人们所希望的方便的办法,但它也意味着在读入数据时必须按一下回车键表示输入行结束后才能得到输入的数据。在游戏中,很多都提供了“老板键”,它的实现,就是利用了这两个函数。


    getche()和getch()很相似,它也需要引入头文件conio.h,它们之间的区别就在于:getch()无回显,getche()有回显



4.getc()函数和fgetc()函数

    原型:int getc(FILE *stream); 

              int fgetc ( FILE * stream );

    返回值:返回指定输入流stream的当前位置的下一个字符,并增加文件的位置指示器。

    这两个函数被ISO C声明为一个宏,所以在用时不能将其做为函数指针传(有一些编译器将其以函数形式也给另说)。它的原型如下:#define getc(_stream) (--(_stream)->_cnt >= 0?0xff & *(_stream)->_ptr++ : _fillbuf(_stream))

    要看懂上面的声明,标记一下FILE的定义
    #ifndef _FILE_DEFINED
    struct _iobuf {
        char *_ptr; //下一字符的位置
        int _cnt; //剩余的字符数
        char *_base; //缓冲区的位置
        int _flag; //文件的访问模式
        int _file;
        int _charbuf;
        int _bufsiz;
        char *_tmpfname;
    };
    typedef struct _iobuf FILE;
    #define _FILE_DEFINED
    #endif


    getc函数从文件指针stream指向的文件中读取一个字符,读取一个字节后,指针位置后移一个字节。用户通过键盘输入以后,系统会在程序里的输入缓冲区放一些数据,然后getc通过特定指针(stdin)直接读取数据,ungetc可以往里面放数据。此外,还可以用文件指针做参数从文件中读数据。

    getc、fgetc用法相同。



5.gets()函数

    原型:char * gets(char * buffer);

    参数:将读取的结果存放在buffer指针所指向的字符数组中

    头文件:stdio.h

    返回值:读入成功,返回与参数buffer相同的指针;读入过程中遇到EOF(End-of-File)或发生错误,返回NULL指针。所以在遇到返回值为NULL的情况,要用ferror或feof函数检查是发生错误还是遇到EOF。


    从stdin流中读取字符串,直至接受到换行符或EOF时停止,并将读取的结果存放在buffer指针所指向的字符数组中。gets()能够接受空格、制表符TAB,并不会因为字符串中有空格或者TAB而结束字符的获取,但和scanf()一样,读取的换行符被转换为null值,做为字符数组的最后一个字符,来结束字符串。


    本函数可以无限读取,不会判断上限,所以程序员应该确保buffer的空间足够大,以便在执行读操作时不发生溢出。如果溢出,多出来的字符将被写入到堆栈中,这就覆盖了堆栈原先的内容,破坏一个或多个不相关变量的值,这个事实导致gets函数只适用于玩具程序,为了避免这种情况,我们可以用fgets()来替换gets()



6.  fgets()函数


    原型:char *  fgets(char * s, int n, FILE *stream);

    头文件:stdio.h

    参数:
         s: 字符型指针,指向存储读入数据的缓冲区的地址。
         n: 从流中读入n-1个字符,第n个写入'\0'表示字符串结束。
         stream : 指向读取的流。
   返回值:
          1. 当n<=0 时返回NULL,即空指针。
          2. 当n=1 时,返回空串""。
          3. 如果读入成功,则返回缓冲区的地址。
          4. 如果读入错误或遇到文件结尾(EOF),则返回NULL。

          看看这个函数的官方说明:

                       /*** 
                    *char *fgets(string, count, stream) - input string from a stream 
                    * 
                    *Purpose:  
                    * get a string, up to count-1 chars or '\n', whichever comes first, 
                    * append '\0' and put the whole thing into string. the '\n' is included 
                    * in the string. if count<=1 no input is requested. if EOF is found 
                    * immediately, return NULL. if EOF found after chars read, let EOF 
                    * finish the string as '\n' would. 
                    * 
                    *Entry: 
                    * char *string - pointer to place to store string 
                    * int count - max characters to place at string (include \0) 
                    * FILE *stream - stream to read from 
                    * 
                    *Exit: 
                    * returns string with text read from file in it. 
                    * if count <= 0 return NULL 
                    * if count == 1 put null string in string 
                    * returns NULL if error or end-of-file found immediately 
                    * 
                    *Exceptions: 
                    * 
                    *******************************************************************************/ 


     在用fgets(..)读入数据时,先定义一个字符数组或字符指针,如果定义了字符指针 ,那么一定要初始化。


        example:
              char s[100]; //可以。
              char *s;  //不可以,因为只是声明了一个指针。但并没有为它分配内存缓冲区。

    所以,如果要用指针,则  char *s=(char *)malloc(100*sizeof(char)); 为其分配内存空间,c++中用char *s=new char [100];如果未分配内存空间,编译时不会检查出问题,但运行时会出现未知错误。


    fgets(...)读入文本行时的两种情况。

    如果n大于一行的字符串长度,那么当读到字符串末尾的换行符时,fgets(..)会返回,换行符也保存,并且在s的最后插入字符串结束标志'\0', 而s缓冲区剩余的位置不会再填充。

    如果n小于等于一行的字符串的长度,那么读入n-1个字符,此时并没有读入\n因为并没有到行尾 ,同样在最后会插入'\0'。fgets若没遇到换行符,会接着从前一次的位置继续读入n-1个字符,只要文本流没关闭。



    fgets(...)读入整个文件内容,通常用while()循环来使fges()读入文本全部内容,并按行读入。


    char s[1024];

    while((fgets(s,1024,fp))!=NULL)

    {

        printf(s);

    }

     当然如果n小于每行的字符个数,也可以读,只不过读的次数要多。

    假设一行为 : 123456789

    char s[2];

    int  num=0;

    while((fgets(s,2,fp))!=NULL)//注意每次读一个字符,则第二个参数应为2

    {

        printf(s);

        n++;

    }

    每次读入一个字符, 最后也会读完一行,num=10,读了十次。所以,fgets若没遇到换行符,会接着从前一次的位置继续读入n-1个字符,只要是文本流没关闭。


    读入空行的情况

    第一行   abcdef123

    第二行

    第三行  helloworld

    其中第二行为空,fget(..)会把第二行也读入,因为并未到文件结尾。

    有时我们并不需要空行,可以这样做。

    while((fgets(s,n,fp))!=NULL)

    {

        if(strlen(s)!=1)    //注意这儿是1不是0,因为尽管是空行,它也会读入换行符,strlen(s)=1;

        printf(s);

    }


    用fgets(...)还也读入标准输入设备(一般为键盘)的信息
    原型:fgets(s,n,stdin);


    假设在控制台下,我们可以用fgets(...)替代gets(),读入键盘输入的信息,fgets()是安全的,因为不会像gets()有溢出的可能,但是是从stdin流输入缓冲读入。



7.getline()函数

    标准C语言中没有getline()函数,C++中才有。在gcc编译器中,对标准库进行了扩展,加入了一个getline函数。

    linux标准C中使用条件:
        #define _GNU_SOURCE
   #include <stdio.h>
    原型:ssize_t getline(char **lineptr, size_t *n, FILE *stream);
    参数:lineptr:指向一个动态分配的内存区域(不能是静态分配的数组),用于存放该行字符,如果是NULL,                                 getline函数会自动进行动态内存的分配(忽略*n的大小),所以使用这个函数非常注意的就使用                               要注意自己进行内存的释放。如果*lineptr分配了内存,但在使用过程中发现所分配的内存不足的                             话,getline函数会调用realloc函数来重新进行内存的分配,同时更新*lineptr和*n。
              n:用于存放字符的所分配内存的长度,如果是由系统malloc的指针,请填0
              stream:文件描述符

    返回值:成功,返回读取的字节数;失败,返回-1。


    getline函数读入的一行是包括最后的换行符的。


    C++中getline本身有两种形式:
    (1)istream& cin.getline (char* s, streamsize n, char delim) 是输入流对象的成员函数,即istream::getline,使用时需头文件#include <iostream>
    (2)istream& getline ( istream& is, string& str, char delim) 是一个在std名字空间的全局函数,因为这个getline函数的参数使用了string字符串,所以声明在了<string>头文件中了,使用时需头文件#include <string>


    cin.getline(char* s, streamsize n, char delim)是cin 的一个成员函数,定义在<iostream>中,用于向 s 指定内存输入指定长度 n 的字符串,输入长度超出 n 或者遇到参数 delim 指定的界定符(不指定delim参数时,默认为 '\n' ),输入结束。


    getline(istream& is, string& str, char delim)是个全局函数,而不是istream/iostream的成员函数getline。getline操作返回的是istream(输入流),第一个参数 is 要求是一个
istream类的输入流对象的引用,即istream&,第二个参数 str 则是一个string类的对象。一般的用法是 getline(cin, str), 这里cin就是C++内部定义的一个全局的输入流对象,即extern istream cin,str是你定义的string类对象。


    在C++中读取一行的函数getline和cin.getline是不读入换行符的,而GCC中getline函数是读入换行符的。可以理解为,一般情况下不读入,特别的是GCC的读入。



8.

另外,绝大多数的这些get函数,都有对应的put版本。


    int fputc ( int character, FILE * stream );


    int putc ( int character, FILE * stream );       //通过宏定义和fputc实现


    int putchar ( int character );        //通过宏定义实现:#define putchar(c) fputc(c, stdout)


    int fputs ( const char * str, FILE * stream );


    int puts ( const char * str );

    说明:两者之间无宏定义实现关系。puts(const char *str)近似等效于fputs(cosnt char *str, stdout),不同点是前者还输出一个'/n'。