C++ 基礎系列——輸入輸出流


一、概覽

C++ 中用於實現數據輸入和輸出的這些流類以及它們之間的關系:

  • istream:常用於接收從鍵盤輸入的數據;
  • ostream:常用於將數據輸出到屏幕上;
  • ifstream:用於讀取文件中的數據;
  • ofstream:用於向文件中寫入數據;
  • iostream:繼承自 istream 和 ostream 類,因為該類的功能兼兩者於一身,既能用於輸入,也能用於輸出;
  • fstream:兼 ifstream 和 ofstream 類功能於一身,既能讀取文件中的數據,又能向文件中寫入數據。

cin、cout 都聲明在 iostream 頭文件中,此外該頭文件還有 cerr、clog 兩個 ostream 類對象。

  • cout 除了可以通過重定向將數據輸出到屏幕上,還可以實現將數據輸出到指定文件中;而 cerr 和 clog 都不支持重定向,它們只能將數據輸出到屏幕上;
  • cout 和 clog 都設有緩沖區,即它們在輸出數據時,會先將要數據放到緩沖區,等緩沖區滿或者手動換行(使用換行符 '\n' 或者 endl)時,才會將數據全部顯示到屏幕上;而 cerr 則不設緩沖區,它會直接將數據輸出到屏幕上。

cin 輸入流對象常用成員方法

成員方法名 功能
getline(str,n,ch) 從輸入流中接收 n-1 個字符給 str 變量,當遇到指定 ch 字符時會停止讀取,默認情況下 ch 為 '\0'。
get() 從輸入流中讀取一個字符,同時該字符會從輸入流中消失。
gcount() 返回上次從輸入流提取出的字符個數,該函數常和 get()、getline()、ignore()、peek()、 read()、readsome()、putback() 和 unget() 聯用。
peek() 返回輸入流中的第一個字符,但並不是提取該字符。
putback(c) 將字符 c 置入輸入流(緩沖區)。
ignore(n,ch) 從輸入流中逐個提取字符,但提取出的字符被忽略,不被使用,直至提取出 n 個字符,或者 當前讀取的字符為 ch。
operator>> 重載 >> 運算符,用於讀取指定類型的數據,並返回輸入流對象本身。

cout 輸出流對象常用成員方法

成員方法名 功能
put() 輸出單個字符。
write() 輸出指定的字符串。
tellp() 用於獲取當前輸出流指針的位置。
seekp() 設置輸出流指針的位置。
flush() 刷新輸出流緩沖區。
operator<< 重載 << 運算符,使其用於輸出其后指定類型的數據。

二、cout.put():輸出單個字符

put() 方法專用於向輸出流緩沖區中添加單個字符,其語法格式如下:

ostream&put(char c);

  • 參數 c 為要輸出的字符。

除了使用 cout.put() 函數輸出一個字符外,還可以用 putchar() 函數輸出一個字符。putchar() 函數是C 語 言中使用的,在 <stdio.h> 頭文件中定義,C++保留了這個函數,在 頭文件中定義。

三、cout.write():輸出字符串

write() 成員方法專用於向輸出流緩沖區中添加指定的字符串,其語法格式如下:

ostream&write(const char * s,streamsize n);

  • 其中,s 用於指定某個長度至少為 n 的字符數組或字符串;n 表示要輸出的前 n 個字符。

四、cout.tellp()和 cout.seekp()方法

當數據暫存於輸出流緩沖區中時,我們仍可以對其進行修改。ostream 類中提供 有 tellp() 和 seekp() 成員方法,借助它們就可以修改位於輸出流緩沖區中的數據。

tellp()

tellp() 成員方法用於獲取當前輸出流緩沖區中最后一個字符所在的位置,其語法格式如下:

streampos tellp();

  • streampos 是 fpos 類型的別名, 而 fpos 通過自動類型轉換,可以直接賦值給一個整形變量。

當輸出流緩沖區中沒有任何數據時,該函數返回的整形值為 0;當指定的輸出流緩沖區不支持此操作, 或者操作失敗時,該函數返回的整形值為 -1。

