C++學習之七、揭開C++I/O的神祕面紗

 

揭開C++I/O的神祕面紗

 

C++通過流(stream)機制來提供比C更先進的輸入輸出方法。每個流都有一個相關聯的源和目的。包括控制檯流、文件流、字符串流等。

 

1.控制檯流<iostream>:

流輸出:cout<< ,可輸出包含int、指針、double、字符、string等類型。

輸出流方法:

put() 和write()是原始的輸出方法。這二個方法風別接受一個字符或字符數組,而不是取已經定義了某種輸出行爲的對象或者變量。傳遞給這二個方法的數據按照原來的格式輸出,沒有進行任何專門的格式化或者處理。轉義字符(如\n)仍然按照正確的形式輸出,但不會發生多態輸出。

flush():向輸出流寫數據時,流不必立即把數據寫到目的中。大部分的輸出流都會進入緩衝區(buffer),或者累積數據,而不是數據一到來就寫出。當下列條件之一發生時,流會刷新輸出(flush),或者寫出累積數據:

        a.到達一個標記,比如endl標記。

        b.流超出了作用域,因此被撤銷。

        c.對應的輸入流請求輸入(也就是說,利用cin輸入時,cout會刷新輸出)。

        d.流緩衝區已滿。

        e.明確告訴流要刷新輸出其緩衝區。就是flush()方法。

            cout<<”abc”;

            cout.flush();

        不是所有的輸出流都會緩衝。比如,cerr流就沒有對其輸出進行緩衝。

 

處理輸出錯誤:輸出錯誤可能會在各種情況下產生。可能你試圖向不存在或者已經指定爲只讀的文件寫入數據。可能一個磁盤錯誤阻止了寫操作成功執行,或者由於某種原因控制檯進入了鎖定狀態。等等。可以調用方法good()來來確定流是否是好的。

cout.flush();

if(cout.good())

  cout<<”cout good!”<<endl;

使用good()能夠很容易地獲取關於流有效性的基本信息,但是它不能告訴你爲什麼流是不可用的。方法bad(),它提供了更多的信息,如果放回true,表示發生了一個致命的錯誤(不同於諸如文件結束等非致命錯誤)。還有一個方法fail(),如果最近的操作失敗,它返回true,意味着下一個操作也會失敗。

cout.flush();

if(cout.fail())

cerr<<”unable to flush to standard out”<<endl;

要重置流的錯誤狀態,使用clear()方法

cout.clear();

這裏討論的幾種方法也適合其他流。

 

輸出控制符:C++的流不僅可以識別數據,還可以識別出控制符(manipulator),這些控制符是一些對象,它們可以改變流的行爲,而不是爲流提供數據,或者除了提供數據之外還可以改變流的行爲。比如endl控制符,它封裝了數據與行爲。它告訴流輸出一個回車符並刷新輸出其緩衝區。

     a.boolalpha和noboolalpha,告訴流把bool類型的值輸出爲true或false(boolalpha)和1或0(noboolalpha)。默認爲noboolalpha。

     b.hex、oct、dec。分別以十六進制、八進制、十進制輸出數字。

     c.setprecision。設置輸出小數時的小數位數。這是一個參數化的控制符,可以接受一個參數。

     d.setw。設置輸出數值數據時的字段寬度。這是一個參數化的控制符。

     e.setfill。數字寬度小於指定的寬度時,這個控制符可以指定填充空位的字符。。這是一個參數化的控制符。

     f.Showpoint和noshowpoing。對於沒有小數部分的浮點數或者雙精度數,分別表示顯示和不顯示小數點。

流也可以通過一些與之等效的方法來提供同樣的功能,比如setPrecision()。

 

流輸入:cin>> 它之後捕獲第一個空白符之前的字符。

注意,即使cout沒有使用endl或flush()明確刷新緩衝區,文本仍然會寫到控制檯上,因爲使用cin就會立刻刷新輸入cout緩衝區。

輸入方法:

get(),此方法允許從流輸入原始數據。僅僅返回流中的下一個字符。經常用於避免使用>>操作符時發生的自動詞法分析。

下面可以讀入有幾個單詞組成的字符串:

string readName(istream & inStream)

{

   string name;

   while(inStream.good())

   {

     int text = inStream.get();

     if(next == EOF)  break;

     name += next;

   }

return  name;

}

