一文搞懂getchar()和putchar()的奇怪现象

1、首先来看一个例子:

#include <stdio.h>

int main( )
{
	int c;
	c = getchar();
	while (c != EOF){
		putchar();
		c= getchar();
	}
	return 0;
}

这里主要解释下为什么要用int型来接受getchar函数。
很多时候,我们会写这样的两行代码:

char c;
c = getchar();

这样就很有可能出现问题。因为getchar函数除了返回终端输入的字符外,在遇到Ctrl+D即文件结束符EOF时,getchar ()返回EOF。但这个EOF在函数库里一般定义为-1。因此,在这种情况下,getchar函数返回一个负值,把一个负值赋给一个char型的变量是不正确的。

2、getchar如何获取字符和输出字符的。

#include "stdio.h"

main()
{
	char c,d,e,f;
	printf("please input two characters:\n");
	c=getchar();
	putchar(c);
	putchar('\n');
	
	d=getchar();
	putchar(d);
	putchar('\n');
	
	e=getchar();
	putchar(e);
	putchar('\n');
	
	f=getchar();
	putchar(f);
	putchar('\n');
	
	printf("c= %c\n",c);
	printf("d= %c\n",d);
	printf("e= %c\n",e);
	printf("f= %c\n",f);
}

运行后先输入“12”,回车,再输入“34”,回车。

运行环境是redhat gcc

运行结果:

$: please input two characters:

12

1

2

34

3

c=1

d=2

e=

f=3

下面具体解释一下:

getchar函数每次从缓冲区中得到一个字符(包括换行符),putchar函数每次输出一个字符(包括换行符)。

首先输入了两个字符’1’和’2’,然后回车。注意这时写入缓存中的有3个字符:‘1’、‘2’、’\n’。

程序中有四个getchar(),于是c=‘1’,d=‘2’,e=’\n’。

这时运行到f=getchar();输入缓存中的三个字符均被前三个getchar获取,这时需要用户输入,

这里输入了34

于是f=‘3’,‘4’ 和后面的 ‘\n’ 没有被利用。

这便是整个流程。

  • 这里我们要注意下面几条:
  1. 用getchar读入时,如果不按回车符,所有输入会放入缓冲区而不会被读入。所以执行c=getchar();时,我们输入12,如果不按回车键,1仍然不会读入;

  2. 最后按下的回车键,虽是用来告诉系统输入已结束,但同时也会作为一个字符放入缓冲区,所以我们输入12按回车后,输入流其实有三个字符:1、2、回车,而这个回车就被e读取了;

  3. 12是被当做两个字符’1’和’2’(注意,不是数字1、2),而不像%d时,作为一个数字12来看待;

  4. putchar()输出指定字符,不会在输出后自动换行,所以putchar©;和putchar(d);之间要加putchar(’\n’);如果不加的话,会把c和d两个自动(1、2)输入到同一行。

  5. getchar可以读入所有字符。

  6. windows下如果想结束,就输入Ctrl+Z,表示EOF,Linux下输入Ctrl+ D。

  • 大师级经典的著作,要字斟句酌的去读,去理解。以前在看K&R的The C Programming Language(Second Edition)中第1.5节的字符输入/输出,很迷惑getchar()和EOF的行为。因此,感觉很有必要总结一下,不然,很多琐碎的知识点长时间过后就会淡忘的,只有写下来才是最好的方法。
  • scanf和getchar的混用
    • 假设程序要求用getchar()处理字符输入,用scanf()处理数值输入,这两个函数都能很好的完成任务,但是不能混合使用。 因为getchar()读取每个字符,包括空格、制表符和换行符;而scanf()在读取数字时则会跳过空格、制表符和换行符。
    • 例如:要求用户输入一个字母和两个数字,输出以第一个数字为行数,第二个数字为列数,以字母为内容的数列,要求可以不断输入直至键入回车退出程序:
#include <stdio.h>
void display(char cr,int lines,int width);
int main(int argc, const char * argv[]) {
   
    int ch;
    int rows,cols;
    printf("Enter a character and two integers:\n");
    while((ch=getchar())!= '\n'){
        scanf("%d %d", &rows,&cols);
        display(ch, rows, cols);
        printf("Enter another character and two integers;\n");
        printf("Enter a newline to quit.\n");
    }
    printf("Bye.\n");
    return 0;
    
    }
void display(char cr,int lines,int width){
    int row,col;
    
    for(row=1; row<= lines; row++){
        for(col =1; col<=width; col++){
            putchar(cr);
        }
        putchar('\n');
            }
}

  • 发现,再输入"c23",成功打印一次后,程序就自动退出了。这明显不符合我们的题目要求。
  • 原因是:输入的c23其实是c23+换行符,scanf()函数把这个换行符留在了缓存中。getchar()不会跳过换行符,所以在进入下一轮迭代时,还没来得及输入字符,它就读取了换行符,然后将其赋值给了ch。而ch是换行符正式终止循环的条件。所以,我们需要删除scanf()函数留在缓存中的换行符。
  • 改进方法
    • 在if语句中使用一个break语句,可以在scanf()的返回值不等于2时终止程序,即如果一个或两个输入值不是整数或者遇到文件结尾就终止程序。
    • 在scanf()后面,添加一个吞噬无用字符的while循环