int main() {
    //定義一個文件輸出流對象 
    std::ofstream outfile;
    //打開 test.txt,等待接收數據 
    outfile.open("test.txt");
    const char * str = "ASDFASDFASDF";

    //將 str 字符串中的字符逐個輸出到 test.txt 文件中,每個字符都會暫時存在輸出流緩沖區中 
    for (int i = 0; i < strlen(str); i++) { 
        outfile.put(str[i]); 
        //獲取當前輸出流
        long pos = outfile.tellp();
        std::cout << pos << std::endl;
    }
    //關閉文件之前,刷新 outfile 輸出流緩沖區,使所有字符由緩沖區流入 test.txt 文件 
    outfile.close();
    return 0;
}

seekp()

seekp() 方法用於指定下一個進入輸出緩沖區的字符所在的位置。

seekp() 方法有如下 2 種語法格式:

//指定下一個字符存儲的位置 
ostream& seekp (streampos pos); 
//通過偏移量間接指定下一個字符的存儲位置
ostream& seekp (streamoff off, ios_base::seekdir way);
  • pos:用於接收一個正整數;
  • off:用於指定相對於 way 位置的偏移量,其本質也是接收一個整數,可以是正數(代表正偏移)或者負數(代表負偏移);
  • way:用於指定偏移位置,即從哪里計算偏移量,它可以接收表 1 所示的 3 個值。

文件定位標志

模式標志 描述
ios::beg 從文件頭開始計算偏移量
ios::end 從文件末尾開始計算偏移量
ios::cur 從當前位置開始計算偏移量

五、cout 格式化輸出

C++ 通常使用 cout 輸出數據,和 printf() 函數相比,cout 實現格式化輸出數據的方式更加多樣化。- cout 作為 ostream 類的對象,該類中提供有一些成員方法,可實現對輸出數據的格式化;

  • C++ 標准庫專門提供了一個 頭文件,該頭文件中包含有大量的格式控制符(嚴格意義上稱為“流操縱算子”),使用更加方便。

表1 ostream 類的成員方法

成員函數 說明
flags(fmtfl) 當前格式狀態全部替換為 fmtfl。注意,fmtfl 可以表示一種格式,也可以表示多種格式。
precision(n) 設置輸出浮點數的精度為 n。
width(w) 指定輸出寬度為 w 個字符。
fill(c) 在指定輸出寬度的情況下,輸出的寬度不足時用字符 c 填充(默認情況是用空格填充)。
setf(fmtfl, mask) 在當前格式的基礎上,追加 fmtfl 格式,並刪除 mask 格式。其中,mask 參數可以省略。
unsetf(mask) 在當前格式的基礎上,刪除 mask 格式。

其中,對於表 1 中 flags() 函數的 fmtfl 參數、setf() 函數中的 fmtfl 參數和 mask 參數以及 unsetf() 函數 mask 參數,可以選擇表 2 中列出的這些值。

表2 fmtfl 和 mask 參數可選值

標志 作用
ios::boolapha 把 true 和 false 輸出為字符串
ios::left 輸出數據在本域寬范圍內向左對齊
ios::right 輸出數據在本域寬范圍內向右對齊
ios::internal 數值的符號位在域寬內左對齊,數值右對齊,中間由填充字符填充
ios::dec 設置整數的基數為 10
ios::oct 設置整數的基數為 8
ios::hex 設置整數的基數為 16
ios::showbase 強制輸出整數的基數(八進制數以 0 開頭,十六進制數以 0x 打頭)
ios::showpoint 強制輸出浮點數的小點和尾數 0
ios::uppercase 在以科學記數法格式 E 和以十六進制輸出字母時以大寫表示
ios::showpos 對正數顯示“+”號
ios::scientific 浮點數以科學記數法格式輸出
ios::fixed 浮點數以定點格式(小數形式)輸出
ios::unitbuf 每次輸出之后刷新所有的流
double a = 1.23; 6. 7. 8. 9. 10. 11. 12. 13. 

//設定后續輸出的浮點數的精度為 4 
cout.precision(4);
cout <<"precision: "<< a << endl; 

//設定后續以科學計數法的方式輸出浮點數 
cout.setf(ios::scientific);
cout <<"scientific:"<< a << endl;

