標籤:
一、C++輸入和輸出概述
1.1、流和緩衝區
C++程式把輸入和輸出看作位元組流。輸入時,程式從輸入資料流中抽取位元組;輸出時,程式將位元組插入到輸出資料流中。對於面相文本的程式,每個位元組代表一個字元,更通俗地說,位元組可以構成字元或數值資料的二進位表示。輸入資料流中的位元組可能來自鍵盤,也可能來自存放裝置(如硬碟)或其他程式。輸出資料流中的位元組可以流向螢幕、印表機、存放裝置或其他程式。流充當了程式和流源或流目標之間的橋樑。這使得C++程式可以以相同的方式對待來自鍵盤的輸入和來自檔案的輸入。C++程式只檢查位元組流,而不需要知道位元組來自何方。同理,通過使用流,C++程式處理輸出的方式將獨立於其去向。因此管理輸入將包含兩步:
*將流與輸入去向的程式關聯起來。
*將流與檔案串連起來。
換句話說,輸入資料流需要兩個串連,每端各一個。檔案端部串連提供了流的來源,程式端串連將流的流出部分轉儲到程式中(檔案端串連可以是檔案,也可以是裝置)。同樣,對輸出的管理組件括將輸出資料流串連到程式以及將輸出目標與流關聯起來。
通常,通過使用緩衝區可以更高效地處理輸入和輸出。緩衝區是用作中介的記憶體塊,它是將資訊從裝置傳輸到程式或從程式傳輸給裝置的臨時儲存工具。
1.2、流、緩衝區和iostream檔案
管理流和緩衝區的工作有點複雜,但iostream(以前為iostream.h)檔案中包含一些專門設計用來實現、管理流和緩衝區的類。C++98版本C++I/O定義了一些類模板,以支援char和wchar_t資料;C++11添加了char16_t和char32_t具體化。通過使用typedef工具,C++使得這些模板char具體化能夠模仿傳統的非模板I/O實現。下面是其中的一些類:
*streambuf類為緩衝區提供了記憶體,並提供了用於填充緩衝區、訪問緩衝區內容、重新整理緩衝區和管理緩衝區記憶體的類方法;
*ios_base類表示流的一般特徵,如是否可讀取、是二進位流還是文字資料流等;
*ios類基於ios_base,其中包含了一個指向streambuf對象的指標成員;
*ostream類是從ios類派生而來的,提供了輸出方法;
*istream類也是從ios類派生而來的,提供了輸入方法;
*iostream類是基於istream和ostream類的,因此繼承了輸入方法和輸出方法。
要使用這些工具,必須使用適當的類對象。例如,使用ostream對象(如cout)來處理輸出。建立這樣的對象將開啟一個流,自動建立緩衝區,並將其與流關聯起來,同時使得能夠使用類成員函數。
補充:沖定義I/O
ISO/ANSI標準C++98對I/O作了兩方面的修訂。首相是從ostream.h到ostream的變化,用ostream將類放到了std名稱空間中。其次,I/O類被重新編寫。為成為國際語言,C++必須能夠處理需要16位的國際字元集或更寬的字元類型。因此,該語言在傳統的8位char(“窄”)類型的基礎上添加了wchar_t(“寬”)字元類型;而C++11添加了類型char16_t和char32_t。每種類型都需要有自己的I/O工具。標準委員會被沒有開發兩套(現在為4套)獨立的類,而是開發了一套I/O類模板,其中包括basic_istream<charT, traits<charT>>和basic_ostream<charT, traits<charT>>。traits<charT>模板是一個模板類,為字元類型定義了具體特性,如如何比較字元是否相等以及字元的EOF值等。該C++11標準提供了I/O的char和wchar_t具體化。例如,istream和ostream都是char具體化的typedef。同樣,wistream和wostream都是wchar_t的具體化。例如,wcout對象用於輸出寬字元流。標頭檔ostream中包含了這些定義。
ios基類中的一些獨立於類型的資訊被移動到了新的ios_base類中,這包括各種格式化常量,例如ios::fixed(現在為ios_base::fixed)。另外,ios_base還包含了一些老式ios中沒有的選項。
C++的iostream類庫管理了很多細節。例如,在程式中包含iostream檔案將自動建立8個流對象(4個用於窄字元流,4個用於寬字元流)。
*cin對象對應於標準輸入資料流。在預設情況下,這個流被關聯到標準輸入裝置(通常為鍵盤)。wcin對象於此類似,但處理的是wchar_t類型。
*cout對象與標準輸出資料流相對應。在預設情況下,這個流被關聯到標準輸出裝置(通常為顯示器)。wcout對象與此類似,但處理的事wchar_t類型。
*cerr對象與標準錯誤流相對應,可用於顯示錯誤訊息。在預設情況下,這個流被關聯到標準輸出裝置(通常為顯示器)。這個流沒有被緩衝,這意味著資訊將被直接發送給螢幕,而不會等到緩衝區填滿或新的分行符號。wcerr對象與此類似,但處理的是wchar_t類型。
*clog對象也對應著標準錯誤流。在預設情況下,這個流被關聯到標準輸出裝置(通常為顯示器)。這個流被緩衝。wclog對象於此類似,但處理的是wchar_t類型
*對象代表流——這意味著什麼呢?當iostream檔案為程式聲明一個cout對象時,該對象將包含儲存了與輸出有關的資訊的資料成員,如顯示資料時使用的欄位寬度、小數位元、顯示整數時採用的計數方法以及描述用來處理輸出資料流的緩衝區的streambuf對象的地址。下面的語句通過指向的streambuf對象將字串"Hello world!"中的字元放到cout管理的緩衝區中:cout << "Hello world!";
總之,流的一端與程式相連,另一端與標準輸出相連,cout對象憑藉streambuf對象的協助,管理者流中的位元組流。
1.3、重新導向
標準輸入和輸出資料流通常串連著鍵盤和螢幕。但很多作業系統(包括UNIX,Linux和Windows)都支援重新導向,這個工具使得能夠改變標準輸入和標準輸出。
二、使用cout進行輸出
C++將輸出看作位元組流(根據平台的不同,可能是8位、16位或32位的位元組,但都是位元組),但在程式中,很多資料被組織成比位元組更大的單位。例如,ing類型由16位或32位的二進位表示;double值由64位的位元表示。但在將位元組流發送給螢幕時,希望每個位元組表示一個字元值。因此,ostream類最重要的任務之一是將數實值型別(如int或float)轉換為以文本形式表示的字元流。也就是說,ostream類將資料內部表示(二進位位元模式)轉換為由字元位元組組成的輸出資料流(以後會有仿生移植物,使得能夠直接翻譯位元據)。為執行這些轉換任務,ostream類提供了多個類方法。
2.1、重載的<<運算子
在C++中,與C一樣,<<運算子的預設含義是按位左移運算子。但ostream類重新定義了<<運算子,方法是將其重載為輸出。在這種情況下,<<叫做插入運算子,而不是左移運算子。插入運算子被重載,使之能夠識別C++中所有的基本類型:
* unsigned char;
* signed char;
* char;
* short;
* unsigned short;
* int;
* unsigned int;
* long;
* unsigned long;
* long long(C++11);
* unsigned long long (C++11);
* float;
* double;
* long double;
對於上述每種類型,ostream類都提供了operator<<()函數的定義。因此,如果使用下面這樣一條語句,而value是前面列出的類型之一,則C++程式將其對應於有相應的特徵標的運算子函數:
cout << value;
2.1.1 輸出和指標
ostream類還為下面的指標類型定義了插入運算子函數:
* const signed char *;
* const unsigned char *;
* const char *;
* void *。
C++使用指向字串儲存位置的指標來表示字串。指標的形式可以是char數組、顯式的char指標或用引號擴起來的字串;並使用字串中的終止Null 字元來確定何時停止顯示字元。
對於其他類型的指標,C++將其對應於void *,並列印地址的數值表示。如果要擷取字串的地址,則必須將其強制轉換為其他類型,例如:
int num = 12;
char * name = "Tom";
cout << # // 列印變數num的地址
cout << name;//將會列印字串“Tom”
cout << (void *) name;//將會列印字串“Tom”的地址
2.1.2 拼接輸出
插入運算子的所有化身的傳回型別都是ostream & 。也就是說,原型格式如下:
ostream & operator<<(type);
(其中,type是要顯示的資料的類型)傳回型別ostream &意味著使用該運算子將返回一個指向ostream對象的引用;函數定義指出,該引用指向用於調用該運算子的對象。換句話說,運算子函數的傳回值為調用運算子的對象。這種特性使得能夠通過插入來串連輸出。
2.2 其他的ostream方法
除了各種operator<<()函數外,ostream類還提供了put()方法和write()方法,前者用於顯示字元,後者用於顯示字串。
最初,put()的原型如下:
ostream & put(char);
當前標準與此相同,但被模板化,以適用於wchar_t。可以用類方法標記法調用它:
cout.put(‘W‘);
其中,cout是調用方法的對象,put()是成員函數。和<<運算子函數一樣,該函數也返回一個指向調用對象的引用,因此可以用它拼接輸出:
cout.put(‘‘A).put(‘B‘);
在原型合適的情況下,可以將數值型參數(如int)用於put(),讓函數原型自動將參數轉換為正確的char值。例如:
cout.put(65);//顯示A
cout.put(66.3);//顯示B
write()方法顯示整個字串,其模板原型如下:
basic_ostream<charT, traits> & write (const char_type* s, streamsize n);
write()的第一個參數提供了要顯示的字串的地址,第二個參數指出要顯示多少個字元。使用cout調用write()時,將調用char具體化,因此傳回型別為ostream &。
1 #include <iostream> 2 3 int main() 4 { 5 using std::cout; 6 using std::endl; 7 const char * state1 = "Florida"; 8 const char * state2 = "Kansas"; 9 const char * state3 = "Euphoria";10 std::size_t len = std::strlen(state2);11 cout << "Increasing loop index:\n";12 int i = 0;13 for (i = 1; i < len; i ++) {14 cout.write(state2, i);15 cout << endl;16 }17 18 cout << "Decreasing loop index:\n";19 for (i = (int)len; i > 0; i--) {20 cout.write(state2, i) << endl;21 }22 cout << "Exceeding string length:\n";23 cout.write(state2, len + 5) << endl;24 return 0;25 }26 27 輸出結果:28 Increasing loop index:29 K30 Ka31 Kan32 Kans33 Kansa34 Decreasing loop index:35 Kansas36 Kansa37 Kans38 Kan39 Ka40 K41 Exceeding string length:42 KansasEuph
從上面的程式可以看出,write()方法並不會在遇到Null 字元時停止列印字元,而只是列印指定數目的字元,即使超出了字串的邊界。
write()方法也可用於數值資料,可以將數位地址強制轉換為char *,然後傳遞給它:
#include <iostream>int main(){ long val = 560031841; std::cout.write((char *) & val, sizeof(val)); return 0;}輸出結果:aha!
這不會將數值轉換為相應的字元,而是傳輸記憶體中儲存的位表示。例如,4位元組的long值將作為4個獨立的位元組被傳輸。輸出裝置將把每個位元組作為ASCII碼進行解釋。
2.3 重新整理輸出緩衝區
如果程式使用cout將位元組發送給標準輸出,情況將如何?由於ostream類對cout對象處理的輸出進行緩衝,所以輸出不會立即發送到目標地址,而是被儲存在緩衝區中,直到緩衝區填滿。然後,程式將重新整理(flush)緩衝區,把內容發送出去,並清空緩衝,以儲存新的資料。通常,緩衝區為512位元組或其整數倍。當標準輸出串連的是硬碟上的檔案時,緩衝可以節省大量的時間。
如果實現不能在所希望時重新整理輸出,可以使用兩個控制符中的一個來強行進行重新整理。Null 字元flush重新整理緩衝區,而控制符endl重新整理緩衝區並插入一個分行符號。這兩個控制符的使用方式與變數名相同:
cout << "Hello world!" << flush;
cout << "Wait for me." << endl;
事實上,控制符也是函數。例如,可以直接調用flush()來重新整理cout緩衝區:
flush(cout);
然而,ostream類對<<插入運算子進行了重載,使得下述運算式將被替換為函數調用flush(cout):
cout << flush;
因此,可以用更為方便的插入標記法來成功地進行重新整理。
2.4 使用cout進行格式化
ostream插入運算子將值轉換為文字格式設定。在預設情況下,格式化值的方式如下。
* 對於char值,如果它代表的是可列印字元,則將被作為一個字元顯示在寬度為一個字元的欄位中。
* 對於數值整型,將以十進位方式顯示在一個剛好容納該數字及負號(如果有的話)的欄位中;
* 字串被顯示在寬度等於該字串長度的欄位中。
浮點數的預設行為有變化。下面詳細說明了老式實現和新式實現之間的區別。
*新式:浮點類型被顯示為6位,末尾的0不顯示(注意,顯示的數字位元與數字被儲存時精度設定沒有任何關係)。數字以定點標記法顯示還是科學計數法表示,取決於它的值。具體來說,當指數大於等於6或小於等於-5時,將使用科學計數法表示。另外,欄位寬度恰好容納數字和負號(如果有的話)。預設的行為對應於帶%g說明符的標準C庫函數fprintf()。
*老式:浮點類型顯示為帶6位小數,末尾的0不顯示(注意,顯示的數字位元與數字被儲存時的精度沒有任何關係)。數字以定點標記法顯示還是以科學計數法表示,取決於他的值。另外,欄位寬度恰好容納數字和負號(如果有的話)。
因為每個值的顯示寬度等於它的長度,因此必須顯式地在值之間提供空格;否則,相鄰的值將不會被分開。
注意:並非所有的編譯器都能產生符合當前C++標準格式的輸出。另外,當前標準允許地區性變化。例如,歐洲實現可能遵循歐洲人的風格:使用逗號而不是句點來表示小數點。也就是說,2.54將被寫成2,54 。地區庫(標頭檔locale)提供了用特定的風格影響(imbuing)輸入或輸出資料流的機制,所以同一個編譯器能夠提供多個地區選項。本章使用美式格式。
(1)修改顯示時使用的計數系統
ostream類是從ios類派生來的,而後者是從ios_base派生來的。ios_base類儲存了描述格式狀態的資訊。例如,一個類成員中某些位決定了使用的計數系統,而另一個成員則決定了欄位寬度。通過使用控制符(manipulator),可以控制顯示整數時使用的技術系統。通過使用ios_base的成員函數,可以控制欄位寬度和小數位元。由於ios_base類是ostream的間接基類,因此可以將其方法用於ostream對象(或子代),如cout。
注意:ios_base類中的成員和方法以前位於ios中。現在,ios_base是ios的基類。在新系統中,ios是包含char和wchar_t具體化的模板,而ios_base包含了非模板特性。
要控制整數以十進位、十六進位還是八進位顯示,可以使用dec、hex和oct控制符。例如,下面的函數調用將cout對象的計數系統格式狀態設定為十六進位:
hex(cout);
完成上述設定後,程式將以十六進位形式列印整數值,直到將格式狀態設定為其他選項為止。注意,控制符不是成員函數,因此不必通過對象來調用。
雖然控制符實際上是函數,但它們通常的使用方式為:
cout << hex;
ostream類重載了<<運算子,這使得上述用法與函數調用hex(cout)等價。控制符位於名稱空間std中。下面的代碼示範了控制符的使用方法(注意,可以單獨使用控制符,也可以將其作為一系列插入的部分):
#include <iostream>int main(){ using namespace std; cout << "輸入一個數字:"; int n; cin >> n; std::string str(5,‘ ‘); cout << "n" << str << "n*n\n"; cout << n << str << n*n << "(decimal)\n"; //設定成十六進位系統 cout << hex; cout << n << str; cout << n*n << " (hexadecimal)\n"; //設定為八進位系統 cout << oct << n << str << n * n << " (octal)\n"; dec(cout); cout << n << str << n * n << " (decimal)\n"; return 0;}輸出結果:輸入一個數字:12n n*n12 144(decimal)c 90 (hexadecimal)14 220 (octal)12 144 (decimal)
(2)調整欄位寬度
C++——輸入、輸出和檔案