C++讀二進制文件時,空白字符讀不到的問題與解決辦法


  昨晚本來准備寫一個讀位圖(bmp)文件的程序,結果在讀信息頭的最后一個字段biClrImportant時出現了錯誤,沒有得到預期的結果。用Winhex以二進制方式打開原位圖文件,對比其中的數據,發現程序意外地跳過了下圖所示陰影部分字段中的兩個值為0x0B的字節:



  查閱ASCII碼表后,發現這是“垂直制表符”,傳說中的空白字符,問題就出在讀取文件使用的提取運算符‘>>’自動跳過了空白字符上。所以下面就討論一下空白字符和C++中如何讀入空白字符的問題,最后延伸一下在C++中進行文件讀寫的問題。

一、什么是ASCII碼表中的空白字符?

  ASCII碼表中的空白字符主要有:空格(0x20,' '),回車符(0x0D,‘\r’),換行符(0x0A,'\n'),水平制表符(0x09,'\t'),垂直制表符(0x0B,'\v'),換頁符(\f)。

以下內容來源於網絡:

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------

“回車”和“換行”是不是一回事?

  “回車”的效果實際上是輸出回到本行行首,結果可能會用回車符后的字符將這一行之前的輸出覆蓋掉。分在控制台中顯示還是在文本編輯器中顯示兩種情況:

    1.在控制台中的效果,不會換行且會用后面的覆蓋前面的;

    2.在文本編輯器中,用windows自帶的記事本做測試,不會覆蓋也不會顯示回車符,與沒有加回車符一樣,在其他文本編輯器中則不一定是此結果(文本編輯器是ASCII顯 示,即顯示文件數據對應的ASCII字符,是否顯示如何顯示與編輯器有關,與文件本身的二進制數據無關)。

  “換行”就是換到下一行首。 終端輸出要達到換行效果用“\n”就可以,但要在文本文件輸出中達到換行效果在各個系統中有所區別。在Unix系統中,每行的結尾是"\n",windows中則是"\n\r",mac則是"\r"。(本段未做測試)

  “垂直制表符”:垂直制表符不常用,它的作用是讓‘\v’后面的字符從下一行開始輸出,且開始的列數是“\v”的前一個字符所在列的后面一列。

  總結一下就是:‘\r’,‘\t’,‘\v’,‘\f’是控制字符,它們會控制字符的輸出方式。當它們在終端輸出時(打印在電腦屏幕上)會有上面的表現,但如果寫入文本文件,一般文本編輯器(vi或記事本)對‘\r’‘\v’‘\f’的顯示是沒有控制效果的。

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------

