[1] I/O基礎
大多數計算機語言的輸入輸出的實現都是以語言本身為基礎的,但是C/C++沒有這樣做。C語言最初把I/O留給了編譯器實現人員。這樣做的一個原因是可以提供足夠的自由度,使之最適合目標機器的硬件條件。但是大多數實現人員都將I/O建立在了Unix庫函數中,之后C才將該庫引入了C標准中,被C++繼承了下來。
但是C++也有自己的I/O解決方案,將其定義於iostream和fstream中。這兩個庫不是語言的組成部分,只是定義了一些類而已。
C++把輸入和輸出看作字節流。對於面向文本的程序,一個字節代表一個字符。流充當了源於目標之間的橋梁,因此對流的管理包含兩個方面:從哪里來,到哪里去。其中,緩沖區作為臨時存儲空間,可以提高流處理的速度。
C++中專門定義了一些類來管理流和緩沖區:
C++98定義了一些模板類,用以支持char和wchar_t類型;
C++ 11添加了char16_t和char32_t類型(實則就是一些typedef來模擬不同的類型)。
C++程序中若包含了iostream類庫,則自動創建8個流對象:4個窄的,4個寬的:
cin: 標准輸入流,默認關聯到鍵盤[與之對應的寬型為wcin(處理wchar_t)];
cout: 標准輸出流,默認關聯到顯示器[與之對應的寬型為wcout(處理wchar_t)];
cerr: 標准錯誤輸出流,默認關聯到顯示器,這個流沒有緩沖區,意味着信息會直接發送到顯示器[與之對應的寬型為wcerr(處理wchar_t)];
clog: 標准錯誤輸出流,默認也關聯到顯示器,但是這個流對應着緩沖區[與之對應的寬型為wclog(處理wchar_t)]。
[1.1] cout 的使用
C++將輸出看作是字節流(可能是8位/16位/32位),但在將字節流發送給屏幕時,希望每個字節表示一個字符值。比如,要顯示-2.34,需要5個字符表示,分別為-/2/./3/4,並不是這個值的浮點數表示形式。因此,ostream類的最重要的任務之一就是將數值類型轉換為文本形式表示的字符流。為了達到這個目標,C++使用了如下方法:
重載的<<運算符:C++的重載操作除了基本的內置類型,還有如下幾種:const signed char*/const unsigned char*/const char*/void*。
對於其他類型的指針,C++將其看作void*,然后打印地址的數值。如果要想獲得字符串的地址,必須將其強轉為void*類型。
ostream類的put()和write()方法,前者用於顯示字符,后者用於顯示字符串。
ostream& put(char);
// cout.put(‘B’).put(‘T’);
basic_ostream<charT, traits>& write(const char_type* s, streamsize n);
// write()的第一個參數是要顯示的字符串的地址,第二個參數是要顯示字符個數。
1 const char* name = "benxintuzi"; 2 for(int i = 0; i <= strlen(name); i++) 3 { 4 cout.write(name, i); 5 cout << endl; 6 } 7 8 /** output */ 9 b 10 be 11 ben 12 benx 13 benxi 14 benxin 15 benxint 16 benxintu 17 benxintuz 18 benxintuzi
說明:
write()方法不會在遇到空白字符就停止打印,而是打印指定數目的字符,即使超出了字符串的邊界。write()也可將數字轉換為相應的形式打印,此時需要將數字的地址轉換為char*,但是不會打印字符,而是內存中表示數值的位,可能是亂碼,這個再正常不過了。
由於ostream類對數據進行了緩沖,因此利用cout也許並不會立即得到輸出,而是被存儲在緩沖區中,直至緩沖區填滿為止。通常,緩沖區大小為512字節及其整數倍。如果需要將尚未填滿的緩沖區進行刷新操作,需要執行如下代碼:
cout << flush; // 立即刷新
或者:
cout << endl; // 立即刷新並插入換行符
控制數值顯示的格式:
dec/hex/oct,雖然這些都是函數,但是其通常的使用方式是:
cout << hex;
cout << value << endl;
使用width成員函數將不同的字段放到寬度相同的字段中:有兩個函數版本:
int width()/int width(int i);
第一個返回當前字段所占的空格數;
第二個函數將寬度設置為i個空格后,返回舊的空格數。但是注意:該函數只影響緊挨着它的一項顯示輸出,然后將字段寬度恢復為默認值。
說明:
C++不會截斷數據,比如如果在寬度為2的字段中打印7位的值,那么該字段將被增大,C++的原則是:內容比形式更重要。
默認情況下,C++的對其方式為右對齊,並且默認的填充字符是空格。可以使用fill()成員函數來更改填充字符,例如:cout.fill(‘*’);
1 string birth("19890608"); 2 cout.width(20); 3 cout << birth << endl; 4 cout.fill('*'); 5 cout.width(20); 6 cout << birth << endl; 7 8 /** output */ 9 19890608 10 ************19890608
默認情況下,浮點數的顯示精度指的是總位數;
在定點模式和科學模式下,指的是小數點后的位數;
C++默認的精度為6位,並且末尾的0不顯示。可以使用cout.precision(int)設置精度的位數,一旦設置,直到第二次設置前永遠有效。
對於有些輸出,必須保留末尾的0,此時需要借助於ios_base類中的setf()函數:cout.setf(ios_base::showpoint);
為了簡化格式設置工作,C++專門提供了一個工具(頭文件<iomanip>),3個最常用的格式控制符分別為:setprecision()、setfill()、setw(),分別用來設置精度、填充字符、字段寬度。
1 for(int i = 1; i <= 10; i++) 2 { 3 cout << setw(5) << i << setw(10) << i * 10 << endl; 4 } 5 6 /** output */ 7 1 10 8 2 20 9 3 30 10 4 40 11 5 50 12 6 60 13 7 70 14 8 80 15 9 90 16 10 100
[1.2] cin 的使用
cin對象將標准輸入表示為字節流,通常情況下,鍵盤生成的是字符流,cin根據接受值的變量的類型,將字符序列轉換為所需的類型。
再次強調:C++解釋輸入的方式取決於變量的數據類型。istream類重載了>>,可以識別如下基本類型:
signed char&/unsigned char&/char&/short&/unsigned short&/int&/unsigned int&/long&/unsigned&/long long&/unsigned long long&/float&/double&/long double&。
除此之外,還支持:
signed char*/char*/unsigned char*。
[1.3] 流狀態
cin和cout對象各自包含一個描述流狀態的數據成員(從ios_base中繼承的)。流狀態被定義為iostate類型,實則是bitmask類型,由3個ios_base元素組成:eofbit/badbit/failbit。其中每個元素都是一位:
當cin到達文件末尾時,置位eofbit;
當cin未能讀取到預期的字符時,置位badbit;
當I/O故障失敗時,置位failbit。
當全部三個狀態位設置為0時,說明一切順利。程序可以根據當前的流狀態決定下一步的動作。
[1.4] 字符的輸入
首先應該確定是否希望跳過空白符,希望跳過空白則選擇>>;如果希望檢查輸入的每個字符,則使用get(void)或者get(char&)。如果要將標准C程序改寫為C++程序,可以使用cin.get()替換getchar(), 使用cin.put(ch)替換putchar(ch)。
字符串輸入函數getline()、get()、ignore():
istream& get(char*, int, char); istream& get(char*, int); istream& getline(char*, int, char); istream& getline(char*, int); 其中: char*: 存放字符串的內存單元地址; int: 內存單元大小(比字符串長度大1,用於存放’\0’); char: 指定分界符。
例如,將字符串讀入字符數組line中: char line[50]; cin.get(line, 50); 讀取時,get將在到達第49個字符或者遇到換行符后停止從流中讀取字符,其在中途若遇到流中的換行符,那么將其留在流中不予處理,下一次讀取時,首先看到的仍然是換行符;而getline則會將遇到的換行符讀取出來並丟棄掉,這樣流中就沒有換行符了。
cin.ignore(255, ‘\n’)表示即將讀取接下來的255個字符並且丟棄掉或者遇到分界符\n后停止讀取操作,其默認形式為:istream& ignore(int = 1, int = EOF)。 |
1 #include <iostream> 2 #include <cstring> 3 #include <iomanip> 4 using namespace std; 5 6 const int Limit = 255; 7 8 int main() 9 { 10 char input[Limit]; 11 12 cout << "Enter a string for getline() processing: "; 13 cin.getline(input, Limit, '#'); // 丟棄流中的# 14 15 cout << "Here is your input: "; 16 cout << input << "\nDone with phase 1" << endl; 17 18 char ch; 19 cin.get(ch); // 讀取到的字符為#后邊的第一個字符 20 cout << "The next input character is: " << ch << endl; 21 if(ch != '\n') 22 cin.ignore(Limit, '\n'); 23 24 cout << "Enter a string for get() processing: "; 25 cin.get(input, Limit, '#'); // 保存流中的# 26 27 cout << "Here is your input: "; 28 cout << input << "\nDone with phase 2" << endl; 29 30 cin.get(ch); // 讀取到的字符為# 31 cout << "The next input character is: " << ch << endl; 32 33 return 0; 34 } 35 36 /** output */ 37 Enter a string for getline() processing: I love tu#zi ! 38 Here is your input: I love tu 39 Done with phase 1 40 The next input character is: z 41 Enter a string for get() processing: I love tu#zi ! 42 Here is your input: I love tu 43 Done with phase 2 44 The next input character is: #
除了前面介紹的方法外,其他的istream方法還包括read()/peek()/gcount()/putback()等:
read()讀取指定數目的字節並存儲到指定的位置,如: char gross[100]; cin.read(gross, 100); 說明: 與get和getline不同,read不會在輸入后加上’\0’,因此,不能將輸入轉換為字符串。Read常與ostream write()函數結合使用,完成文件的輸入和輸出。返回類型為istream&。
peek()函數返回輸入中的下一個字符,但是不去讀下一個字符,也就是說可以用來作為檢查流的作用,以此來判斷是否還要繼續讀取: char gross[80]; char ch; int i = 0; while((ch = cin.peek()) != ‘\n’) cin.get(gross[i++]); gross[i] = ‘\0’;
gcount()返回最后一個非格式化方法讀取的字符個數,意味着get()/ getline()/ ignore()/ read()方法讀取的字符個數。實際中用的不多,可以用strlen()代替。
putback()方法將一個字符退回到流中,以便下一次讀取。該方法返回一個istream&。使用peek()的效果相當於先使用一個get()讀取一個字符,然后使用putback()將該字符退回到流中去。當然,putback()也可將字符放置在流中的其他位置。 |
1 #include <iostream> 2 using namespace std; 3 4 int main() 5 { 6 char ch; 7 8 while(cin.get(ch)) 9 { 10 if(ch != '#') 11 cout << ch; 12 else 13 { 14 cin.putback(ch); 15 break; 16 } 17 } 18 if(!cin.eof()) 19 { 20 cin.get(ch); 21 cout << endl << ch << "is the next input character." << endl; 22 } 23 else 24 { 25 cout << "End of file reached." << endl; 26 } 27 28 while(cin.peek() != '#') 29 { 30 cin.get(ch); 31 cout << ch; 32 } 33 if(!cin.eof()) 34 { 35 cin.get(ch); 36 cout << endl << ch << "is the next input character." << endl; 37 } 38 else 39 { 40 cout << "End of file reached." << endl; 41 } 42 43 return 0; 44 } 45 46 /** output */ 47 I love #tuzi and I am #benxin. 48 I love 49 #is the next input character. 50 tuzi and I am 51 #is the next input character.
[2] 文件I/O
文件類的I/O與基本I/O非常相似,其相關類派生自iostream類,只是要操作文件,還必須將文件與流進行關聯起來。
[2.1] 文件I/O基礎
要讓程序寫文件,必須進行如下操作:
1 創建一個ofstream對象管理輸出流:ofstream fout;
2 將該對象與特定文件關聯起來:fout.open(“jar.txt”)【如果jar.txt不存在,那么會自動創建它】;
3 將內容寫入文件中去:fout << “benxintuzi test”;
要完成上述任務,首先包含頭文件fstream, 由於fstream中的類大多派生自iostream中的類,因此,fstream必然包含了iostream,因此,包含頭文件fstream后不必再顯式包含iostream了。
說明:
可以在創建流的同時關聯文件:ofstream fout(“jar.txt”);
以上述這種默認模式打開文件時,如果文件不存在,則自動創建該文件;若文件存在,則自動將文件清空,然后再寫入新內容。
程序讀文件流程類似:
ifstream fin;
fin.open("jayne.txt");
char buf[50];
fin.getline(buf, 50); // 讀入一行內容
cout << buf << endl;
雖然程序結束時會自動關閉流,但是最好顯式關閉,如下:
fout.close();
fin.close();
關閉流並不是刪除流,只是將斷開流與文件的關聯性。這樣就可以重新將該流關聯到另一個文件中。
[2.2] 流狀態檢查
C++文件流類從ios_base類繼承了一個表示流狀態的成員以及報告流狀態的方法。可以通過這些方法判斷流操作是否成功。比如檢查打開文件是否成功,可以如下:
fin.open(“***”);
if(fin.fail())
或者:
fin.open(“***”);
if(!fin)
或者利用C++提供的新方法:
if(!fin.is_open())
說明:
if(fin.fail())、if(!fin.good())、if(!fin)...是等價的。但是如果以不合適的文件模式打開文件失敗時,這些方法將不會檢測出來。相比之下,is_open()卻可以檢測到這種錯誤。
在打開多個文件時,常理來說需要為每個文件關聯一個流對象,然而為了節約系統資源,可以創建一個流,然后分別關聯到不同的文件中。
文件模式描述的是打開的文件將被如何使用:讀、寫、追加等。
ifstream fin(“jar.txt”, model);
ofstream fout;
fout.open(“jar.txt”, model);
ios_base類定義了一個openmode類型,用來表示模式。與fmtflags和iostate類型一樣,它也是一個bitmask類型。可以選擇ios_base類中定義的多個常量來指定模式:
說明:
ios_base::ate和ios_base::app都將文件指針指向打開的文件尾,二者的區別在於:ios_base::app模式只允許將數據添加到文件尾,而ios_base::ate模式將指針放到文件尾。
追加文件示例:
1 #include <iostream> 2 #include <fstream> 3 #include <string> 4 #include <cstdlib> 5 6 using namespace std; 7 8 const char* file = "append.txt"; 9 10 int main() 11 { 12 // show the initial contents 13 ifstream fin; 14 fin.open(file); 15 if(fin.is_open()) 16 { 17 cout << "The contents of " << file << " is: " << endl; 18 char ch; 19 while(fin.get(ch)) 20 cout << ch; 21 fin.close(); 22 } 23 24 // add new contents to the end of file 25 ofstream fout(file, ios::out | ios::app); 26 if(!fout.is_open()) 27 { 28 exit(EXIT_FAILURE); 29 } 30 else 31 { 32 cout << "Enter your contents to be added: "; 33 string contents; 34 while(getline(cin, contents) && contents.size() > 0) 35 fout << contents << endl; 36 fout.close(); 37 } 38 39 // show revised file 40 fin.clear(); 41 fin.open(file); 42 if(fin.is_open()) 43 { 44 cout << "The contents of " << file << " is: " << endl; 45 char ch; 46 while(fin.get(ch)) 47 cout << ch; 48 fin.close(); 49 } 50 cout << endl << "Done !" << endl; 51 52 return 0; 53 } 54 55 /** output */ 56 The contents of append.txt is: 57 This is line 1 58 This is line 2 59 This is line 3 60 Enter your contents to be added: I love tuzi 61 So you can guess 62 63 The contents of append.txt is: 64 This is line 1 65 This is line 2 66 This is line 3 67 I love tuzi 68 So you can guess 69 70 Done !
說明:
以二進制讀寫文件時,請使用write和read方式。
[2.3] 隨機存取文件
隨機存取常被用於數據庫文件中,可以根據索引操作數據項。
在文件中移動指針:
seekg(): 將輸入指針移到指定的位置;
seekp(): 將輸出指針移到指定的位置。
說明:
其實由於fstream類使用緩沖區來存儲中間數據,因此指針指向的是緩沖區的位置,而不是真正的文件中的位置。
檢查文件指針的當前位置:
對於輸入流,使用tellg();
對於輸出流,使用tellp();
它們都返回一個表示當前位置距離起始位置的偏移量,單位為字節。
在系統中需要創建臨時文件時,可以使用cstdio中的char* tmpnam(char* pszName)函數,該函數可以生成TMP_NAM個不同的臨時文件名,其中每個文件名包含的字符不超過L_tmpnam個。
1 #include <cstdio> 2 #include <iostream> 3 4 using namespace std; 5 6 int main() 7 { 8 char pszName[L_tmpnam] = {'\0'}; 9 cout << "10 temp file names are follows: " << endl; 10 for(int i = 0; i < 10; i++) 11 { 12 tmpnam(pszName); 13 cout << pszName << " "; 14 } 15 cout << endl; 16 17 return 0; 18 } 19 20 /** output */ 21 10 temp file names are follows: 22 \s4ic. \s4ic.1 \s4ic.2 \s4ic.3 \s4ic.4 \s4ic.5 \s4ic.6 \s4ic.7 \s4ic.8 \s4ic.9
[2.4] 內核格式化
iostream提供程序與終端之間的I/O;
fstream提供程序和文件之間的I/O;
sstream提供程序和string對象之間的I/O。
讀取string對象中的格式化信息或者將格式化的信息寫入到string對象中被稱為內核的格式化(incore formatting):
sstream類定義了一個從ostream類派生而來的ostringstream類,當創建了一個ostringstream對象時,就可以將信息寫入其中。該對象使用動態內存分配來增大緩沖區。ostringstream類有一個名為str()的成員函數,該函數返回一個被初始化為緩沖區內容的string對象。
string mesg = outstr.str();
注意:使用str()方法后不能再對ostringstream對象進行寫操作。
與之相對的有istringstream類。
1 #include <iostream> 2 #include <sstream> 3 #include <string> 4 using namespace std; 5 6 int main() 7 { 8 ostringstream outstr; // 管理一個string流 9 string name; 10 cout << "What's the name of your hard disk ? : "; 11 getline(cin, name); 12 cout << "What's the capacity in GB ? : "; 13 int cap; 14 cin >> cap; 15 16 outstr << "The name is : " << name << ", and the capacity is " << cap << " GB." << endl; 17 string str = outstr.str(); 18 19 // <11111> 20 cout << "<11111>" << endl; 21 cout << str << endl; 22 23 // <22222> 24 cout << "<22222>" << endl; 25 istringstream instr(str); 26 string word; 27 while(instr >> word) // read a word every a time 28 cout << word << endl; 29 30 return 0; 31 } 32 33 /** output */ 34 What's the name of your hard disk ? : TOSHIBA 35 What's the capacity in GB ? : 320 36 <11111> 37 The name is : TOSHIBA, and the capacity is 320 GB. 38 39 <22222> 40 The 41 name 42 is 43 : 44 TOSHIBA, 45 and 46 the 47 capacity 48 is 49 320 50 GB.