C++的iostream標準庫介紹以及對左移與右移運算子的過載
我們從一開始就一直在利用C++的輸入輸出在做著各種練習,輸入輸出是由iostream庫提供的,所以討論此標準庫是有必要的,它與C語言的 stdio庫不同,它從一開始就是用多重繼承與虛擬繼承實現的面向物件的層次結構,作為一個c++的標準庫元件提供給程式設計師使用。
iostream為內建型別型別物件提供了輸入輸出支援,同時也支援檔案的輸入輸出,類的設計者可以通過對iostream庫的擴充套件,來支援自定義型別的輸入輸出操作。
為什麼說要擴展才能提供支援呢?我們來一個示例。
C++ 程式碼
|
由於自定義類的特殊性,在上面的程式碼中,無論你使用c風格的輸入輸出,或者是c++的輸入輸出都不是不明確的一個表示,由於c語言沒有運算子過載機制, 導致stdio庫的不可擴充性,讓我們無法讓printf()和scanf()支援對自定義類物件的擴充識別,而c++是可以通過運算子過載機制擴充 iostream庫的,使系統能能夠識別自定義型別,從而讓輸入輸出明確的知道他們該幹什麼,格式是什麼。
在上例中我們之所以用printf與cout進行對比目的是為了告訴大家,C與C++處理輸入輸出的根本不同,我們從c遠的輸入輸出可以很明顯看出是函式呼叫方式,而c++的則是物件模式,cout和cin是ostream類和istream類的物件
C++中的iostream庫主要包含下圖所示的幾個標頭檔案:
我們所熟悉的輸入輸出操作分別是由istream(輸入流)和ostream(輸出流)這兩個類提供的,為了允許雙向的輸入/輸出,由istream和ostream派生出了iostream類。
類的繼承關係見下圖:
iostream庫定義了以下三個標準流物件:
1.cin,表示標準輸入(standard input)的istream類物件。cin使我們可以從裝置讀如資料。
2.cout,表示標準輸出(standard output)的ostream類物件。cout使我們可以向裝置輸出或者寫資料。
3.cerr,表示標準錯誤(standard error)的osttream類物件。cerr是匯出程式錯誤訊息的地方,它只能允許向螢幕裝置寫資料。
輸出主要由過載的左移操作符(<<)來完成,輸入主要由過載的右移操作符(>>)完成。
>>a表示將資料放入a物件中。
<
這些標準的流物件都有預設的所對應的裝置,見下表:
由於iostream庫不光支援物件的輸入輸出,同時也支援檔案流的輸入輸出,所以在詳細講解左移與右移運算子過載只前,我們有必要先對檔案的輸入輸出以及輸入輸出的控制符有所瞭解。
和檔案有關係的輸入輸出類主要在fstream.h這個標頭檔案中被定義,在這個標頭檔案中主要被定義了三個類,由這三個類控制對檔案的各種輸入輸出操作, 他們分別是ifstream、ofstream、fstream,其中fstream類是由iostream類派生而來,他們之間的繼承關係見下圖所示。
由於檔案裝置並不像顯示器螢幕與鍵盤那樣是標準預設裝置,所以它在fstream.h標頭檔案中是沒有像cout那樣預先定義的全域性物件,所以我們必須自 己定義一個該類的物件,我們要以檔案作為裝置向檔案輸出資訊(也就是向檔案寫資料),那麼就應該使用ofstream類。
ofstream類的預設建構函式原形為:
ofstream::ofstream(const char *filename,int mode = ios::out,int openprot = filebuf::openprot);
filename: 要開啟的檔名
mode: 要開啟檔案的方式
prot: 開啟檔案的屬性
其中mode和openprot這兩個引數的可選項表見下表:
mode屬性表
ios::app: 以追加的方式開啟檔案
ios::ate: 檔案開啟後定位到檔案尾,ios:app就包含有此屬性
ios::binary: 以二進位制方式開啟檔案,預設的方式是文字方式。兩種方式的區別見前文
ios::in: 檔案以輸入方式開啟
ios::out: 檔案以輸出方式開啟
ios::trunc: 如果檔案存在,把檔案長度設為0
可以用“或”把以上屬性連線起來,如ios::out|ios::binary。
openprot屬性表:
0:普通檔案,開啟訪問
1:只讀檔案
2:隱含檔案
4:系統檔案
可以用“或”或者“+”把以上屬性連線起來 ,如3或1|2就是以只讀和隱含屬性開啟檔案。
示例程式碼如下:
C++ 程式碼
|
檔案使用完後可以使用close成員函式關閉檔案。
ios::app為追加模式,在使用追加模式的時候同時進行檔案狀態的判斷是一個比較好的習慣。
示例如下:
C++ 程式碼
|
在定義ifstream和ofstream類物件的時候,我們也可以不指定檔案。以後可以通過成員函式open()顯式的把一個檔案連線到一個類物件上。
例如:
C++ 程式碼
//程式作者:管寧 //站點:www.cndev-lab.com //所有稿件均有版權,如要轉載,請務必著名出處和作者 #include <iostream> #include <fstream> using namespace std; int main() { ofstream myfile; myfile.open("c://1.txt",ios::out|ios::app,0); if(!myfile)//或者寫成myfile.fail() { cout<<"檔案建立失敗,磁碟不可寫或者檔案為只讀!"; system("pause"); exit(1); } myfile<<"中國軟體開發實驗室"<<<"網址:"<<"WWW.CNDEV-LAB.COM"< myfile.close(); } |
下面我們來看一下是如何利用ifstream類物件,將檔案中的資料讀取出來,然後再輸出到標準裝置中的例子。
程式碼如下:
C++ 程式碼
|
上例中,我們利用成員函式get(),逐一的讀取檔案中的有效字元,再利用put()成員函式,將檔案中的資料通過迴圈逐一輸出到標準裝置(螢幕)上, get()成員函式會在檔案讀到默尾的時候返回假值,所以我們可以利用它的這個特性作為while迴圈的終止條件,我們同時也在上例中引入了C++風格的 字串型別string,在迴圈讀取的時候逐一儲存到content中,要使用string型別,必須包含string.h的標頭檔案。
我們在簡單介紹過ofstream類和ifstream類後,我們再來看一下fstream類,fstream類是由iostream派生而來,fstream類物件可以同對檔案進行讀寫操作。
C++ 程式碼
|
由於fstream類可以對檔案同時進行讀寫操作,所以對它的物件進行初始話的時候一定要顯式的指定mode和openprot引數。
接下來我們來學習一下串流類的基礎知識,什麼叫串流類?
簡單的理解就是能夠控制字串型別物件進行輸入輸出的類,C++不光可以支援C++風格的字串流控制,還可以支援C風格的字串流控制。
我們先看看看C++是如何對C風格的字串流進行控制的,C中的字串其實也就是字元陣列,字元陣列內的資料在記憶體中的位置的排列是連續的,我們通常用 char str[size]或者char *str的方式宣告建立C風格字元陣列,為了能讓字元陣列作為裝置並提供輸入輸出操作,C++引入了ostrstream、istrstream、 strstream這三個類,要使用他們建立物件就必須包含strstream.h標頭檔案。
istrstream類用於執行C風格的串流的輸入操作,也就是以字串陣列作為輸入裝置。
ostrstream類用於執行C風格的串流的輸出操作,也就是一字串陣列作為輸出裝置。
strstream類同時可以支援C風格的串流的輸入輸出操作。
istrstream類是從istream(輸入流類)和strstreambase(字串流基類)派生而來,ostrstream是從 ostream(輸出流類)和strstreambase(字串流基類)派生而來,strstream則是從iostream(輸入輸出流類)和和 strstreambase(字串流基類)派生而來。
他們的繼承關係如下圖所示:
串流同樣不是標準裝置,不會有預先定義好的全域性物件,所以不能直接操作,需要通過建構函式建立物件。
類istrstream的建構函式原形如下:
istrstream::istrstream(const char *str,int size);
引數1表示字串陣列,而引數2表示陣列大小,當size為0時,表示istrstream類物件直接連線到由str所指向的記憶體空間並以/0結尾的字串。
下面的示例程式碼就是利用istrstream類建立類物件,制定流輸入裝置為字串陣列,通過它向一個字元型物件輸入資料。
程式碼如下:
C++ 程式碼
|
ostrstream::ostrstream(char *_Ptr,int streamsize,int Mode = ios::out);
第一個引數是字元陣列,第二個是說明陣列的大小,第三個引數是指開啟方式。
我們來一個示例程式碼:
C++ 程式碼
|
上面的程式碼中,我們建立一個c風格的串流輸出物件ostr,我們將arraysize內的資料成功的以字串的形式輸出到了ostr物件所指向的pbuffer指標的堆空間中,pbuffer也正是我們要輸出的字串陣列,在結尾要使用ends結束字串,如果不這麼做就有溢位的危險。
接下來我們繼續看一下C++風格的串流控制,C++引入了ostringstream、istringstream、stringstream這三個類,要使用他們建立物件就必須包含sstream.h標頭檔案。
istringstream類用於執行C++風格的串流的輸入操作。
ostringstream類用於執行C++風格的串流的輸出操作。
stringstream類同時可以支援C++風格的串流的輸入輸出操作。
istringstream類是從istream(輸入流類)和stringstreambase(c++字串流基類)派生而來, ostringstream是從ostream(輸出流類)和stringstreambase(c++字串流基類)派生而來, stringstream則是從iostream(輸入輸出流類)和和stringstreambase(c++字串流基類)派生而來。
他們的繼承關係如下圖所示:
istringstream是由一個string物件構造而來,istringstream類從一個string物件讀取字元。
istringstream的建構函式原形如下:
istringstream::istringstream(string str);
示例程式碼如下:
C++ 程式碼
|
上例中,構造字串流的時候,空格會成為字串引數的內部分界,例子中對a,b物件的輸入"賦值"操作證明了這一點,字串的空格成為了整型資料與浮點型資料的分解點,利用分界獲取的方法我們事實上完成了字串到整型物件與浮點型物件的拆分轉換過程。
str()成員函式的使用可以讓istringstream物件返回一個string字串(例如本例中的輸出操作(cout< ostringstream同樣是由一個string物件構造而來,ostringstream類向一個string插入字元。
ostringstream的建構函式原形如下:
ostringstream::ostringstream(string str);
示例程式碼如下:
C++ 程式碼
|
在上例程式碼中,我們通過put()或者左移操作符可以不斷向ostr插入單個字元或者是字串,通過str()函式返回增長過後的完整字串資料,但值 得注意的一點是,當構造的時候物件內已經存在字串資料的時候,那麼增長操作的時候不會從結尾開始增加,而是修改原有資料,超出的部分增長。
對於stringstream了來說,不用我多說,大家也已經知道它是用於C++風格的字串的輸入輸出的。
stringstream的建構函式原形如下:
stringstream::stringstream(string str);
C++ 程式碼
|
除此而外,stringstream類的物件我們還常用它進行string與各種內建型別資料之間的轉換。
示例程式碼如下:
接下來我們來學習一下輸入/輸出的狀態標誌的相關知識,C++中負責的輸入/輸出的系統包括了關於每一個輸入/輸出操作的結果的記錄資訊。這些當前的狀態資訊被包含在io_state型別的物件中。io_state是一個列舉型別(就像open_mode一樣),以下便是它包含的值。
goodbit 無錯誤
Eofbit 已到達檔案尾
failbit 非致命的輸入/輸出錯誤,可挽回
badbit 致命的輸入/輸出錯誤,無法挽回
有兩種方法可以獲得輸入/輸出的狀態資訊。一種方法是通過呼叫rdstate()函式,它將返回當前狀態的錯誤標記。例如,假如沒有任何錯誤,則rdstate()會返回goodbit.
下例示例,表示出了rdstate()的用法:
C++ 程式碼
|
另一種方法則是使用下面任何一個函式來檢測相應的輸入/輸出狀態:
bool bad();
bool eof();
bool fail();
bool good();
下例示例,表示出了上面各成員函式的用法:
C++ 程式碼
|
如果錯誤發生,那麼流狀態既被標記為錯誤,你必須清除這些錯誤狀態,以使你的程式能正確適當地繼續執行。要清除錯誤狀態,需使用clear()函式。此函式帶一個引數,它是你將要設為當前狀態的標誌值。,只要將ios::goodbit作為實參。
示例程式碼如下:
C++ 程式碼
|
通常當我們發現輸入有錯又需要改正的時候,使用clear()更改標記為正確後,同時也需要使用get()成員函式清除輸入緩衝區,以達到重複輸入的目的。
示例程式碼如下:
C++ 程式碼
|
最後再給出一個對檔案流錯誤標記處理的例子,鞏固學習,程式碼如下:
C++ 程式碼
|
C語言提供了格式化輸入輸出的方法,C++也同樣,但是C++的控制符使用起來更為簡單方便,在c++下有兩中方法控制格式化輸入輸出。
1.有流物件的成員函式。
例如,下列程式以成員函式的方式控制輸出的精度:
C++ 程式碼
|
2.使用C++輸入輸出控制符,控制符是在拖檔案iomanip.h中定義的物件,與成員函式有一樣的效果,控制符不必像成員函式學那樣單獨呼叫,它可以直接插入流中使用。
例如,下列程式以控制符的方式控制輸出的精度:
C++ 程式碼
|
下表我們列出了一些比較常用的控制符號,由於篇幅有限讀者請根據自己的需要查閱相關書籍:
對於iostream標準庫來說包含了眾多的成員函式,各函式都有其自身的作用,篇幅問題筆者在這裡不能一一說明例舉,由於標準輸入物件cin 提供輸入的時候會自動以空格作為分界,給我們獲取一行帶有空格的完整字串帶來了困難,在這裡補充一個非常用有的成員函式----getline()。
其函式原型為:
getlin(chiar *str,int size,char='/n');
第一個引數是字元陣列,用於存放整行文字,第二個引數讀取的最大字元個數,第三個引數為作為分界界限的字元,預設識是/n,換行符。
示例程式碼如下:
C++ 程式碼
|
通過上面內容的學習,我們對i/o有了一些基本點基本的認識,現在是該切入正題的時候了,詳細學習一下,如何過載左移與右移操作符。
先說左移(<<)操作符,也就是我們常說的輸出操作符。
對於自定義類來說,過載左移操作符的方法我們常使用類的友元方式進行操作。
示例程式碼如下:
C++ 程式碼
|
上例程式碼中,我們對void outmembers(ostream &out)的引數使用ostream定義主要是為了可以向它傳遞任何ostream類物件不光是cout也可以是ofstrem或者是 ostrstream和ostringstream類物件,做到通用性。
過載運算子,我們知道可以是非成員方式也可以是成員方式的,對於<<來說同樣也可以是成員方式,但我十分不推薦這麼做,因為對於類的成員函式來說,第一個引數始終是會被隱藏的,而且一定是當前類型別的。
下面的示例程式碼就是將上面的<<過載函式修改成成員方式的做法:
C++ 程式碼
|
從程式碼實現上,我們將函式修改成了ostream& operator <<(ostream &out),迫不得已將ostream型別的引用引數放到了後面,這是因為,成員方式運算子過載函式第一個引數會被隱藏,而且一定是當前類型別 的,這和ostream型別衝突了。由此我們在使用cout輸出的時候就必須寫成a<
為了鞏固學習,下面我們以fstream物件輸出為例做一個練習。
程式碼如下:
C++ 程式碼
|
對於左移運算子過載函式來說,由於不推薦使用成員方式,那麼使用非成員方式在類有多重繼承的情況下,就不能使用虛擬函式進行左移運算子過載的區分,為了達到 能夠區分顯示的目的,給每個類分別新增不同的虛擬函式是必要的。 示例程式碼如下: C++ 程式碼 //程式作者:管寧 //站點:www.cndev-lab.com //所有稿件均有版權,如要轉載,請務必著名出處和作者 #include #include using namespace std; class Student { public: Student(int age = 0,char *name = "/0") { Student::age = age; strcpy(Student::name,name); } virtual void outmembers(ostream &out) = 0; friend ostream& operator <<(ostream& ,Student&); protected: int age; char name[50]; }; ostream& operator <<(ostream& out,Student &temp) { temp.outmembers(out); return out; } class Academician:public Student { public: Academician(int age = 0,char *name = "/0",char *speciality="/0"):Student(age,name) { strcpy(Academician::speciality,speciality); } 對於左移運算子過載函式來說,由於不推薦使用成員方式,那麼使用非成員方式在類有多重繼承的情況下,就不能使用虛擬函式進行左移運算子過載的區分,為了 達到能夠區分顯示的目的,給每個類分別新增不同的虛擬函式是必要的。
示例程式碼如下:
C++ 程式碼
|