當 cout 采用此方式進行格式化輸出時,其后不能立即輸出數據,需要再用一個 cout 輸出數據。

當調用 unsetf() 或者 2 個參數的 setf() 函數時,為了提高編寫代碼的效率,可以給 mask 參數傳遞如下 3 個組合格式:

  • ios::adjustfield:等價於 ios::left | ios::right | ios::internal;
  • ios::basefield:等價於 ios::dec | ios::oct | ios::hex;
  • ios::floatfield:等價於 ios::scientific | ios::fixed。
double f = 123; 
//設定后續以科學計數法表示浮點數 
cout.setf(ios::scientific); 
cout << f << '\n';

//刪除之前有關浮點表示的設定 
cout.unsetf(ios::floatfield);
cout << f;

使用流操縱算子格式化輸出

C++流操縱算子

流操縱算子 作用
*dec 以十進制形式輸出整數
hex 以十六進制形式輸出整數
oct 以八進制形式輸出整數
fixed 以普通小數形式輸出浮點數
scientific 以科學計數法形式輸出浮點數
left 左對齊,即在寬度不足時將填充字符添加到右邊
*right 右對齊,即在寬度不足時將填充字符添加到左邊
setbase(b) 設置輸出整數時的進制,b=8、10 或 16
setw(w) 指定輸出寬度為 w 個字符,或輸入字符串時讀入 w 個字符。注意,該函數所起的作用是一次性的,即只影響下一次 cout 輸出。
setfill(c) 在指定輸出寬度的情況下,輸出的寬度不足時用字符 c 填充(默認情況是用空格填 充)
setprecision(n) 設置輸出浮點數的精度為 n。 在使用非 fixed 且非 scientific 方式輸出的情況下,n 即為有效數字最多的位數, 如果有效數字位數超過 n,則小數部分四舍五人,或自動變為科學計 數法輸出並保 留一共 n 位有效數字。 在使用 fixed 方式和 scientific 方式輸出的情況下,n 是小數點后面應保留的位數。
setiosflags(mask) 在當前格式狀態下,追加 mask 格式,mask 參數可選擇表 2 中的所有值。
resetiosflags(mask) 在當前格式狀態下,刪除 mask 格式,mask 參數可選擇表 2 中的所有值。
boolapha 把 true 和 false 輸出為字符串
*noboolalpha 把 true 和 false 輸出為 0、1
showbase 輸出表示數值的進制的前綴
*noshowbase 不輸出表示數值的進制.的前綴
showpoint 總是輸出小數點
*noshowpoint 只有當小數部分存在時才顯示小數點
showpos 在非負數值中顯示 +
*noshowpos 在非負數值中不顯示 +
uppercase 十六進制數中使用 A~E。若輸出前綴,則前綴輸出 0X,科學計數法中輸出 E
*nouppercase 十六進制數中使用 a~e。若輸出前綴,則前綴輸出 0x,科學計數法中輸出 e。
internal 數值的符號(正負號)在指定寬度內左對齊,數值右對 齊,中間由填充字符填充。

注意:“流操縱算子”一欄帶有星號 * 的格式控制符,默認情況下就會使用。例如在默認情況下,整數是用十 進制形式輸出的,等效於使用了 dec 格式控制符。

//以十六進制輸出整數 
cout << hex << 16 << endl;
//刪除之前設定的進制格式,以默認的 10 進制輸出整數 
cout << resetiosflags(ios::basefield)<< 16 << endl;

double a = 123;
//以科學計數法的方式輸出浮點數 
cout << scientific << a << endl;
//刪除之前設定的科學計數法的方法
cout << resetiosflags(ios::scientific) << a << endl;

六、輸入輸出重定向

cout 和 cerr、clog 的一個區別是,cout 允許被重定向,而 cerr 和 clog 都不支持。值得一提的是,cin 也允許被重定向。

freopen()函數實現重定向

freopen() 定義在stdio.h頭文件中,是 C 語言標准庫中的函數,專門用於重定向輸入流(包括 scanf()、 gets() 等)和輸出流(包括 printf()、puts() 等)。該函數也可以對 C++ 中的 cin 和 cout 進行重定向。

