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