首先,函數的參數是istream的引用,而不是其const引用,因爲從流中讀取數據的方法會改變所讀取的具體流(會改變流的位置),所以它不是const方法。因此不能對const引用調用這些方法。第二,get()返回值存儲在int中,而不是char中,因爲get可以返回特殊的非字符值,比如EOF(文件結束)。會隱性轉換爲char。

另一個版本:

string readName(istream & inStream)

{

   string name;

char   text;

   while inStream.get(text))

   {

     name += next;

   }

   return name;

}

 

unget():對於大部分目的而言,理解輸入流的正確方法是把它看作是單向的清洗槽。數據落入其中並進入變量。方法unget會打破這種模型,它允許把數據推回到清洗槽中。以此unget會引起流後退一個位置,其本質是把最後一個字符讀回到流中。

char ch1,ch2,ch3;

cin>>ch1>>ch2>>ch3;

cin.unget();

char ch4;

cin>>ch4;

//ch4==ch3

 

putback():就向unget一樣,允許在流中後退一個字符。二者的區別在於,putback取流中要後退的字符作爲參數。

char ch1;

cin>>ch1;

cin.putback(ch1);

//ch1是輸入流中得下一個字符。

 

peek():允許預覽下一個值。

int next = cin.peek();

if(isdigit(next) code;

else othercode;

 

getline():從流中讀入一行不超過指定值的數據。

char buffer[max+1];

cin.getline(buffer,max);

getline方法會刪除換行字符。所得到的字符串不包括換行字符。而get方法會把換行字符留在輸入流中。

還有一個getline方法可以用於C++字符串。

string myStr;

std::getline(cin,myStr);

 

處理輸入錯誤:不光有前面的good()之類的方法,還有一個eof()方法,它用來判斷是否到了流的結尾(文件結尾)。是,返回true,否則,返回false。

下段程序使用了從流中讀取數據和處理錯誤的常用模式。

#include<iostream>

#include<fstream>

#include<string>

 

using namespace std;

 

int main()

{

   int sum = 0;

   if(!cin.good())

   {

     cout<<”Standard input is in a bad state!”<<endl;

     exit(1);

   }

   int number;

   while(true)

{

  cin>>number;

  if(cin.good())

    sum += number;

  else if(cin.eof()) break;

  else

  {

    cin.clear();

    string badToken;

    cin>>badToken;

    cerr<<”WARNING:Bad input encounter:”<<badToken<<endl;

}

}

cout<<”The sum is :”<<sum<<endl;

return 0;

}

 

輸入控制符:

     a.oolalpha和noboolalpha,告訴流把bool類型的值輸入爲true或false(boolalpha)和1或0(noboolalpha)。默認爲noboolalpha。

     b.hex、oct、dec。分別以十六進制、八進制、十進制讀取數字。

     c.skipws和noskipws。告訴流在做詞法分析時,忽略空白符或者把空白符讀入作爲一個空白符token。

     d.ws。這是一個便利控制符,它只會忽略流中當前位置的一串空白。

 

2.   輸入與輸出對象

可以通過操作符重載,來控制輸入輸出對象的信息。

 

3.   字符串流

字符串流提供了一種對string應用流語義的方法。採用這種方法,可以有一個表示文本數據的內存中流。如果多個線程都向同一個字符串提供數據,或者需要把一個string傳遞給不同的函數,而同時還要維護當前的讀取位置,在這樣一些應用中,這種方法就很有用。因爲流有內置的詞法分析功能,所以字符串流對於解析文本也很有用。

類ostringstream和istringstream分別用於向字符串寫數據和從字符串讀數據。它們都在<sstream>頭文件中定義的。分別繼承了ostream和istream同樣的行爲。

 

4.   文件流 見C++文件操作學習

補充:

seek()函數:可以移動到輸入或輸出流的任意位置。輸入流爲seekg(),輸出流爲seekp()。每種類型的流都有二個查找方法。可以在流中查找絕對位置或相對位置。位置以字節爲單位。

移動到流的開始位置:ios_base::beg

移動到流的結束位置:ios_base::end

移動到流的中間常量:ios_base::cur

帶一個參數:

outStream.seekp(ios_base::beg)或inStream.seekg(ios_base::beg)

帶二個參數(第一個參數表示要移多少位置,第二個參數表示移動的起點):

移動到相對於流的開頭的第二個字符:

outStream.seekp(2, ios_base::beg);

移動到相對於流的倒數的第二個字符:

isStream.seekg(-2,ios_base::end);

 

tell()函數:查詢流的當前位置,返回ios_base::pos_type。輸入流爲tellg(),輸出流爲tellp()。

 

 

5.   鏈接流

可以在任何輸入流與輸出流之間建立鏈接。從而提供一種“一旦訪問就刷新輸出”的行爲。換句話說,從輸入流請求數據時,與其鏈接的輸出流會自動刷新輸出。這個行爲可以用於所有的流,但是對於可能相互依賴的文件流尤其有用。

流鏈接用方法tie()來實現。要把輸出流綁定到一個輸入流上,可以在輸入流上調用tie(),並把輸出流的地址傳遞給它。要斷開這個鏈接,傳遞NULL即可。

下面這段程序把一個文件的輸出流綁到了另一個文件的輸入流上。也可以把它綁定到同一個文件的輸入流上,不過要同時讀寫一個文件,雙向I/O可能更好。

#include<iostream>

#include<fstream>

#include<string>

 

using namespace std;

 

int main()

{

   ifstream inFile(“input.txt”);

   ofstream outFile(“output.txt”);

  

   inFile.tie(&outFile);

 

   outFile<<”Hello there!”;

   string text;

   inFile>>text;

return 0;

}

方法flush()是在ostream基類上定義的,所以也可以把一個輸出流綁定到另一個輸出流上。

outFile.tie(&antherOutFile);

這種關係表示,每次向一個文件寫數據時,就會向另一個文件寫入已經發送的緩衝數據。可以使用這種機制保持二個相關文件之間的同步。

 

6.   雙向流

前面一直把輸入流,輸出流最爲獨立的二個類來討論的。實際上還有一種流可以同時進行輸入輸出。雙向流可以同時作爲輸入流輸出流操作。雙向流是iostream的子類,所以也是istream和ostream的子類。

通過stringstream類,也可以一雙向方式訪問字符串流。

雙向流對於讀位置和寫位置分別有單獨的指針。在讀操作與寫操作之間切換時,需要查找正確的位置。

 

7.   國際化

寬字符:把字符看做是一個字節的問題就是,並不是所有編程語言和字符集都能用8爲二進制爲或1個字節來表示。幸運的是,C++提供了寬字符,wchar_t。它會像char一樣工作,唯一區別的是字符串和字符直接量都有一個前綴字母L,來指示應該使用寬字符編碼。

wchar_t letter = L’m’;

string有wstring,ofstream有wofstream,cin、cout、cerr有wcin、wcout、wcerr。等等。

 

非西方字符集:Unicode編碼。可以查看相關資料。

 

本地化環境與方面:各國之間表示數據的唯一差別就是字符集。但是即使使用同一個字符集的國家之間也有差別,比如美國與英國之間類似日期和貨幣之類的。

標準C++提供了一種內置機制,他可以把特定地方的數據分組到一個本地化環境中。本地化環境是特定位置相關設置的集合。各個設置稱爲一個方面(facet)。C++還提供了定製或者增加方面的方法。

下面把美國英語本地化環境(en_US)附加到寬字符控制檯輸出流上。

wcout.imbue(locale(“en_US”));

默認的本地化環境不是美國英語,而是經典本地化環境,它使用ANSI C的約定。

比如,如果根本沒有設置本地化環境,或者設置默認的本地化環境,並且要輸出一個數字,那麼輸出時不帶任何標點。

wcout.imbue(locale(“C”));

wcout<<32767<<endl;

 

32767

但是如果使用的本地化環境是美國英語,數字就會用美國英語的標點格式化。

wcout.imbue(locale(“en_US”));

wcout<<32767<<endl;

 

32,767

大多數操作系統都有一種機制來確定用戶定義的本地化環境,在C++中,可以向本地化環境對象構造函數傳遞一個空字符串,從而由用戶環境創建一個本地化環境。一旦創建了這個對象,就可以查詢本地化環境。

locale loc(“”);

if(loc.name().find(“en_US”) == string::npos&&loc.name().find(“United States”)==string::npos))

   ----doing something----

根據本地化環境的名字來確定本地化環境,還不一定能夠正確地確定用戶是否的確在這個地方。,但是可以提供一條線索。

 

使用方面:

可以使用函數是std::use_facet()來獲取特定本地化環境的特定方面。比如,下面用來檢索英國本地化環境的標準貨幣符號方面。(需要使用頭文件<locale>

use_facet<moneypunct<wchar_t> >(locale(“en_GB”));

 

發佈了123 篇原創文章 · 獲贊 663 · 訪問量 157萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章