string name, url; 
// 將標准輸入流重定向到 in.txt 文件 
freopen("in.txt", "r", stdin); 
cin >> name >> url;

// 將標准輸出重定向到 out.txt 文件 
freopen("out.txt", "w", stdout);
cout << name << "\n" << url;

rdbuf()函數實現重定向

rdbuf() 函數定義在 頭文件中,專門用於實現 C++ 輸入輸出流的重定向。

ios 作為 istream 和 ostream 類的基類,rdbuf() 函數也被繼承,因此 cin 和 cout 可以直接調 用該函數實現重定向。

rdbuf() 函數的語法格式有 2 種,分別為:

  • streambuf * rdbuf() const; // 返回一個指向當前流緩沖區的指針
  • streambuf * rdbuf(streambuf * sb); // 將 sb 指向的緩沖區設置為當前流的新緩沖區,並返回一個指向舊緩沖區的對象。

streambuf 是 C++ 標准庫中用於表示緩沖區的類,該類的指針對象用於代指某個具體的流緩沖區。

通過控制台實現重定向

在控制台中使用 > 或者 < 實現重定向

D:\test> D:\demo.ext < in.txt > out.txt

七、管理輸出緩沖區

導致緩沖刷新(數據寫到輸出設備或文件)的原因有很多:

  • 程序正常結束,作為 main() 函數的 return 操作的一部分,緩沖刷新被執行。
  • 緩沖區滿時,需要刷新緩沖,而后新的數據才能繼續寫入緩沖區。
  • 使用操縱符如 endl 來顯式刷新緩沖區。
  • 每個輸出操作之后,可以用操作符 unitbuf 設置流的內部狀態,來清空緩沖區。默認情況下,對 cerr 是設置 unitbuf 的,因此寫到 cerr 的內容都是立即刷新的。
  • 一個輸出流可能被關聯到另一個流。在這種情況下,當讀寫被關聯的流時,關聯到的流的緩沖區會被刷新。例如,默認情況下,cin 和 cerr 都關聯到 cout。因此,讀 cin 或寫 cerr 都會導致 cout 的緩沖區被刷新。

刷新輸出緩沖區

  • flush 和 ends。flush 刷新緩沖區,但不輸出任何額外的字符;
  • ends 向緩沖區插入一個空字符,然后刷新緩沖區。
cout << "hi!" << endl;      //輸出 hi 和一個換行,然后刷新緩沖區
cout << "hi!" << flush;     //輸出 hi,然后刷新緩沖區,不附加任何額外字符
cout << "hi!" << ends;      //輸出 hi 和一個空字符,然后刷新緩沖區

unitbuf 操作符

可以使用 unitbuf 操作符,它告訴流在接下來的每次寫操作之后都進行一次 flush 操作。而 nounitbuf 操作符則重置流, 使其恢復使用正常的系統管理的緩沖區刷新機制:

cout << unitbuf;    // 所有輸出操作后都會立即刷新緩沖區

cout << nounitbuf; //回到正常的緩沖方式

如何程序崩潰,輸出緩沖區不會被刷新。

關聯輸入和輸出流

當一個輸入流被關聯到一個輸出流時,任何試圖從輸入流讀取數據的操作都會先刷新關聯的輸出流。標准庫將 cout 和 cin 關聯在一起,因此下面語句:

cin >> ival;

導致 cout 的緩沖區被刷新。

tie() 函數可以用來綁定輸出流,它有兩個重載的版本:

ostream* tie ( ) const; //返回指向綁定的輸出流的指針。
ostream* tie ( ostream* os ); //將 os 指向的輸出流綁定的該對象上,並返回上一個綁定的輸出流指針。

cin.tie(&cout);     //僅僅是用來展示,標准庫已經將 cin 和 cout 關聯在一起 

//old_tie 指向當前關聯到 cin 的流(如果有的話) 
ostream *old_tie = cin.tie(nullptr);    // cin 不再與其他流關聯

//將 cin 與 cerr 關聯,這不是一個好主意,因為 cin 應該關聯到 cout
cin.tie(&cerr);         //讀取 cin 會刷新 cerr 而不是 cout
cin.tie(old_tie);       //重建 cin 和 cout 間的正常關聯