#include <stdio.h>
void display(char cr,int lines,int width);
int main(int argc, const char * argv[]) {
   
    int ch;
    int rows,cols;
    printf("Enter a character and two integers:\n");
    while((ch=getchar())!= '\n'){
        if( scanf("%d %d", &rows,&cols)!=2 ){
            break;
        }
        display(ch, rows, cols);
        
        while(getchar()!='\n'){
            continue;
        }
        printf("Enter another character and two integers;\n");
        printf("Enter a newline to quit.\n");
    }
    printf("Bye.\n");
    return 0;
    
    }
void display(char cr,int lines,int width){
    int row,col;
    
    for(row=1; row<= lines; row++){
        for(col =1; col<=width; col++){
            putchar(cr);
        }
        putchar('\n');
            }
}

3、总结

3.1 对getchar的两点总结

  1. getchar是以行为单位进行存取的。
  • 当调用getchar函数读取输入时,只有当输入字符为换行符’/n’或文件结束符EOF时,getchar才会停止执行,整个程序将会往下执行。并且,如果输入行是以EOF结束的(EOF之前不是换行符),则EOF会被“吃掉”(即不会被getchar 读取到)。譬如下面程序段:
while((c = getchar()) != EOF){
	putchar(c);
}
  • 执行程序,输入:abc,然后回车。则程序就会去执行puchar©,然后输出abc 和一个回车。然后可以继续输入,再次遇到换行符的时候,程序又会把那一行的输入的字符输出在终端上。

  • 令人迷惑的是,getchar不是以字符为单位读取的吗?那么,既然我输入了第一个字符a,肯定满足while循环(c = getchar()) != EOF 的条件,那么应该执行putchar©在终端输出一个字符a。但是程序就偏偏不这样执行,而是必需读到一个换行符或者文件结束符EOF才进行一次输出

  • 造成这种结果的一种解释是,输入终端驱动处于一次一行的模式下。也就是虽然getchar()和putchar()确实是按照每次一个字符进行的。但是终端驱动处于一次一行的模式,它的输入只有到’/n’或者EOF时才结束。

  • 在本例中,程序段调用了getchar函数,则控制权从程序段转移到getchar函数,而getchar函数要依赖于操作系统的驱动来读取输入,没遇到换行符或者EOF,驱动不会通知getchar函数,getchar函数处于“阻塞”状态。而遇到换行符或者EOF 后, getchar函数解除“阻塞”,读取一个字符,控制权返回程序段,执行putchar 函数,循环执行。直到遇到EOF字符或者这行输入全部处理完。

  1. getchar()的返回值必须用int定义。一般情况下返回值是非负值,但也可能是负值,即返回EOF。这个EOF在函数库里一般定义为-1。正确的定义方法如下(K&R C中特别提到了这个问题):
int c;
c = getchar();

3.2 EOF的两点总结(主要指普通终端中的EOF)

  1. EOF作为文件结束符时的情况:
  • EOF虽然是文件结束符,但并不是在任何情况下输入Ctrl+D(Windows下Ctrl+Z)都能够实现文件结束的功能,只有在下列的条件下,才作为文件结束符:
    • (1)遇到getcahr函数执行时,要输入第一个字符时就直接输入Ctrl+D;
    • (2)在前面输入的字符为换行符时,接着输入Ctrl+D;
    • (3)在前面有字符输入且不为换行符时,要连着输入两次Ctrl+D,这时第二次输入的Ctrl+D起到文件结束符的功能,至于第一次的Ctrl+D作为行结束符(如1.1所讲)。

其实,这三种情况都可以总结为:只有在getchar()提示新的一次输入时,直接输入Ctrl+D才相当于文件结束符。

  1. EOF作为行结束符时的情况
  • 这时候输入Ctrl+D作为行结束的标志能结束getchar()的“阻塞”,使getchar()逐个字符读入,但是EOF会被“吃掉”,并不会被读取。

  • 以上面的代码段为例,如果执行时输入abc,然后Ctrl+D,程序输出结果为: abcabc

注意:第一组abc是你从终端输入的,然后输入Ctrl+D,getchar逐个字符读取并逐个输出打印出第二组abc,同时光标停在第二组字符的c后面,然后可以进行新一次的输入。这时如果再次输入Ctrl+D,就会起到了文件结束符的作用,因为EOF是一行输入的第一个字符。如果输入abc之后,然后回车,输入换行符的话,则终端显示为:

abc'/n'
abc'/n'
//第三行

其中第一行为你是终端输入的,第二行是终端输出(含换行符),光标停在了第三行处,等待新一次的终端输入。从这里也可以看出Ctrl+D和换行符分别作为行结束符时,输出的不同结果。

3.3 scanf()

  • scanf()在读取数字时会自动跳过缓存区中的前导空格、制表符和换行符。即当输入格式是%d时,scanf会忽略任何空白字符(空格、回车、制表符等),忽略的意思是,从缓冲区里删除,但并不保存;如果遇到数字,则拿出并保存给后面的整数,也就是说%d的时候,scanf想要的字符是数字和空白符。但如果格式是%c,那么任何字符都是它想要的,包括空格、回车、制表符等。
  • 例如:
    在这里插入图片描述
    参考文献:C语言 getchar()原理及易错点解析
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Leon_George

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值