一文讀懂C/C++語(yǔ)言輸入輸出流與緩存區(qū)
有沒(méi)有發(fā)現(xiàn),基本上所有的C語(yǔ)言入門(mén)書(shū)籍,或者是我們的教程里面,第一個(gè)C語(yǔ)言程序?qū)嶓w,都是“Hello World!
”;我不知道這是不是行業(yè)的“潛規(guī)則”,總之,它把無(wú)數(shù)的程序員帶進(jìn)了計(jì)算機(jī)的世界,步入了代碼的大坑里,所以你好,世界!
一件趣事
我記得大學(xué)學(xué)習(xí)計(jì)算機(jī)的時(shí)候,就是在電腦這樣的一個(gè)程序,不知道經(jīng)過(guò)了什么過(guò)程,就能在計(jì)算機(jī)上顯示出"Hello World!
"。后來(lái)我把這個(gè)"Hello World!
"改成了"I Love xxx!
",哦買(mǎi)噶,真是驚呆我了,有一種立馬在女神面前炫耀的感覺(jué)了.
那么,這其中有什么奧妙呢,我們從C語(yǔ)言的輸入輸出流開(kāi)始說(shuō)起.
hello world 是怎么顯示出來(lái)的
對(duì)的,就是這樣的一個(gè)程序
#include <stdio.h>
int main(int argc,char **argv)
{
//printf("Hello World!");
printf("I Love xxx!");
}
在我們?nèi)粘I钪?總看到電子顯示屏上面顯示著"歡迎某位領(lǐng)導(dǎo)蒞臨我校觀察","賓館"之類(lèi)的,那是不是"Hello World!
"也是這樣編譯出來(lái)的呢?
我們看到程序中的printf();由系統(tǒng)或者編譯器提供商提供的一個(gè)應(yīng)用接口,是格式化輸出函數(shù), 一般用于向標(biāo)準(zhǔn)輸出設(shè)備按規(guī)定格式輸出信息。一般其函數(shù)原型應(yīng)該是這樣的:
int printf(const char *,...);
int _EXFUN(printf, (const char *__restrict, ...)
_ATTRIBUTE ((__format__ (__printf__, 1, 2))));
經(jīng)過(guò)預(yù)處理,編譯,匯編,鏈接四個(gè)過(guò)程,借助了相應(yīng)的緩沖區(qū)來(lái)進(jìn)行輸入與輸出,就會(huì)顯示出來(lái)輸入輸出流
流是什么
“流”即是流動(dòng)的意思,是物質(zhì)從一處向另一處流動(dòng)的過(guò)程,是對(duì)一種有序連續(xù)且具有方向性的數(shù)據(jù)的抽象描述。
在計(jì)算機(jī)系統(tǒng)中是指信息從外部輸入設(shè)備向計(jì)算機(jī)內(nèi)部輸入,或者從內(nèi)存向外部輸出設(shè)備輸出的過(guò)程。這種輸入輸出的過(guò)程被形象的比喻為“流”。
輸入輸出
什么是輸入輸出呢?C語(yǔ)言中我們用到的最頻繁的輸入輸出方式就是scanf()與printf()。
scanf():從標(biāo)準(zhǔn)輸入設(shè)備(鍵盤(pán))讀取數(shù)據(jù),并將值存放在變量中。
printf():將指定的文字/字符串輸出到標(biāo)準(zhǔn)輸出設(shè)備(屏幕)。注意寬度輸出和精度輸出控制。
字符輸入輸出(getchar/putchar),字符串輸入輸出函數(shù) (gets與puts),與gets/puts類(lèi)似的還有fgets與fputs,它們一般用于對(duì)文件的操作.
緩沖區(qū)
定義
緩沖區(qū)是內(nèi)存空間的一部分,也就是說(shuō)在內(nèi)存空間中預(yù)留了一定大小的存儲(chǔ)空間,這些存儲(chǔ)空間用來(lái)緩沖輸入或輸出的數(shù)據(jù),這部分預(yù)留的空間就叫做緩沖區(qū),根據(jù)其對(duì)應(yīng)的是輸入設(shè)備還是輸出設(shè)備,分為輸入緩沖區(qū)和輸出緩沖區(qū)。
原理介紹
當(dāng)調(diào)用輸入函數(shù)scanf()時(shí),輸入函數(shù)會(huì)將我們輸入的數(shù)字輸入到輸入緩沖區(qū),而當(dāng)我們的輸入緩沖區(qū)有內(nèi)容時(shí),再次輸入將不會(huì)被執(zhí)行,而是直接跳過(guò)執(zhí)行,將輸入緩沖區(qū)的內(nèi)容賦給變量。
引入緩沖區(qū)的意義
緩沖區(qū)就是一塊內(nèi)存,用來(lái)做數(shù)據(jù)的一個(gè)臨時(shí)存放點(diǎn),在輸入輸出操作中起著至關(guān)重要的作用,在百度百科定義如下
比如我想把一篇文章以字符序列的方式輸出到計(jì)算機(jī)顯示器屏幕上,那么我的程序內(nèi)存作為數(shù)據(jù)源而顯示器驅(qū)動(dòng)程序作為數(shù)據(jù)目標(biāo),如果數(shù)據(jù)源直接對(duì)數(shù)據(jù)目標(biāo)發(fā)送數(shù)據(jù)的話。數(shù)據(jù)目標(biāo)獲得第一個(gè)字符,便將它顯示。然后從端口讀取下一個(gè)字符,可是這時(shí)就不能保證數(shù)據(jù)源向端口發(fā)送的恰好是第二個(gè)字符(也許是第三個(gè),而第二個(gè)已經(jīng)在數(shù)據(jù)目標(biāo)顯示時(shí)發(fā)送過(guò)了)。這樣的話就不能保證輸出的數(shù)據(jù)能完整的被數(shù)據(jù)目標(biāo)所接受并處理。
緩沖區(qū)的類(lèi)型
緩沖區(qū)有三種,我一個(gè)一個(gè)地說(shuō)下:
1、全緩沖
內(nèi)存中有一段存儲(chǔ)區(qū)域,比如有1024個(gè)字節(jié)大小,有一個(gè)程序會(huì)從這段存儲(chǔ)區(qū)域中讀取數(shù)據(jù)?,F(xiàn)在系統(tǒng)把一個(gè)文件的內(nèi)容放入這個(gè)存儲(chǔ)區(qū),只要1024個(gè)字節(jié)都放滿了,那么程序會(huì)立即來(lái)讀取這1024個(gè)字節(jié)的數(shù)據(jù)。只要1024個(gè)字節(jié)沒(méi)有放滿,哪怕只放了1023個(gè)字節(jié),程序都不會(huì)來(lái)讀取,這就是全緩沖的意思。
#include <fstream>
using namespace std;
int main()
{
//創(chuàng)建文件test.txt并打開(kāi)
ofstream outfile("test.txt");
//向test.txt文件中寫(xiě)入4096個(gè)字符’a’
for(int n=0; n < 4096; n++)
{
outfile << 'a';
}
//暫停,按任意鍵繼續(xù)
system("PAUSE");
//繼續(xù)向test.txt文件中寫(xiě)入字符’b’,也就是說(shuō),第4097個(gè)字符是’b’
outfile << 'b';
//暫停,按任意鍵繼續(xù)
system("PAUSE");
return 0;
}
編譯并執(zhí)行,運(yùn)行結(jié)果如下:
此時(shí)打開(kāi)工程所在文件夾下的test.txt文件,您會(huì)發(fā)現(xiàn)該文件是空的,這說(shuō)明4096個(gè)字符“a”還在緩沖區(qū),并沒(méi)有真正執(zhí)行I/O操作。敲一下回車(chē)鍵,窗口變?yōu)槿缦拢?/p>
此時(shí)再打開(kāi)test.txt文件,您就會(huì)發(fā)下該文件中已經(jīng)有了4096個(gè)字符“a”。這說(shuō)明全緩沖區(qū)的大小是4K(4096),緩沖區(qū)滿后執(zhí)行了I/O操作,而字符“b”還在緩沖區(qū)。
再次敲一下回車(chē)鍵,窗口變?yōu)槿缦拢骸 〈藭r(shí)再打開(kāi)test.txt文件,您就會(huì)發(fā)現(xiàn)字符“b”也在其中了。這一步驗(yàn)證了文件關(guān)閉時(shí)刷新了緩沖區(qū)。
2、行緩沖
內(nèi)存中有一段存儲(chǔ)區(qū)域,比如有1024個(gè)字節(jié)大小,有一個(gè)程序會(huì)從這段存儲(chǔ)區(qū)域中讀取數(shù)據(jù)?,F(xiàn)在系統(tǒng)把一個(gè)文件的內(nèi)容放入這個(gè)存儲(chǔ)區(qū),假如放了10個(gè)字節(jié)的數(shù)據(jù),你敲了回車(chē)鍵,那么程序就馬上來(lái)讀取了。假如放了20個(gè)字節(jié),你敲了回車(chē)獎(jiǎng),程序也會(huì)來(lái)讀取。所以即使1024個(gè)字節(jié)沒(méi)有放滿,但是你敲了回車(chē)鍵,程序就會(huì)來(lái)讀取,這個(gè)就叫做行緩沖。
使用鍵盤(pán)操作演示行緩沖,先介紹getchar()函數(shù)。函數(shù)原型:
int getchar(void) ;
說(shuō)明:當(dāng)程序調(diào)用getchar()函數(shù)時(shí),程序就等著用戶(hù)按鍵,用戶(hù)輸入的字符被存放在鍵盤(pán)緩沖區(qū)中,直到用戶(hù)按回車(chē)為止(回車(chē)字符也放在緩沖區(qū)中)。當(dāng)用戶(hù)鍵入回車(chē)之后,getchar()函數(shù)才開(kāi)始從鍵盤(pán)緩沖區(qū)中每次讀入一個(gè)字符。也就是說(shuō),后續(xù)的getchar()函數(shù)調(diào)用不會(huì)等待用戶(hù)按鍵,而直接讀取緩沖區(qū)中的字符,直到緩沖區(qū)中的字符讀完后,才重新等待用戶(hù)按鍵。
#include<iostream>
using namespace std;
int main()
{
char c;
//第一次調(diào)用getchar()函數(shù),程序執(zhí)行時(shí),您可以輸入一串字符并按下回車(chē)鍵,按下回車(chē)鍵后該函數(shù)返回。返回值是用戶(hù)輸入的第一個(gè)字符 (假設(shè)用戶(hù)輸入了 abcdef,函數(shù)返回a)
c = getchar();
//顯示getchar()函數(shù)的返回值
cout<< c << endl; // 輸出 a
// 循環(huán)多次調(diào)用getchar()函數(shù),將每次調(diào)用getchar()函數(shù)的返回值顯示出來(lái),直到遇到回車(chē)符才結(jié)束。 這時(shí)函數(shù)執(zhí)行不會(huì)讓用戶(hù)輸入而是順序讀取緩沖區(qū)字符內(nèi)容。第一個(gè)字符用戶(hù)輸入結(jié)束后已經(jīng)讀取,所以會(huì)從第二個(gè)字符開(kāi)始讀
while((c = getchar())!='\n')
{
cout<< "," << c <<endl
}
return 0;
}
最后輸出結(jié)果是
a ,b ,c ,d ,e ,f
可以交替按下一些字符,編譯結(jié)果如下:
當(dāng)按到第4096個(gè)字符時(shí),提示您不能再按下去,說(shuō)明行緩存的大小是4k,此時(shí)按下回車(chē)鍵,返回第一個(gè)字符是‘a(chǎn)’繼續(xù)敲下回車(chē)鍵,緩存區(qū)的其他字符就全部輸出
3、無(wú)緩沖
內(nèi)存中有一段存儲(chǔ)區(qū)域,比如有1024個(gè)字節(jié)大小,有一個(gè)程序會(huì)從這段存儲(chǔ)區(qū)域中讀取數(shù)據(jù)?,F(xiàn)在系統(tǒng)把一個(gè)文件的內(nèi)容放入這個(gè)存儲(chǔ)區(qū),剛放了1個(gè)字節(jié),程序就馬上來(lái)讀取了;又放了一個(gè)字節(jié),程序又馬上來(lái)讀取了,這就是沒(méi)有緩沖。
在C語(yǔ)言中,一般規(guī)定是要有行緩沖的。但是使用scanf函數(shù)和getchar時(shí),如果行緩沖的換行符沒(méi)有處理好,程序運(yùn)行可能會(huì)有異?;蛘唛W退等現(xiàn)象。也就是不進(jìn)行緩沖,標(biāo)準(zhǔn)出錯(cuò)情況stderr是典型代表,這使得出錯(cuò)信息可以直接盡快地顯示出來(lái)。
如錯(cuò)誤輸出時(shí)使用:
cerr<<”錯(cuò)誤,請(qǐng)檢查輸入的參數(shù)!” ;
這條語(yǔ)句等效于:
fprintf(stderr, ”錯(cuò)誤,請(qǐng)檢查輸入的參數(shù)!”) ;
緩沖區(qū)的大小
如果我們沒(méi)有自己設(shè)置緩沖區(qū)的話,系統(tǒng)會(huì)默認(rèn)為標(biāo)準(zhǔn)輸入輸出設(shè)置一個(gè)緩沖區(qū),這個(gè)緩沖區(qū)的大小通常是 512個(gè)字節(jié) 的大小。
緩沖區(qū)大小由 stdio.h 頭文件中的宏 BUFSIZ 定義,如果希望查看它的大小,包含頭文件,直接輸出它的值即可:
printf("%d", BUFSIZ);
緩沖區(qū)的大小是可以改變的,也可以將文件關(guān)聯(lián)到自定義的緩沖區(qū),詳情可以查看 setvbuf()和 setbuf() 函數(shù)。
緩沖區(qū)的刷新
下列情況會(huì)引發(fā)緩沖區(qū)的刷新:
-
緩沖區(qū)滿時(shí); -
執(zhí)行flush語(yǔ)句,即使用特定函數(shù)刷新緩沖區(qū); -
執(zhí)行endl語(yǔ)句,即行緩沖區(qū)遇到回車(chē)時(shí); -
關(guān)閉文件。
可見(jiàn),緩沖區(qū)滿或關(guān)閉文件時(shí)都會(huì)刷新緩沖區(qū),進(jìn)行真正的I/O操作。另外,在C++中,我們可以使用flush函數(shù)來(lái)刷新緩沖區(qū)(執(zhí)行I/O操作并清空緩沖區(qū)) 如:
cout << flush; //將顯存的內(nèi)容立即輸出到顯示器上進(jìn)行顯示
endl控制符的作用是將光標(biāo)移動(dòng)到輸出設(shè)備中下一行開(kāi)頭處,并且清空緩沖區(qū)。
cout < < endl;
相當(dāng)于
cout < < ”\n”< < flush;
強(qiáng)制緩沖區(qū)的數(shù)字打印
/*
輸出緩沖區(qū)演示
*/
#include <stdio.h>
int main(){
printf("1\n");
fflush(stdout); //強(qiáng)制將輸出緩沖區(qū)的內(nèi)容顯示在屏幕上
while (1){
}
return 0;
}
如何清空輸入緩沖區(qū)的內(nèi)容?
如果我想讓getchar()每次都能夠等待用戶(hù)輸入的話就要清空緩沖區(qū),下面就介紹不同平臺(tái)的方法
C標(biāo)準(zhǔn)規(guī)定 fflush()函數(shù)是用來(lái)刷新輸出(stdout)緩存的。對(duì)于輸入(stdin),它是沒(méi)有定義的。但是有些編譯器也定義了 fflush( stdin )的實(shí)現(xiàn),比如微軟的VC。其它編譯器是否也定義了 fflush( stdin )的實(shí)現(xiàn)應(yīng)當(dāng)查找它的手冊(cè)。GCC編譯器沒(méi)有定義它的實(shí)現(xiàn),所以不能使用 fflush( stdin )來(lái)刷新輸入緩存。
對(duì)于沒(méi)有定義 fflush( stdin )的編譯器,可以使用 fgets()函數(shù)來(lái)代替它(比用 getchar()、scanf()等函數(shù)通用性好)??梢赃@樣忽略輸入流中留下的回車(chē)等其它輸入,從而使下一次的輸入總是保持一個(gè)“干凈”的狀態(tài)。(這個(gè)是任何平臺(tái)下都可以的)
char sbuf[1024];
// ...
fgets( sbuf, 1024, stdin );
// ...
在windows 的vc下面就可以這樣了:
for(int i=0;i<10;++i)
{
char ch=getchar();
fflush(stdin); //每次都會(huì)有等待狀態(tài)了
}
這里說(shuō)到gcc編譯器沒(méi)有定義fflush的實(shí)現(xiàn),我們一般用getchar();來(lái)清除緩沖區(qū)
#include <stdio.h>
main()
{
char c;
for(;(c=getchar())!='a';)
printf("%c",c);
getchar();
c=getchar();
printf("%c",c);
}
輸入:ssss,回車(chē)
得到:ssss,光標(biāo)處(等待輸入)
說(shuō)明: 此時(shí)程序沒(méi)有結(jié)束,進(jìn)行到for循環(huán),因?yàn)椴](méi)有字符a出現(xiàn),所以還沒(méi)跳出for循環(huán).鍵入回車(chē)后,getchar依次從緩沖區(qū)內(nèi)取出(for循環(huán)):'s''s''s''s''\n'
如果我們輸入:ssssa,回車(chē)
得到:ssssa,光標(biāo)處(等待輸入)
說(shuō)明: 程序已經(jīng)跳出for循環(huán),但是由于我們用getchar();清除了換行'\n',后面第7句c=getchar();需要你輸入一個(gè)字符(因?yàn)閟sssa后面并沒(méi)有新的字符),所以程序仍然沒(méi)有結(jié)束.
如果我們注釋掉getchar();這一句,那么得到:ssss,光標(biāo)處(程序結(jié)束) 這個(gè)輸入ssssa是的回車(chē)中的換行符'\n'就被c=getchar();這一句讀取并輸出了。
總結(jié):鍵盤(pán)輸入的字符都存到緩沖區(qū)內(nèi),一旦鍵入回車(chē),getchar就進(jìn)入緩沖區(qū)讀取字符,一次只返回第一個(gè)字符作為getchar函數(shù)的值,如果有循環(huán)或足夠多的getchar語(yǔ)句,就會(huì)依次讀出緩沖區(qū)內(nèi)的所有字符直到'\n'.
要理解這一點(diǎn),之所以你輸入的一系列字符被依次讀出來(lái),是因?yàn)檠h(huán)的作用使得反復(fù)利用getchar在緩沖區(qū)里讀取字符,而不是ge
最后
很多表面的現(xiàn)象看起來(lái)可能不能引起我們的注意,但是當(dāng)我們注意到細(xì)節(jié)的時(shí)候,才能深究其中的奧秘,寫(xiě)代碼亦是如此。
叨叨一下,最近建了一個(gè)學(xué)習(xí)群,用于學(xué)習(xí)交流,需要加群的,加微信號(hào):cyuyan2020,備注:加群
點(diǎn)【在看】是最大的支持
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問(wèn)題,請(qǐng)聯(lián)系我們,謝謝!