每個流同時最多關聯到一個流, 但多個流可以同時關聯到同一個 ostream。

八、cin.get():C++讀取單個字符

get() 是 istream 類的成員函數,它有多種重載形式。

最簡單最常用的一種:

int get();

此函數從輸入流中讀入一個字符,返回值就是該字符的 ASCII 碼。如果碰到輸入的末尾,則返回值為 EOF(-1)。

get() 函數不會跳過空格、制表符、回車等特殊字符,所有的字符都能被讀入。

九、cin.getline():C++讀入一行字符串(整行數據)

getline() 是 istream 類的成員函數,它有如下兩個重載版本:

istream & getline(char* buf, int bufSize); \ 從輸入流中讀取 bufSize-1 個字符到緩沖區 buf,或遇到\n 為止
istream & getline(char* buf, int bufSize, char delim); \ 讀到 delim 字符為止。

十、忽略指定字符

ignore() 是 istream 類的成員函數,它的原型是:

istream & ignore(int n =1, int delim = EOF);

此函數的作用是跳過輸入流中的 n 個字符,或跳過 delim 及其之前的所有字符,哪個條件先滿足就按哪個執行。 兩個參數都有默認值,因此 cin.ignore() 就等效於 cin.ignore(1, EOF), 即跳過一個字符。

十一、查看輸入流中的下一個字符

peek() 是 istream 類的成員函數,它的原型是:

int peek();

此函數返回輸入流中的下一個字符,但是並不將該字符從輸入流中取走, 因此叫 peek。

cin.peek() 不會跳過輸入流中的空格、回車符。在輸入流已經結束的情況下,cin.peek() 返回 EOF。

十二、cin 如何判斷輸入結束

在控制台中輸入特殊的控制字符就表示輸入結束了:

  • 在 Windows 系統中,通過鍵盤輸入時,按 Ctrl+Z 組合鍵后再按回車鍵,就代表輸入結束。
  • 在 UNIX/Linux/Mac OS 系統中,Ctrl+D 代表輸入結束。

不管是文件末尾,還是 Ctrl+Z 或者 Ctrl+D,它們都是結束標志;cin 在正常讀取時返回 true,遇到結束標志 時返回 false,我們可以根據 cin 的返回值來判斷是否讀取結束。

十三、處理輸入輸出錯誤

發生輸入輸出錯誤的可能情況是無限的!但 C++ 將所有可能的情況歸結為四類,稱為流狀態(stream state)。 每種流狀態都用一個 iostate 類型的標志位來表示。

流狀態對應的標志位

標志位 意義
badbit 發生了(或許是物理上的)致命性錯誤,流將不能繼續使用。
eofbit 輸入結束(文件流的物理結束或用戶結束了控制台流輸入,例如用戶按下了 Ctrl+Z 或 Ctrl+D 組合鍵。
failbit I/O 操作失敗,主要原因是非法數據(例如,試圖讀取數字時遇到字母)。流 可以繼續使用,但會設置 failbit 標志。
goodbit 一切止常,沒有錯誤發生,也沒有輸入結束。

ios_base 類定義了以上四個標志位以及 iostate 類型,但是 ios 類又派生自 ios_base 類,所以可以使用 ios::failbit 代替 ios_base::failbit 以節省輸入。

一旦流發生錯誤,對應的標志位就會被設置,我們可以通過下表列出的函數檢測流狀態。

檢測函數 對應的標志位 說明
good() goodbit 操作成功,沒有發生任何錯誤。
eof() eofbit 到達輸入末尾或文件尾。
fail() failbit 發生某些意外情況(例如,我們要讀入一個數字,卻讀入了字符 'x')。
bad() badbit 發生嚴重的意外(如磁盤讀故障)。

不幸的是,fail() 和 bad() 之間的區別並未被准確定義

  • 如果輸入操作遇到一個簡單的格式錯誤,則使流進入 fail() 狀態,也就是假定我們(輸入操作的用戶)可以從錯 誤中恢復。
  • 如果錯誤真的非常嚴重,例如發生了磁盤故障,輸入操作會使得流進入 bad() 狀態。也就是假定面對這種情況所能做的很有限,只能退出輸入。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM