c++進階---IO類的詳細介紹(一)

IO類
c++爲了更好的處理不同的種類的IO的操作,IO庫中定義了龐大的類庫來處理不同種類的IO操作,該類庫組成如下圖所示:

首先,我們先了解一下這個龐大的IO庫各個類之間的關係。

ios是最基本的父類,其中istream類和ostream類都繼承了ios類。
iostream類通過多重繼承繼承了istream類和ostream類。
ifstream、istringstream兩個類都是繼承了istream類,oftream、ostringstream兩個類都繼承了ostream類。
OK,另外,我們要知道:

處理標準輸入輸出的類:istream(從流中讀取數據,即處理輸入)、ostream(向流中寫入數據,即處理輸出)、iostream(處理輸入和輸出),這些類都被定義在頭文件:iostream中。
處理文件的輸入和輸出的類:ifstream(從文件讀取數據)、ofstream(向文件寫入數據)、fstream(讀寫文件),這些類都被定義在頭文件:fstream中
處理string流的類:istringstream(從string中讀取數據)、ostringstream(向string寫入數據)、stringstream(讀寫string)。然後,這些類都是定義在頭文件:sstream中。
另外,我們再補充一下標準輸入輸出中的一些知識:

cin是STL中定義的一個istream對象,它的作用是用於輸入。
cout、cerr、clog是STL中定義的一個ostream對象,它們的作用是用於輸出,其中cout是標準輸出,cerr是用於輸出錯誤或者警告信息,clog是用於輸出程序的一般性信息。其中cerr不經過緩衝區,直接把錯誤信息輸出到顯示器中,clog則先把信息放在緩衝區中。如果我們輸入一個endl,那麼它將會把我們輸出緩衝區的內容全部輸出,並且輸出一個換行。
cin的詳解
程序的每次輸入都會建立一個輸入緩衝區。而我們的cin就是直接從這個輸入緩衝區獲取數據的,如果我們的緩衝區中有數據殘留(不爲空時),那麼cin對象直接從緩衝區讀取數據而不是請求輸入。另外,之前我們已經說過了cin是istream定義的一個對象,所以我們每次使用cin進行請求輸入的時候,都是在調用istream這個對象的方法,其中istream這個對象處理輸入的方法三種:cin>> 、cin.get() 、cin.getline().另外,cin是可以連續從緩衝區中讀取想要的數據的,它是以(tab 、space、enter)中的一種作爲分隔符,

cin>>方法的介紹
注意事項:
(1)cin>>,其實是調用類的:istream & operator >>方法,它很多種重載的版本,分別用於處理字符、浮點數、整型等數據類型的輸入。
(2)cin>>,這個方法遇到了分割符(tab 、space、enter)時,就返回結束該次cin>>方法的調用。然後,如果我們調用cin>>方法時,從緩衝區讀取數據,如果開頭的數據是分隔符,那麼直接把分割符忽略並且清除,直至第一個字符不是分隔符時纔開始輸入。
示例展示:

#include<iostream>
#include<string>
using namespace std;
int main()
{
    int num;
    string s;
    cin >> num >> s;
    cout << num << " " << s << endl;

    getline(cin,s);
    cout << "string:" << s << endl;
    cout << endl;
    cout << "test" << endl;
    cout << endl;
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
輸入:
[回車][回車][回車]12 abcd[回車]

輸出: 
ps:首先了getline函數是不會忽略開頭的分割符的,我們開始保存到緩衝區的三個回車都被cin>>方法忽略了,而最後一個回車也在緩衝區中,但是沒有被忽略,而是被geiline函數讀了進來,所以在輸出test之前會有兩個換行。

cin.get()方法介紹
注意事項:
(1)cin.get()它有四種比較常見的重載格式:
int cin.get();    //不帶參數時,返回值爲整型,但遇到了文件的結束,返回EOF,其實就是:-1
istream & cin.get(char & s) //返回值爲輸入流。
istream & cin.get(char * s,streamsize n) //用於輸入一個字符串,但是它只接受參數時c語言風格字符串風格的參數,即是不接受string類的參數,而且它默認是以換行作爲結束符。
istream & cin.get(char * s,streamsize n,char end) //用於輸入一個字符串,同樣參數必須是c風格的,當時它是以:字符end,作爲結束符。
1
2
3
4
5
(2)上面的streamsize在頭文件iostream中的定義爲long long型的數據類型的別名。所有版本的cin.get()方法是不會忽略開頭的分隔符符號的,而且它和cin>>一樣,遇到分隔符(tab、space 、enter)時,就結束該次方法的調用。最後的那個分隔符還是保存在緩衝區中。

示例展示:

#include<iostream>
using namespace std;
int main()
{
    char s;

    char s1;
    s1 = cin.get();
    cin.get(s);
    cout << (int)s1 << " " << (int)s << endl; 

    char str[5] = {NULL};
    char end;
    cin.get(str,5);
    cin.get(end);
    cout << str << " " << (int)end << endl;
    cout << endl;
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
輸入:
[回車][回車]test[回車]

輸出:


ok,我們從結果可以看出,我們開始輸入的兩個回車都被作爲s1和s的初始值了,而我們字符串的最後一個回車也被end作爲初始值了,從而可以看出cin.get()是不會忽略緩衝區中的分隔符的。

cin.getline()方法的介紹
注意事項:
(1)cin.getline()方法重要的幾個重載版本
//這裏我cin.get後面的兩個方法一樣,用於輸入字符串。
//cin.getline不會將最後一個輸入的結束符或者換行符殘留在輸入緩衝區中,
//而是一起輸出到顯示器中。默認遇到‘\n’結束輸入。
istream & cin.getline(char *s ,streamsize n) 
//這個方法就是結束方式不同,它是遇到了字符:end就結束輸入。然後,輸出長度爲n-1範圍內,end之前的所有字符。

istream & cin.getline(char * s,streamsize n,char end) 
1
2
3
4
5
6
7
8
代碼示例:

#include<iostream>
#include<string>
using namespace std;
int main()
{
    char s[5];
    char t;
    cin.getline(s, 5);
    cout << s << endl;;
    cin >> t;
    cout << t << endl;
    cout << endl;
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
輸入:
new[回車]f
輸出:

ok,從輸出的結果,我們可以發現,cin.getline是把我們輸入緩衝區的那個回車一併輸出來了。

補充:getline函數的介紹(用於輸入一行時使用,接受空格的輸入)
(1):C++中定義了一個在std名字空間的全局函數getline,因爲這個getline函數的參數使用了string字符串,所以聲明在了string頭文件中了。
getline利用cin可以從標準輸入設備鍵盤讀取一行,當遇到如下三種情況會結束讀操作:1)到文件結束,2)遇到函數的定界符,3)輸入達到最大限度。
函數原型有兩個重載形式:

istream& getline ( istream& is, string& str);//默認以換行符結束

istream& getline ( istream& is, string& str, char end);//以end字符結束
1
2
3
(2)注意,getline遇到結束符時,會將結束符一併讀入指定的string中,再將結束符替換爲空字符。因此,進行從鍵盤讀取一行字符時,建議使用getline,較爲安全。但是,最好還是要進行標準輸入的安全檢查,提高程序容錯能力。另外,cin.getline()類似,但是cin.getline()屬於istream流,而getline()屬於string流,是不一樣的兩個函數。

代碼示例:

