Thinking in C++ 2 筆記 2
作者:lester2008-01-11 18:07:12
標簽:
雜談標準C++庫(98版本) -- 預計2008年出的第二版標準,可能會使得下面一些東西發(fā)生變化,比如string的[]也可以拋出異常了。
=======================================================================
二、輸入輸出流
=======================================================================
1. 自定義的 插入符 一般采用友元函數(shù)。
2. 接受用戶格式化輸入的一般模式是:
先把用戶的輸入緩沖到一個地兒,然后掃描并檢查之,確認格式無誤后,再用istream對象對這塊內(nèi)存進行格式化輸入。
不要用老方法直接用cin得到用戶輸入。
(一)格式化函數(shù):
1. 格式化的操作符:
<<
>> 默認讀到空格,或者不再是該類型為止。
“間隔提取”代碼示例:
std::istream& operator>>(std::istream& is, const char*s) {
if (s ==NULL) return is;
std::stringstr(s);
returnis.ignore(str.length());
}
2. 格式化操作算子:
算子的作用:暫時改變流的狀態(tài)!!,直到你再改為其他的。
有一組函數(shù)操作等于操作算子,但用起來麻煩,我沒看。
(1)#include <iostream>:不帶參數(shù)的操作算子。
例子:cout<< left << ...將流的每次輸出改為左對齊。
cout << hex << ... 按16進制 (oct 八進制,dec 十進制)
(2)#include <iomanip>: 帶參數(shù)的操作算子。
例子:
cout << setw(4) << ...將每次輸出的寬度(稱為域?qū)挘┰O為4,意思是:可以超過4,但不足的話,將以填充字符填滿4個。
cout << setfill(‘0‘) << ... 設置和setw()配套的填充字符。
比如:怎樣輸出09-08-2007? 因為不確定月份和日期是1位還是2位,所以,這里的技巧是:設定域?qū)?,并用填充字符?。?div style="height:15px;">
(二)非格式化函數(shù):
1. 按行輸入:
應該用到三個中的一個:
(1) std::getline(istream& i, string& s):這個是首選,原因是讀到s中,就不用關心分配的內(nèi)存不夠的問題。
(2) 成員getline(char*buf, int bufsize, int 行結(jié)尾符=‘\n‘) : 這個參數(shù)意義很明確了,因為讀到buf中,所以會在末尾加0。
以上兩個getline都會讀取并拋棄行結(jié)尾符,所以下一次getline會讀下一行。
(3) 成員get(char *buf,int bufsize, int 行結(jié)尾符=‘\n‘) : 這個參數(shù)和getline一樣,不同的是它不去讀取并拋棄行結(jié)尾符!所以調(diào)用一次之后,無論你調(diào)用多少此get,它都還在第一行的行結(jié)尾符之前。
NOTICE: 和C庫的風格一樣,上面(2)(3)中的bufsize,將限制最大獲取長度為bufsize-1,最后一個字節(jié)總會被置0的??!
2. 讀原始字節(jié):
應該用到兩個中的一個:
(1) 成員intget(): 這個是重載版本的get,它一個字節(jié)一個字節(jié)讀取,包括換行符號和EOF,等效于c庫的fgetc()
(2) 成員 read(char *buf,int read_howmany_bytes):等效于c庫的read,好處一樣,就是可以一次讀取多個字節(jié),而get只有一個。
注意:按行輸出和輸出原始字節(jié),和上面類似了。
(三)判斷流狀態(tài):
1. 必須判斷本次流操作是否成功:————直接將流對象用于bool表達式
原理:大部分流的操作,都是返回*this,即流對象本身。而流出現(xiàn)在bool表達式里,則會被自動類型轉(zhuǎn)換為其good()的值。而good()的意義如2所述。
(1)判斷“打開”及“打開模式”是否正常:
ifstream is("sth.txt");
if(!is) cout << "open error!" << endl;
(2)判斷文件尾:
while(myStream.getline(dest, 100)) {
cout<< dest;
}
(3)判斷seek的結(jié)果是否正常:
if(!seek(-5, ios::end)) cout << "seek error!" <<endl;
2.少數(shù)情況下,我們需要詳細了解流的狀態(tài),那么有四個操作可以入手:
good():當下面三個狀態(tài)都正常的時候,才為true。
eof():流到文件尾,設置為false
fail():eof() || bad()|| 流遇到了非致命性錯誤(如格式化輸入時遇到不合要求的類型,流可以繼續(xù)使用)
bad():流遇到了致命性錯誤,無法再使用。多半是物理上的。
3. clear():
NOTICE??!:上訴3個狀態(tài)——eof,fail,bad,一旦設置,會導致很多I/O操作失效?。”热缒銜l(fā)現(xiàn)fstream讀到EOF后,你再試圖去寫,但是無效,這不是因為你忘了用seekp重定位,因為你確實重定位了啊?;蛘吣惆l(fā)現(xiàn)seekp失敗后,再寫怎么不行啊。這時候,你應該先手動清除:myStream.clear()。因此:必須判斷本次I/O操作是否成功(除非你肯定一定成功),若不成功,一定要調(diào)用clear清除錯誤狀態(tài),再進行下一步操作。
(四)設置流應拋出的異常:
建議設置:myStream.exceptions(ios::badbit)
這樣在badbit被設置(一般是物理錯誤)時拋出異常。但是ios::failbit,ios::eofbit還是不要拋出異常的好。
(五)寫回文件和關閉文件:
如果你僅僅想將c++緩沖區(qū)的東西輸出到內(nèi)核,那么請用如 fcout <<flush;
如果你不僅僅想這樣做,還想關閉文件,那直接關閉就可以了fcout.close()。(或等著對象到出了作用范圍)
#include <fstream>
ifstream ofstream fstream
(一)如何“打開”多個文件 以及如何“關閉”文件:
如果多個文件是先后打開,那么用一個流對象就可以了。用完一個,myStream.close(),然后再myStream.open("新文件")。
而關閉文件,除非你要打開新文件,否則是不必要顯示的close的,因為stream對象會在析構函數(shù)里面關閉。但是,如果close()函數(shù)可能拋出異常的話(不確定),那么它其實是提供一個途徑然用戶顯示調(diào)用close來捕獲可能的異常,見effective c++。
(二)打開模式:
1. iso::binary
首先聲明:這個選項是專為windows系統(tǒng)設計的,以下所有效果不存在于linux下,因為linux不區(qū)分二進制和文本文件,統(tǒng)一看成二進制,不做任何處理的給用戶,或是寫入文件。
針對windows的換行為“回車+換行“(\r\n)而設計,效果為:
- 如果以ios::binary方式打開:就是最自然的處理方式,文件的所有字節(jié)都展現(xiàn)在你面前,包括\r。
- 如果以默認方式打開(即所謂ASCII模式):
(1)“讀”:所有的\r\n或者\r都被先轉(zhuǎn)換為了\n。你可以用get,read等任何讀的函數(shù),以及seekg設定游標位置,你會很正常的使用這些函數(shù)在似乎完全沒有\(zhòng)r的世界里翱翔。
(2)“寫”:就不像‘讀’時那么正常了:
a.任何寫的函數(shù)(put,write等),只要寫出的內(nèi)容包含‘\n‘,則實際上會被自動轉(zhuǎn)換為‘\r\n‘輸出。
b.如果你用seekp設定一個寫出的位置,那將是包含\r情況下的絕對位置。瞧,和(1)多么不一致!這點一定要小心?。。?!
NOTICE: 但不管是以上哪種方式打開,tellg()和tellp()返回的永遠是包含\r情況下的絕對位置。因此你若用默認方式,用get()讀,并用tellg()打印流的當前位置,你會發(fā)現(xiàn)有了一個跳躍。
*** 建議 ***:考慮到移植性,建議用ios::binary,因為linux可能操作windows的文件,windows也可能操作linux的文件。如果你用默認文本方式,則linux和windows打開一個含\r的文件的話,則在linux上和windows上看到的是不一樣的內(nèi)容了,則代碼很可能就不同了。
如果你用的是C庫,請在模式后加個b,表示binary。如:FILE*fp = fopen("rr.txt", "r+b"); 否則C庫也默認是文本方式。
2. ios::trunc
NOTICE?。。寒斒褂胻runc的時候,不要用ios::app或ios::ate,不然會有不可預知的效果。
3. ios::app 和 ios::ate
兩個都是打開文件時把指針定位到文件尾——即:EOF處!,但是不同的是,ios::app只能和ios::out連用(或者當前流是ofstream),確保永遠只在文件尾添加,不管你用seek到哪兒。但若你用fstream,有讀的能力的話,你用seek可以影響讀的位置。
************** 標志對 fstream, ifstream 和 ofstream 的效果 ****************
4. 對于fstream:所有的標志必須指明,否則效果相反!
NOTICE!!:從輸出切換到輸入,或者從輸入切換到輸出,必須用seekg或seekp重定位指針到你要操作的地兒,要不然操作完全無效。對于fstream來講,并不是有兩個指針,而是同樣只有一個指針,所以seekg和seekp效果一樣,都是對一個指針操作。
(1)要讀或要寫,必須寫明ios::in或ios::out
(2)默認不會截斷的,否則必須指明截斷ios::trunc
(3)默認不會到末尾的,否則必須ios::app或ios::ate
5. 對于ofstream:這個和fstream一樣,啥標志都能用!當它和 ios::in連用時表示不截斷,并把指針定位在文件頭,否則默認是截斷的。
不要妄想那它來輸入,因為ofstream沒有輸入函數(shù)。
6. 對于ifstream:請只選用與兩個標志,in能沾邊的:ios::ate和ios::binary。
你當然可以選其他的,但可能在打開文件時就會得到一個錯誤流,或者在操作時得到錯誤流。
并且不要妄想那它來輸出,因為ifstream沒有輸出函數(shù)。
**************** 打開模式 特別: “一個互斥,一個聯(lián)合”******************
ios::trunc請 和 ios::app/ate 互斥使用
ios::app請一定和ios::out聯(lián)合使用
(三)流定位:
tellg, seekg: seekg(offset, direction), 注意就是ios::end,是代表的EOF,所以(0, ios::end)返回的是EOF。
tellp, seekp: 同上。
(四)高效完成流的復制:
如果你問:”流的復制有啥用???“,那是因為你沒考慮到文件內(nèi)容在“文件”,“字符串”,和“終端”這三種文件間怎樣能高效轉(zhuǎn)移。
傳統(tǒng)的C做法,只能“一邊提取一邊寫入”,但是C++提供了更方便的做法,來將文件內(nèi)容從一個輸入流導向一個輸出流:
ifstreamin("rr.txt");
ofstreamout("rrw.txt");
out <<in.rdbuf(); //如果out換成cout,那么可以非常方便的往屏幕上打印輸出。
就行了。關鍵是:每個流對象都有其buffer,用rdbuf()可提取,并可作為輸出流的參數(shù)。
注意的是:從rdbuf中能得到的文件內(nèi)容為當前指針位置之后的,而調(diào)用一次rdbuf后,指針便到了末尾。
(五)ungetc()的C++做法:
有時,我們只想看看輸入流的下一個是什么,可以用get()得到后,再putback()(=ungetc())
或者用peek(),它不從流中提取,只是復制,不影響下一次get()。
(六)字節(jié)序:讀寫“字節(jié)流”時要注意的:
a. 概念:
Little-Endian和Big-Endian,是說系統(tǒng)和與他配套的C/C++,怎樣在內(nèi)存中放置一個內(nèi)置類型;和怎么將一段內(nèi)存看成內(nèi)置類型。
首先明白,對于這樣寫:0x12345678, 左邊都叫做MSB,右邊的都叫做LSB。既然是MSB,就是說,當這樣寫給你看時,左邊的是權值更高的位。
再看看內(nèi)存:一般變量放棧里面,我們看看棧:當有一個buf指針時,在任何當今的機器上,都是這樣放置的:
棧底 (高地址)
----------
buf[3]
buf[2]
buf[1]
buf[0]
----------
棧頂 (低地址)
在上訴兩點的基礎上,看看Little-Endian和Big-Endian
Little-Endian: Intel平臺上的系統(tǒng)一般采用之 —— LSB放低地址
棧底 (高地址)
---------------
buf[3] (0x12) -- 高位
buf[2] (0x34)
buf[1] (0x56)
buf[0] (0x78) -- 低位
---------------
棧頂 (低地址)
Big-Endian: SPARK等平臺上的系統(tǒng)和Java這種“中間件” —— MSB放低地址
棧底 (高地址)
---------------
buf[3] (0x78) -- 低位
buf[2] (0x56)
buf[1] (0x34)
buf[0] (0x12) -- 高位
---------------
棧頂 (低地址)
然后再回頭看看這句話,就明白概念了:
“怎樣在內(nèi)存中放置一個內(nèi)置類型;和怎么將一段內(nèi)存看成內(nèi)置類型。”
再明確指出一點:這種放置相關性,只針對一個類型。而其他的任何只關心字節(jié)的函數(shù),在任何平臺上,比如從文件讀取字節(jié),是不會在意這點的————仍然是先讀來的就放低地址?。。。?div style="height:15px;">
舉個例子:比如上面這兩種放置情況,你想把buf寫入文件,你按照[0]-[3]順序?qū)懭?,那么在不同的平臺上得到的文件,文件中從左到右,出現(xiàn)的字節(jié)序是不同的?。。?!然后你再交換兩個平臺的文件,讓他們各自讀入,一定會和原先的整數(shù)字節(jié)序相反了!
b. 應用:
實際中,TCP/IP網(wǎng)絡中為了規(guī)范,統(tǒng)一規(guī)定,凡是傳入網(wǎng)絡的字節(jié)流,一律按照Big-Endian。這樣,一般在PC上的程序,比如,要想寫出一個整數(shù)到待發(fā)送的緩沖區(qū),就得用下面的來轉(zhuǎn)換一下字節(jié)序:
同樣的,如果你希望一個二進制文件比較通用,也可以規(guī)定,文件中統(tǒng)一采用Big-Endian,這樣同理,PC機要做和上訴一樣的轉(zhuǎn)換。
a.既然,表示一個數(shù)的多個字節(jié)有字節(jié)序,那么對于一個字節(jié)內(nèi)部的8個比特,也有比特序?!?,我們一般從來不考慮這個。而是認為:經(jīng)過網(wǎng)絡傳過來,或者得到一個別的機器上的文件,我們認為:當用任意函數(shù),取得一個字節(jié),則字節(jié)內(nèi)部的比特序和原先的機器上看到的相同。
一個字節(jié)是:0xABCDEFGH,則寫到文件中,就是說打開來看,如果也能看到比特流的話,從左到右,仍然是0xABCDEFGH.所以,我寫一個字節(jié)時,都是把先來的bit放最高位,后來的bit次高位,湊滿一個字節(jié)后,寫入文件。而不管從什么機器讀出來,比特序和這個相同:所以,對于這一點,用這個模型吧,會舒服一點:
下面是一個字符串,左邊是str[0],挨著是str[1]...
|10100011|00110101|.....
b. windows下的文件寫入和讀?。?div style="height:15px;">