首先,getchar是一个宏,它的宏定义如下:
#define getchar() getc(stdin)
#define getc(f) \
((--((f)->level) >= 0) ? (unsigned char)(++(f)->curp)[-1] : \
_fgetc (f))
由上可以看出,getchar是getc()的宏,而getc()又是一个宏,通过代入流stdin进入宏展开;stdin:输入流,从键盘输入到内存,又符合文件操作的一些概念,所以用到了FILE的一些内容,不然,又如何解释宏里面的level和curp呢?
接着,进入到FILE的结构,从头文件中查看:
typedef struct {
short level; /* fill/empty level of buffer */
unsigned flags; /* File status flags */
char fd; /* File descriptor */
unsigned char hold; /* Ungetc char if no buffer */
short bsize; /* Buffer size */
unsigned char *buffer; /* Data transfer buffer */
unsigned char *curp; /* Current active pointer */
unsigned istemp; /* Temporary file indicator */
short token; /* Used for validity checking */
} FILE; /* This is the FILE object */
一堆英文单词,大概了解到level表示满/空的程度,而curp是当前活动指针的位置;
联想到键盘输入,每次从键盘输入一个字符时,字符进入输入流缓冲区,程序怎么知道缓冲区还有多少个字符呢?可能就要借助这个level,来表示缓冲区中还有多少字符,每取走一个,level--,而指针的curp就前进一个位置;
这样就可以顺理理解上面的宏定义,首先判定--stdin->level的大小,意思是,如果缓冲区空了,就不能取了,如果不空,就把指针先++,前进一格,再取前面的值;这个步骤是先往前走,接着再取,很像队列;
举例比较容易理解:
while((c=getchar())!=‘#‘)分析这句代码;
首先调用getchar()这个宏,getchar()又调用getc(stdin)传入到宏中展开,先判定--stdin->level,现在没有输入任何值,缓冲区肯定为空的,则level为0,再自减就为-1,肯定不会>=0,则前半段不执行,执行后半段的_fgetc(stdin),这个貌似内部函数,不管怎么样,它在控制台等待用户输入;不然,那一动一动的光标从哪来呢?
用户输入:programming#,回车就表示输入完成,接着再进入c=getchar()进行取值,缓冲区已经存入内容,所以后半段不再执行,执行前半段;而curp,也就是当前指针,应该指向的是programming#的第一个字符p;
接着,++stdin->curp,指向r,再取前面的值stdin->curp[-1],也即p,而curp自然又指向未取的第一个字符,这样就解释了上面宏中的定义;
while循环向下执行,直到取走‘#‘号时,循环中止,缓冲内还剩下一个回车符,level自然就为1;
比如:c=getchar();c=getchar();程序输入a 回车,最后c得到的是回车符,之后,level=0;
似乎已经完了,可是接下来会出现一个问题,如果有这样的程序,c=getchar(),一不小心输入一串字符,那缓冲区肯定还有剩余的字符,如果再用其它从输入流缓冲区中取值的函数,可能会把剩余的字符取走;
比如说,c=getchar();gets(str);
gets也从输入流中取值,这样巧合很少,但也有这种可能,这并非想要的值;于是,又产生了一个fflush(FILE *stream)函数,把流清空,这样也合理了;
问题是fflush(stdin)这样的函数,具体又是如何工作的呢?真的把流“清空”了吗?我宁愿相信它是把level置为0,也不愿相信它把内存清空了,事实上,那些输入的值还在;
再以上面的例子,while((c=getchar())!=‘#‘),似乎到最后已经取完了,假如输入:programming#,取走#之后,curp指向回车符这个值,我要把这个curp往前拉回来,执行:--stdin->curp,再--stdin->curp,按字面上,它应该指向字符‘g‘,可是再取还是取不到值,原因是此时level已经为0了,做了限制,于是再强制的把level赋值,比如stdin->level+=2;这样一来,好像还有两个字符没有取,接着,c=getchar(),还是可以取到字符‘g‘,这说明,内存中的数据一直都存在;
char c; c=getchar(); c=getchar(); printf("%d\n",c); --stdin->curp; --stdin->curp; stdin->level+=2; c=getchar(); printf("%c\n",c);
这个是关于上面的代码;
另一个比较有意思的是,缓冲区中的流不仅可以取出,也可以退回去;这个函数是ungetc(int ch,FILE *stream);
按照上面的推理,每取走一个字符,curp先++,然后再取前面的字符;如果回退一个字符,这个字符退到什么地方呢?
经过实验,字符退到curp后面,也即是已经取走的那个字符的位置;举例来说;
abcd#,假如已经取走c,则curp最后实际上指向的是字符d,如果再回退一个字符,比如:ungetc(100,stdin),则ASCII码为100的字符会退到c的位置,但是不仅仅是退回一个字符,curp和level都随之变化,这也是情理之中的事儿,结果是:--curp;++level;
curp依然指向当前未取的字符;(它假定这个退回来的字符是未取的);
除了getchar(),gets(),从缓冲区中取值的还有很多函数,比如scanf()等等;
scanf()每次从缓冲区中取值,取决于格式,也即是程序要求它取什么,比如取整型数,则scanf()一直扫描缓冲区,前面的空格忽略,一直到数值型,而在后面,遇到字符或者空格时,就截断;
例:int n;scanf("%d",&n);
最后,做一个程序,该程序从控制台一行中,输入任意个空格和整数,计算输入的整数和;
例如输入:78 98 32 11 10...
这个程序主要考虑如何过滤空格,假如用c=getchar()来判定,如果取到了非空格的值怎么办呢?这就用到上面的ungetc(),如果不是空格(数值型)就退回到流中,让scanf()来取;
int var,sum=0;
while(scanf("%d",&var))
{
sum+=var;
while((c=getchar())==‘‘); /* 过滤空格 */
if(c==‘\n‘)break;
ungetc(c,stdin);
};
全文结束