我們從一開始就一直在利用C++的輸入輸出在做著各種練習,輸入輸出是由iostream庫提供的,所以討論此標准庫是有必要的,它與C語言的stdio庫不同,它從一開始就是用多重繼承與虛擬繼承實現的面向對象的層次結構,作為一個c++的標准庫組件提供給程序員使用。
iostream為內置類型類型對象提供了輸入輸出支持,同時也支持文件的輸入輸出,類的設計者可以通過對iostream庫的擴展,來支持自定義類型的輸入輸出操作。
為什麼說要擴展才能提供支持呢?我們來一個示例。
#include <stdio.h>
#include <iostream>
using namespace std;
class Test
{
public:
Test(int a=0,int b=0)
{
Test::a=a;
Test::b=b;
}
int a;
int b;
};
int main()
{
Test t(100,50);
printf("%???",t);//不明確的輸出格式
scanf("%???",t);//不明確的輸入格式
cout<<t<<endl;//同樣不夠明確
cin>>t;//同樣不夠明確
system("pause");
}
由於自定義類的特殊性,在上面的代碼中,無論你使用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對象中。
<<a表示將a對象中存儲的數據拿出。
這些標准的流對象都有默認的所對應的設備,見下表:
圖中的意思表明cin對象的默認輸入設備是鍵盤,cout對象的默認輸出設備是顯示器屏幕。
那麼原理上C++有是如何利用cin/cout對象與左移和右移運算符重載來實現輸入輸出的呢?
下面我們以輸出為例,說明其實現原理:
cout是ostream類的對象,因為它所指向的是標准設備(顯示器屏幕),所以它在iostream頭文件中作為全局對象進行定義。
ostream cout(stdout);//其默認指向的C中的標准設備名,作為其構造函數的參數使用。
在iostream.h頭文件中,ostream類對應每個基本數據類型都有其友元函數對左移操作符進行了友元函數的重載。
ostream& operator<<(ostream &temp,int source);
ostream& operator<<(ostream &temp,char *ps);
。。。。等等
一句輸出語句:cout<<"www.cndev-lab.com";,事實上調用的就是ostream& operator<<(ostream &temp,char *ps);這個運算符重載函數,由於返回的是流對象的引用,引用可以作為左值使用,所以當程序中有類似cout<<"www.cndev-lab.com"<<"中國軟件開發實驗室";這樣的語句出現的時候,就能夠構成連續輸出。
由於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就是以只讀和隱含屬性打開文件。
示例代碼如下:
//程序作者:管寧
//站點:www.cndev-lab.com
//所有稿件均有版權,如要轉載,請務必著名出處和作者
#include <fstream>
using namespace std;
int main()
{
ofstream myfile("c:\\1.txt",ios::out|ios::trunc,0);
myfile<<"中國軟件開發實驗室"<<endl<<"網址:"<<"www.cndev-lab.com";
myfile.close()
system("pause");
}
文件使用完後可以使用close成員函數關閉文件。
ios::app為追加模式,在使用追加模式的時候同時進行文件狀態的判斷是一個比較好的習慣。
示例如下:
//程序作者:管寧
//站點:www.cndev-lab.com
//所有稿件均有版權,如要轉載,請務必著名出處和作者
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
ofstream myfile("c:\\1.txt",ios::app,0);
if(!myfile)//或者寫成myfile.fail()
{
cout<<"文件打開失敗,目標文件狀態可能為只讀!";
system("pause");
exit(1);
}
myfile<<"中國軟件開發實驗室"<<endl<<"網址:"<<"www.cndev-lab.com"<<endl;
myfile.close();
}
在定義ifstream和ofstream類對象的時候,我們也可以不指定文件。以後可以通過成員函數open()顯式的把一個文件連接到一個類對象上。
例如:
//程序作者:管寧
//站點: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<<"中國軟件開發實驗室"<<endl<<"網址:"<<"www.cndev-lab.com"<<endl;
myfile.close();
}
下面我們來看一下是如何利用ifstream類對象,將文件中的數據讀取出來,然後再輸出到標准設備中的例子。
代碼如下:
//程序作者:管寧
//站點:www.cndev-lab.com
//所有稿件均有版權,如要轉載,請務必著名出處和作者
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main()
{
ifstream myfile;
myfile.open("c:\\1.txt",ios::in,0);
if(!myfile)
{
cout<<"文件讀錯誤";
system("pause");
exit(1);
}
char ch;
string content;
while(myfile.get(ch))
{
content+=ch;
cout.put(ch);//cout<<ch;這麼寫也是可以的
}
myfile.close();
cout<<content;
system("pause");
}
上例中,我們利用成員函數get(),逐一的讀取文件中的有效字符,再利用put()成員函數,將文件中的數據通過循環逐一輸出到標准設備(屏幕)上,get()成員函數會在文件讀到默尾的時候返回假值,所以我們可以利用它的這個特性作為while循環的終止條件,我們同時也在上例中引入了C++風格的字符串類型string,在循環讀取的時候逐一保存到content中,要使用string類型,必須包含string.h的頭文件。
我們在簡單介紹過ofstream類和ifstream類後,我們再來看一下fstream類,fstream類是由iostream派生而來,fstream類對象可以同對文件進行讀寫操作。
示例代碼如下:
//程序作者:管寧
//站點:www.cndev-lab.com
//所有稿件均有版權,如要轉載,請務必著名出處和作者
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
fstream myfile;
myfile.open("c:\\1.txt",ios::out|ios::app,0);
if(!myfile)
{
cout<<"文件寫錯誤,文件屬性可能為只讀!"<<endl;
system("pause");
exit(1);
}
myfile<<"中國軟件開發實驗室"<<endl<<"網址:"<<"www.cndev-lab.com"<<endl;
myfile.close();
myfile.open("c:\\1.txt",ios::in,0);
if(!myfile)
{
cout<<"文件讀錯誤,文件可能丟失!"<<endl;
system("pause");
exit(1);
}
char ch;
while(myfile.get(ch))
{
cout.put(ch);
}
myfile.close();
system("pause");
}
由於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所指向的內存空間並以結尾的字符串。
下面的示例代碼就是利用istrstream類創建類對象,制定流輸入設備為字符串數組,通過它向一個字符型對象輸入數據。
代碼如下:
//程序作者:管寧
//站點:www.cndev-lab.com
//所有稿件均有版權,如要轉載,請務必著名出處和作者
#include <iostream>
#include <strstream>
using namespace std;
int main()
{
char *name = "www.cndev-lab.com";
int arraysize = strlen(name)+1;
istrstream is(name,arraysize);
char temp;
is>>temp;
cout<<temp;
system("pause");
}
類ostrstream用於執行串流的輸出,它的構造函數如下所示:
ostrstream::ostrstream(char *_Ptr,int streamsize,int Mode = ios::out);
第一個參數是字符數組,第二個是說明數組的大小,第三個參數是指打開方式。
我們來一個示例代碼:
#include <iostream>
#include <strstream>
using namespace std;
int main()
{
int arraysize=1;
char *pbuffer=new char[arraysize];
ostrstream ostr(pbuffer,arraysize,ios::out);
ostr<<arraysize<<ends;//使用ostrstream輸出到流對象的時候,要用ends結束字符串
cout<<pbuffer;
delete[] pbuffer;
system("pause");
}
上面的代碼中,我們創建一個c風格的串流輸出對象ostr,我們將arraysize內的數據成功的以字符串的形式輸出到了ostr對象所指向的pbuffer指針的堆空間中,pbuffer也正是我們要輸出的字符串數組,在結尾要使用ends結束字符串,如果不這麼做就有溢出的危險。