#include<iostream>
#include<string>
using namespace std;
int main()
{
    string str;
    getline(cin, str);
    cout << str;
    cout << endl;
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
輸入:
i am a student;
輸出:


gets_s 函數的介紹(一般不要用它好了,c中定義的語法),原來是gets,但是在新的vs2015中已經沒有gets了。只有gets_s
gets是C中的庫函數,在< stdio.h>申明,從標準輸入設備讀字符串,可以無限讀取,不會判斷上限,以回車結束或者EOF時停止讀取,所以程序員應該確保buffer的空間足夠大,以便在執行讀操作時不發生溢出。
函數原型:char *gets_s( char *buffer );

代碼示例:

#include<iostream>
using namespace std;
int main()
{
    char array[20] = { NULL };
    gets_s(array);
    cout << array << endl;
    return 0;
}
1
2
3
4
5
6
7
8
9
輸入:
new find
輸出:


重點內容補充:IO類的對象都是不可以拷貝的,所以如果我們需要使用IO類的對象作爲函數的參數的時候或者返回值的時候,我們都要使用引用類型

條件狀態
因爲我們在使用IO類的時候,總會出現一些錯誤,於是每個IO類都定義了一個iostate這個數據類型表示當前某個操作所處的狀態。其中IO庫定義了4個iostate類型的constexpr的值,它們分別是:

strm::badbit :首先,我們要說明strm代表的是IO類中的一種,例如istream、ostream、fstream等,babit表示系統級的錯誤,如果IO發生了不可修復的錯誤,badbit將被置位,而且流將無法再使用。

strm::failbit:這個會在流發生可修復的錯誤時,failbit將會被置位,例如:當我們本來是要求輸入一個數值的卻輸入了一個字符等錯誤,這種問題可以被修正的,流還可以繼續被使用。

strm::eofbit:當流達到了文件的結束的位置,eofbit和failbit這兩個狀態都會被置位。

strm::goodbit:當goodbit的值爲1時,表示流未發生錯誤。

最後就是,只要badbit、eofbit、failbit任意一個狀態被置位,那麼一切以流狀態爲條件的語句都是返回false。例如:while(cin>>word),這條語句,只要上述三個中,有一個被置位,循環就結束。

IO庫中還定義了一組函數,用於查詢當前某種狀態的值。具體函數如下:其中s爲一個流的對象。

s.eof() // 若流s的eofbit置位,則返回true
s.fail() // 若流s的failbit或badbit置位,則返回true
s.bad() // 若流s的badbit被置位,則返回true
s.good() // 若流s處於有效狀態,則返回true
1
2
3
4
在實際我們在循環中判斷流的狀態是否有效時,都直接使用流對象本身,比如:while(cin>>variable){cout<

#include<iostream>
using namespace std;
int main()
{
    int t;
    while (cin>>t)
    {
        cout << "cin.good的值:"<<cin.good() << endl;
        cout << "cin.eof的值:" << cin.eof() << endl;
        cout << "cin.fail的值:" << cin.fail() << endl;
        cout << "cin.bad的值:" << cin.bad() << endl;
    }
    cout << "結束後" << endl;
    cout << "cin.good的值:" << cin.good() << endl;
    cout << "cin.eof的值:" << cin.eof() << endl;
    cout << "cin.fail的值:" << cin.fail() << endl;
    cout << "cin.bad的值:" << cin.bad() << endl;

    system("pause");
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
輸入:1[空格]2[空格]d[回車]
輸出:


IO類庫還提供了3個函數來管理和設置流的狀態:

s.clear(); // 將流s中所有條件狀態復位,將流的狀態設置爲有效,調用good會返回true
s.clear(flags); // 根據給定的flags標誌位,將流s中對應的條件狀態復位,flags的類型爲iostate
s.setstate(flags); // 根據給定的flags標誌位,將流s中對應的條件狀態置位。,flags的類型爲iostate
s.rdstate(); // 返回一個iostate類型的值,對應流當前的狀態。
1
2
3
4
註釋:Windows下標準輸入輸入文件結束符爲Ctrl+z,Linux爲Ctrl+d。

最後就是,我們來完成一下《c++ primer 第五版》的281那個練習題,代碼如下:

#include<iostream>
#include<string>
using namespace std;

istream & test(istream & s)
{
    string str;

    while ((s >> str).eof() == false)
    {
        cout << str << " " << endl;
    }
    cout << "結束" << endl;
    s.clear();
    return s;
}

int main()
{
    //Windows下標準輸入輸入文件結束符爲Ctrl+z,Linux爲Ctrl+d。
    test(cin);
    system("pause");
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
測試結果:


緩衝區詳解
(1)輸入緩衝區

觀察如下代碼:

#include<iostream>
using namespace std;


int main()
{
    char ch;
    while (cin >> ch)
    {
        cout << ch;
    }

    system("pause");
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
我們預想的是,我們輸入一個字符就顯示一個字符,但是實際上情況是如下圖:


其實,輸入字符立即回顯是非緩衝或直接輸入的一個形式,它表示你所鍵入的字符對正在等待的程序立即變爲可用。相反,延遲迴顯是緩衝輸入的例子,這種情況下你所鍵入的字符塊被收集並存儲在一個被稱爲緩衝區的臨時存儲區域中。按下回車鍵可使你輸入的字符段對程序起作用。

緩衝輸入一般常用在文本程序內,當你輸入有錯誤時,就可以使用你的鍵盤更正修正錯誤。當最終按下回車鍵時,你就可以發送正確的輸入。

而在一些交互性的遊戲裏需要非緩衝輸入,如:遊戲裏你按下一個鍵時就要執行某個命令。

輸入緩衝分類:

完全緩衝:緩衝區被充滿時被清空(內容發送到其目的地)。這種類型的緩衝通常出現在文件輸入中。

行緩衝:遇到一個換行字符時被清空緩衝區。鍵盤的輸入是標準的行緩衝,因此按下回車鍵將清空緩衝區

(2)輸出緩衝區

每個輸出流都會管理一個緩衝區,用了保存程序讀寫的數據,這個和輸入緩衝區是一個道理的,但是輸出不一樣,我們是希望在程序結束是,輸出緩衝區中的內容都要被輸出去,所以會有緩衝區刷新,在下面這幾種情況會引起緩衝區的刷新(注意:如要程序異常終止,輸出緩衝區是不會被刷新的。當一個程序崩潰後,它所輸出的數據很可能停留在輸出緩衝區中等待打印。所以最好在每個輸出後加一個緩衝區刷新的操作):

程序正常結束,作爲main函數的return操作的一部分,緩衝刷新被執行。

緩衝區滿時,需要刷新緩衝,而後新的數據才能繼續寫入緩衝區。

我們可以使用操縱符endl來顯式刷新緩衝區。

在每個輸出之後,我們可以用操縱符unitbuf設置流的內部狀態,來清空緩衝區。默認情況下,對cerr是設置unitbuf的,因此寫到cerr的內容都是立即刷新的。

一個輸出流被關聯到另一個流。在這種情況下,當讀寫被關聯的流時,關聯到的流的緩衝區會被刷新,cin和cerr都關聯到cout。因此讀cin或寫cerr會導致cout的緩衝區被刷新(cin立即回顯的一個原因)。

IO庫中除了endl可以刷新緩衝區外,ends和flush也會刷新緩衝區。只是它們會有一點差別:

cout << "hi!" << endl; // 輸出 hi 和一個換行符,然後刷新緩衝區 
cout << "hi!" << flush; // 輸出hi,然後刷新緩衝區,不附加任何額外字符 
cout << "hi!" << ends; // 輸出hi和一個空字符。然後刷新緩衝區
1
2
3
unitbuf操作符,如果我們希望每次輸出操作後都要刷新緩衝區,那麼就可以使用:unitbuf,它就是告訴系統,以後每次進行輸出操作後都要指向flush操作,我們之前提到的cerr就是設置了unitbuf的

cout << unitbuf; // 所有輸出操作後都立即刷新緩衝區 
// 任何輸出都立即刷新,無緩衝 
cout << nounitbuf; // 回到正常的緩衝方式
1
2
3
4
點贊 4
————————————————
版權聲明:本文爲CSDN博主「Ouyang_Lianjun」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/qq_35644234/article/details/56679127

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章