二、C++中如何讀入空白字符?

  無論是文本文件還是二進制文件,從存儲的角度而言,一切文件在物理上都是以二進制形式存儲的,都是二進制文件,只不過我們解讀的方式不同,或者說用不同的查看器打開時解析的方式不一樣,因此有了我們看到的文本文件和二進制文件(不能以ASCII碼映射為有意義的文本形式,通常所說的亂碼文件)。對於編程,最重要的是要知道,如果目標文件本身是一個文本文件,那么最終該文件在文本編輯器中顯示的時候,是以其二進制存儲格式(以Winhex查看的格式)中的每個字節值作為一個ASCII碼,逐字節翻譯成對應的字符進行顯示的。套用引文中的話說,二進制文件是無格式有數據類型的,文本文件是有格式無數據類型的。

  •   然后討論文件讀取的方式,主要有三種:

  1.純C風格的File*;

  2.調用Windows API;

  3.C++風格的文件流fstream。

  大一學的就是fstream,對此比較熟悉,因此討論以fstream為主。fstream有兩個派生類,即ifstream和ofstream,分別對應輸入文件流、輸出文件流。使用文件流讀文件的流程:

  1.創建輸入文件流對象:ifstream fin;

  2.將該對象與一個具體的文件關聯起來:fin.open("XXX.YYY");  實際上,open方法還包含一個參數mode,用以指定其打開方式。

    ios::in 以讀取方式打開文件

    ios::out 以寫入方式打開文件

    ios::ate 存取指針在文件末尾

    ios::app 寫入時采用追加方式

    ios::trunc 寫入時抹去舊數據

    ios::binary 以二進制方式存取。

  未指定任何打開方式時,則采用默認參數:輸入文件流即ios::in,輸出文件流即ios::out。一般在需要組合特殊的mode才顯式指定,比如: ios::in | ios::binary(以二進制方式讀取文件)。除此之外,還可以在構造時指定相應的文件路徑和名稱,讓創建過程一步到位。上述代碼可改寫為:

  ifstream fin("C:\filename.txt");

  與open方法相反的是close方法,它的作用與open正好相反。open是將文件流對象與外設中的文件關聯起來,close則是解除二者的關聯。close還起到清空緩存的作用。最好讓open方法與close方法成對出現。  

  創建並打開一個文件流后,就能像操作標准I/O那樣使用流插入操作符(<<)與流提取操作符(>>)。對於輸入文件流來說,使用提取操作符時會自動跳過上述六種空白字符,如果二進制文件中剛好有某個字節的值與這六種空白字符的ASCII碼相同,則會造成問題,如我開篇所講那樣。尤其當文件較大時,遇到空白字符(只是值與空白字符的ASCII碼相同,並不是起控制作用的)的概率將大大增加。

  •   最后關鍵地,如何在讀取文件時能讀取到空白字符(顯然已不能繼續使用流提取操作符">>"):

  經過實驗,發現調用ifstream的成員函數 read(char* _Str,std::streamsize _Count) 可以讀出任意的字節值,它是一種無格式有數據類型的讀取,不會infer每個字節所包含的意義(如,該字節是不是空白字符,在ASCII碼表中對應哪個字符),全部當作8位二進制的整數(具體是有符號還是無符號整數,取決於保存該整數的變量是unsigned char還是char)。值得注意的一點是read函數的第一個參數_Str是一個字符指針,實際上是一個字符串,用於保存讀出的值,而實際輸入的字節數由后面的_Count確定(不能超過數組的大小)。我的寫法是:

  //已經定義ifstream對象 pic

  unsigned char uc_var;

  read( (char*)&uc_var , 1 );

  由於我每次只讀入一個字節,將它以無符號整型的格式保存在uc_var中,所以采用上述強制類型轉換。

 

  最后提一下流提取操作符和流插入操作符,他們的道理是一致的,以流提取操作符>>為例說明。所有的原始輸入(鍵盤輸入或文本文件中保存的字符數據)對於>>而言都是字符串,即char* [](若一次只輸入一個字符,則是單字符的字符串)。>>會根據后面所跟變量的數據類型,適當的進行轉換。如后面是int型,就要將字符串(如"1234",是四個獨立的ASCII碼)轉換成整型值(如1234,一個整型數值)。當越界或類型無法匹配時,如對於unsigned short型變量,輸入"65536"或“abc”,則會出錯(VS2012中陷入死循環)。對於輸出而言,如果對一個值為1234的unsigned short類型的變量用<<進行輸出,那么實際上<<會首先對該變量進行轉換,轉換成由四個字符組成的字符串"1234"(四個ASCII碼)。

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------

  最后放一篇剛看到的文章,排版吐槽一下,看得我好累...文章中有部分結論我覺得還有待討論,作者說的有點絕對,而我並不認同,這些剩下的有時間再整理吧。

  http://pnig0s1992.blog.51cto.com/393390/563152  這篇文章講二進制文件的讀寫,部分引用如下,

  只有使用fwrite和write()函數才能以二進制形式輸出到文件中,調用puts、fprintf、<<等函數輸出的都是ASCII文本,使用<<來輸出一個整數時,輸出到二進制文件中的仍然是文本格式!<< operator在輸出之前會自動給你進行轉換,把一個整數值轉換成一位一位的數字字符!而且我后來試過了,即使我以文本模式打開一個文件,假如我用fwrite 函數輸出的話,文件中仍然是二進制格式,呵呵,說明在輸出數據到文件時,的確與打開文件的模式沒有關系,只與調用的輸出函數有關!!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM