?? c++12.dat
字號:
第十二章 輸入輸出流
第一節 輸入輸出流類
C++語言同C語言一樣,也不具有內部輸入輸出能力,這樣做的目的是為了最大限度地保證語言與平臺的無關性.計算機語言的輸入輸出功能都是與操作系統相關的,如果C++為某種操作系統實現內部輸入輸出功能,那它也就被限制在這個操作系統上了,這是我們所不希望的.
如果一個應用程序沒有輸入和輸出,那它也就沒有應用價值.在C++中,輸入輸出功能,是通過調用該操作系統的I/O庫來實現的.
scanf、printf都是C語言標準輸入輸出庫函數,不能否認,C語言的標準輸入輸出庫函數也是安全、高效的,為什么說C++的輸入輸出更安全高效呢?關鍵在于C++的輸入輸出與C的輸入輸出實現方法不同.
C++的輸入輸出是如何實現的?是不是依據面向對象的思想,把C的標準I/O庫封裝成類,然后進行處理呢?比如操作一個文件,我們想確保文件能夠安全地打開,及時地關閉,而不完全依賴用戶調用open()、close()函數,可以構造一個文件類,定義一個成員變量,作為文件指針,分別在構造函數和析構函數中打開、關閉文件.再進一步,可以在類中實現C的標準輸入輸出庫中所有的I/O函數,并把文件指針置為私有變量.從外面看來,這已經是一個封裝得很好的文件類.在某種意義上講,這種方法是相當有效的,我們也可以為標準I/O和存儲塊構造類似的類.
我們已經知道,C++是使用iostream流庫,并沒有使用C的輸入輸出庫,是不是iostream流庫更好呢?答案是肯定的.C的I/O庫函數主要用來處理基本數據類型(字符、整型、浮點數等),使用參數表進行數據傳輸,使用格式字符串指定數據類型和輸入輸出格式.它們在運行時對格式字符串進行語法分析,并據此對變量進行解釋.例如:
printf("a=%d\tb=%d\ta+b=%d\n",a,b,a+b);
C語言I/O庫的缺點是:
1. 即使只使用了解釋程序的一個功能,也要全部裝載.如上面的例子,我們要裝載整個包,包括解釋浮點數和字符串那部分程序段,無法減少程序的長度.
2. 雖然printf族函數已經優化得很好,但是,它是在運行期間進行解釋,如果能在編譯期間分析格式字符串里的變量,根據不同的類型調用各自的函數處理,運行會快得多,而且C++編譯期間的類型檢查會有助于我們發現錯誤.
3. 對于C++來說,printf不能被擴展是它最大的缺點.我們不能通過重載函數對它進行擴展,因為重載函數要有不同類型的參數,而printf族函數把類型信息隱藏在可變參數表和格式字符串中.
iostream是通過類的繼承,類成員函數的重載來實現的,利用類的可繼承性和多態性,使iostream類庫使用統一的函數接口操作標準I/O、文件、存儲塊等輸入輸出設備.通過函數重載,為每種內部數據類型定義了流輸入輸出函數,使得用戶可以用相同的格式對各種數據類型進行操作,編譯程序根據數據的類型自動選擇相應的輸入輸出函數,不必將所有函數一并加載.同時,iostream擁有了很好的擴展性,用戶通過重載還可以對自定義對象進行流的操作.因此,與標準C輸入輸出庫的各種各樣的函數相比,輸入輸出流更容易、更安全、更有效.
1.1 輸入輸出流類層次
iostream是一組C++類,用于實現面向對象模型的輸入輸出,可以提供無緩沖的(低級)和緩沖的I/O操作.在某些情況下,如果C++編譯器提供的iostream庫中沒有合適的輸入輸出函數可用,我們還可以利用類的繼承和多態特性來改進它們.
抽象流基類
ios 流基類
輸入流類
istream 普通輸入流類和用于其它輸入流的基類
ifstream 輸入文件流類
istream_withassign 用于cin的輸入流類
istrstream 輸入串流類
輸出流類
ostream 普通輸出流類和用于其它輸出流類的基類
ofstream 輸出文件流類
ostream_withassign 用于cout、cerr和clog的流類
ostrstream. 輸出串流類
輸入輸出流類
iostream 普通輸入輸出流類和用于其它輸入輸出流的基類
fstream 輸入輸出文件流類
strstream 輸入輸出串流類
stdiostream 用于標準輸入輸出文件的輸入輸出類
緩沖流類
streambuf 抽象緩沖流基類
filebuf 用于磁盤文件的緩沖流類
strstreambuf. 用于串的緩沖流類
stdiobuf 用于標準輸入輸出文件的緩沖流類
預定義流初始化類
iostream_init 預定義流初始化的類
其中,ios、istream、ostream和streambuf類構成了C++中iostream輸入輸出功能的基礎.
1.2標準輸入和輸出
輸出流類在iostream.h中預定義了四個全局的流對象:cout、cerr、clog和cin,用于標準輸出和輸入,cout和cin在程序設計中會經常用到.
cout流對象控制向控制臺(顯示器)的標準輸出,cin控制從控制臺(鍵盤)輸入.cerr與標準錯誤設備連在一起,是非緩沖輸出,也就是說插入到cerr的數據會被立刻顯示出來,非緩沖輸出可以迅速把出錯信息告知用戶.clog也是與標準錯誤設備連在一起的,但它是緩沖輸出.
一.標準輸出
程序1
#include <iostream.h>
void main()
{
float pi=3.14159;
cout<<"pi=";
cout<<pi;
cout<<endl;
}
程序的輸出結果為:
pi=3.14159
程序2
#include <iostream.h>
void main()
{
float pi=1.4;
cout.put('A');
cout.write((unsigned char*)&pi,sizeof(float));
}
二、標準輸入
#include <iostream.h>
void main()
{
int n;
cin>>n;
char ch;
cin>>ch;
float pi;
cin>>"pi=";
char str[20];
cin>>str;
cout<<"n="<<n<<endl;
cout<<"ch="<<ch<<endl;
cout<<"pi="<<pi<<endl;
cout<<"string="<<str<<endl;
}
當輸入是:5 c 3.14159 hello時,程序運行結果是:
n=5
ch=c
pi=3.14159
string=hello
1.3 操縱算子
操縱算子是插入到流中或從流中抽取出來、影響流的格式狀態的函數或對象.流的格式狀態由ios類定義,其中包括指定數據對象的基數,如十進制、八進制、十六進制等,還有輸出寬度、精度、填充字符等等.事實上,ios類有自己的成員函數可以設置、清除這些格式變量.操縱算子與這些成員函數的功能是重疊的,但是引入操縱算子為我們提供了很大的方便和表達能力,它們有助于提高程序的可讀性.
第二節 文件流
利用文件流操作打開一個文件,只需要建立一個對象,它的構造函數負責打開文件,當該對象生存期結束時,它會調用析構函數關閉文件.當然,我們也可以調用成員函數open()和close()進行文件的打開和關閉,下面這個例子說明了如何用文件流進行文件操作.
文件流類其實是輸入輸出流類的一部分,由于在實際中,會經常用到文件操作,所以我們把文件流類再單獨介紹.
一、打開文件
用文件流打開文件可以利用無參的構造函數,然后調用open():
ofstream outfile;
outfile.open("outfile", iosmode);
也可調用帶參數的構造函數,指定文件名和打開方式:
ofstream outfile("outfile", iosmode);
二、文件操作
由于iostream的設備無關性,構造了文件流以后.就可以象前面標準輸入輸出流的方法一樣使用了.
三、關閉文件
在文件操作結束時,可以用close()成員函數關閉該文件.
Outfile.close();
不過,在該文件流對象生存期結束時,對象也會自動調用析構函數來關閉文件.最好在文件操作結束時,關閉文件,這樣會使程序的可讀性更好.
第三節 字節流
字節流可直接與內存而不是與文件或標準輸出一起工作.我們可以用與標準輸出同樣的格式,操作內存里的數據(字節).如果我們想把數據放入字節流,可以建立一個ostrstream對象;如果想從字節流中提取數據,就建立一個istrstream對象.
3.1 輸入流
istrstream類支持一個字符數組作為源的輸入流.在構造istrstream對象前,必須存在一個字符數組,而且這個數組中已經填充了我們想要提取的字符.下面是兩個構造函數的原型:
istrstream::istrstream(char* buf);
istrstream::istrstream(char* buf, int size);
第一個構造函數取一個指向以"\0"作為結尾符的字符數組的指針,我們可以提取字節直至遇到"\0"為止.第二個構造函數還需要這個數組的大小,但不需要數組包含字符串的結尾符"\0",我們可以一直提取字節到buf[size-1],而不管是否遇到"\0".
3.2 輸出流
ostrstream類支持一個字符數組作為數據傳輸目的地的輸出流,它可以使用我們為它申請的存儲空間,這時字節在內存中被格式化;也可以使用自動分配的存儲空間.
我們為ostrstream申請存儲空間的方法是通過ostrstream有參的構造函數:
ostrstream(char*, int, int=ios::out);
第一個參數是緩沖區的指針,第二個參數是緩沖區的大小,第三個參數是打開模式.如果是缺省的模式,則從緩沖區頭部開始添加新的字符;如果打開模式是ios::ate或ios::app,則從緩沖區中的字符串的結尾符處開始添加新的字符 (結尾符不后移,只是被簡單地覆蓋,下面程序中os<<ends的作用就是在buf后面加上結尾符).
第四節 流錯誤處理
4.1 流狀態測試
在iostream中,每一個流對象都有一個表示操作是否成功的狀態位.
每一步操作后流的狀態有下面五種:
表1
good 流狀態正常.
end-of-file 表明輸入操作到達輸入序列尾部.
fail 表明出現了格式化問題或者不影響緩沖區的其他問題,如果fail位被清除,流還 是可用的.
bad 表明緩沖區出現錯誤,數據丟失.
hardfail 出現不可恢復性錯誤.
iostream提供了一些成員函數,如good()、fail()等來查詢當前流的狀態,下表列出了所有用于狀態查詢的成員函數.
表2
int rdstate() 返回當前狀態.
int good() 若未設置錯誤狀態時返回非零值.
int eof() 當end-of-file位設置時,返回非零值.通常在執行過程中遇到文件的 末尾時設置.不能從緩沖區讀入更多的字符,也不能向緩沖區輸出更多 的字符.
int fail() 當fail、bad或hardfail位設置時,返回非零值.
int bad() 當bad或hardfail位設置時,返回非零值.
int operator!() 當流狀態非正常時,返回非零值.
在對流進行操作時,我們應該先對流的狀態進行檢測,以確保流的狀態正常,通常的做法是:
if (!(cout << "Hello World !"))
handle_error();
使用插入或者抽取運算符的優點就是用戶可以把插入或者抽取操作成組進行.例如,
int n=5;
cout<<"n="<<n;
if (!cout) handle_error();
這樣可以使程序簡潔明了,但是隨之會產生一個問題:用戶不可能在每次流操作結束后檢測流的狀態.C++的例外可以解決這個問題,因此標準iostream允許使用例外處理流錯誤.
4.2 例外處理流錯誤
I/O流庫中允許使用例外對流錯誤進行處理.在ios類中添加了兩個成員函數:
void exceptions(iostate except_mask);
iostate exceptions();
第一個函數用來設置標志位,是流拋出相應的例外.這些標志位可以是eof、bad、fail或者它們的組合,第二個函數用來返回當前的標志.例
#include "iostream.h"
void main()
{
int a=5;
try
{
cout.exceptions(ios::failbit);
cout<<"a="<<a<<endl;
}
catch(ios_base::failure& excep)
{
cerr<<excep.what()<<endl;
}
}
在try模塊里cout調用exceptions()拋出一個例外,使catch模塊里的程序能夠執行,這一步僅僅是為了測試之用.
例外處理是C++語言的一個新特征,它提出了出錯處理更完美的方法,并使出錯處理程序和主代碼分離,從而簡化了出錯處理程序的編寫.例外處理的一般格式:
try {
//可能會生成例外的代碼段
} catch (type id1) {
//例外處理程序
}
關鍵字try引導的程序段為測試程序段,在測試程序段中生成的例外由catch引導的例外處理器捕獲并進行處理,catch帶的參數是要捕獲的例外類型.
當例外拋出時,如果被捕獲,主程序段立即中止執行,轉到例外處理程序中執行例外處理,處理結束并不返回到異常拋出的地方,通常的作法是報告錯誤,進行一些保護性工作,如關閉文件,然后退出函數段.
例外處理的內容很多,詳細了解請參考其它書籍.
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -