690 likes | 781 Views
Chap 12 資料流與檔案的存取. 如果程式所處理的資料只能寫在原始程式內部,或以互動的方式由鍵盤逐一輸入,則功能將很有限。本章探討如何從檔案讀取資料,以及將處理後的資料存入檔案的方法。. 資料流與檔案的存取. 12.1 資料流 12.2 檔案的存取 12.3 檔案的存取模式 12.4 資料的讀取與寫入 12.5 檔案內容的位置標記 12.6 將檔案的存取寫成函數. 資料流 (stream). 回顧標準的輸出入管道 cout 和 cin : 範例程式 檔案 BasicIO.cpp // BasicIO.cpp
E N D
Chap 12 資料流與檔案的存取 如果程式所處理的資料只能寫在原始程式內部,或以互動的方式由鍵盤逐一輸入,則功能將很有限。本章探討如何從檔案讀取資料,以及將處理後的資料存入檔案的方法。
資料流與檔案的存取 • 12.1 資料流 • 12.2 檔案的存取 • 12.3 檔案的存取模式 • 12.4 資料的讀取與寫入 • 12.5 檔案內容的位置標記 • 12.6 將檔案的存取寫成函數
資料流 (stream) • 回顧標準的輸出入管道cout和cin: • 範例程式 檔案 BasicIO.cpp // BasicIO.cpp #include <iostream> using namespace std; int main () { int N; cout << “請輸入一個整數:” << endl; cin >> N; cout << “您輸入的是 “ << N << endl; return 0; }
操作結果 請輸入一個整數: 4563 您輸入的是 4563
物件 (object) 和類別 (class) • 物件(object),其原文有「個體」、「對象」等多種意涵。 • 類別是一種廣義的資料型態,它可以由程式寫作者自行定義。 • 由類別定義的「物件」不僅包括資料,而且還包括與物件進行互動所需要的各種函數。 • cin和cout分別屬於istream和ostream兩個類別。我們不需要定義cin和cout,因為它們是最常用的預設物件。
驅動程式 • 驅動程式 (drivers) 是作業系統 (例如Windows和Linux) 的一部份,用來與硬體 (譬如鍵盤和顯示器) 溝通,以處理硬體裝置與記憶體之間的資料流動。如下圖所示: 資料流 (stream) 與硬體之間的關係圖
使用成員函數 (member functions) 的語法 • 類別(Class) 內部所定義的各種函數稱為成員函數(member functions) 或方法(methods)。 • 敘述 cin.get(Ch); 裏,句點「.」稱為成員運算符號(member operator),它的前面是物件的名稱,而其後是成員函數的名稱。
檔案 • 在一個名稱之下,內含有資料的獨立儲存單位。檔案通常儲存於硬碟、磁片、光碟、磁帶或 MO 等媒體上。
檔案的存取 • 程式開頭要加入標頭檔: <fstream>,以含入必要的類別宣告。 • 必需明確地宣告和定義讀寫檔案所需的管道 (亦即資料流)。例如: 要讀取檔案時,定義類別為ifstream 的輸入型檔案資料流: ifstream FileInput; 要寫入檔案時,則需定義類別為ofstream的輸出型檔案資料流: ofstream FileOutput; 讀寫檔案前,需要先執行將檔案「開啟」 (open) 的動作。讀寫完畢後,最好執行將檔案「關閉」(close) 的動作。
資料存取的語法 • 一旦檔案被開啟,則資料存取的語法和先前cin、cout的用法相同。例如: FileInput >> x; FileOutput << x << endl • 程式內部所直接使用的是各個「已經聯結到特定檔案的檔案資料流」,而不再提及檔案的名稱。
將資料流聯結到檔案的語法 • 例如,在檔案開啟時將敘述寫成: FileInput.open(“FileA.txt”); • 檔案關閉時,則不用再寫出檔案名稱,只需寫成: FileInput.close();
範例程式TaxFile.cpp • 從檔案Income.txt將收入的數值讀到變數Income裏,再呼叫函數CalculateTax() 以計算稅額Tax,最後把稅額的數值輸出到顯示器,並同時存到檔案Tax.txt裏。 • 示範了如何宣告資料流,如何開啟和關閉檔案,以及如何在檔案裏存取資料的細節。
範例程式 檔案 TaxFile.cpp // TaxFile.cpp #include <iostream> #include <fstream> using namespace std; double CalculateTax(double); // 函數的宣告 // ---主程式------------------------ int main() { char* FileNameIn = "Income.txt"; // 以字串的方式宣告檔名 char* FileNameOut = "Tax.txt"; double Income, Tax; ifstreamFileInput; ofstreamFileOutput; FileInput.open ( FileNameIn ); FileOutput.open( FileNameOut );
if (!FileInput) { cout << "檔案: " << FileNameIn << " 開啟失敗!" << endl; exit(1); } if (!FileOutput) { cout << "檔案: " << FileNameOut << " 存檔失敗!" << endl; exit(1); } FileInput >> Income; Tax = CalculateTax(Income); cout << "您要繳的綜合所得稅是: " << Tax << " 元";
FileOutput << "您要繳的綜合所得稅是: " << Tax << " 元"; FileInput.close(); // 關閉輸入檔案 FileOutput.close(); // 關閉輸出檔案 return 0; } // ---函數 CalculateTax() 的定義----- // 改寫自4.4節「巢狀if-else敘述」 // 程式 Tax.cpp 以計算稅額 double CalculateTax(double GIncome) { double Tax; if (GIncome < 0.0) cout << "您輸入的綜合所得淨額沒有意義!\n"; else if (GIncome < 330000.0) Tax = GIncome * 0.06;
else if (GIncome < 890000.0) Tax = GIncome * 0.13 - 23100; else if (GIncome < 1780000.0) Tax = GIncome * 0.21 - 94300; else if (GIncome < 3340000.0) Tax = GIncome * 0.3 - 254500; else Tax = GIncome * 0.4 - 588500; return Tax; }
操作結果 您要繳的綜合所得稅是:50715.2元
檔案的存取模式 1. create (創造) 和replace (取代): 如果檔案不存在,要不要產生一個新的檔案,如果檔案已經存在,要不要將其取代。 2. append (附加) 和 replace (取代): 從資料流輸入的資料要接在原來的內容之後,還是要取代原來的內容。
另外一種檢查開檔動作有沒有失敗的語法 • 將開檔部份的敘述改寫如下: FileInput.open (FileNameIn, ios::nocreate); if (FileInput.fail()) { cout << "檔案: " << FileNameIn << " 開啟失敗!" << endl; exit(1); }
結合資料流的宣告敘述和開檔的動作 • 如果檔案資料流FileInput在程式中只跟一個檔案聯結,則可以進一步寫成: ifstream FileInput (FileName, ios::nocreate); • 參數 nocreate 又稱為模式指示參數(mode indicator)。
統一使用fstream類別其後區別該資料流的方向 fstream FileInput; fstream FileOutPut; FileInput.open(FileName, ios::in); FileOutput.open(FileName, ios::out);
結合模式指示參數 • 進一步決定「如果要開啟以供輸入的檔案不存在」的處理方式,使用「或」 (也就是or) 的語法: FileInput.open(FileName, ios::in | ios::nocreate);
EOF (end-of-file) • 檔案的結束記號。 • EOF為標頭檔 <fstream> 內定義的常數。 • 它在Windows和DOS作業系統都是使用第26個ASCII字元‘Ctrl-Z’,在某些作業系統內EOF的值設為 –1。
範例程式ConvFile.cpp • 使用本節介紹的語法進行讀檔與存檔的操作。首先從檔案Original.txt 逐一讀進字母,再經 <cctype> 中的函數toupper()將字母轉換為大寫字母後,逐一存進檔案Converted.txt 中。
範例程式 檔案 ConvFile.cpp // ConvFile.cpp #include <iomanip> #include <iostream> #include <fstream> #include <cctype> using namespace std; char* FileNameIn = "Original.txt"; char* FileNameOut = "Converted.txt"; // ---函數 Sort() 的宣告---- int Sort(char X); // ---主程式------------------------ int main() {
char C; fstream FileInput(FileNameIn, ios::in); if (!FileInput) { cout << "檔案: " << FileNameIn << " 開啟失敗!" << endl; exit(1); } fstream FileOutput(FileNameOut, ios::out); if (!FileOutput) { cout << "檔案: << FileNameOut << " 存檔失敗!" << endl; exit(1); }
while ((C = FileInput.get())!= EOF) { switch (Sort(C)) { case 1: FileOutput << char(toupper(C)); break; case 0: case 2: case 3: case 4: FileOutput << C ; break; case 5: FileOutput << "Other" << endl; break; default: cout << "程式有問題!" << endl; } }
FileOutput.close(); FileInput.close(); cout << "成功存於檔案 " << FileNameOut << " 內.\n"; return 0; } // ---函數 Sort() 的定義--------- int Sort(char X) { if (isupper(X)) return 0; else if (islower(X)) return 1;
else if (isdigit(X)) return 2; else if (isspace(X)) return 3; else if (ispunct(X)) return 4; else return 5; }
操作結果 (在顯示器上會看到下列訊息:) 成功存於檔案 Conerted.txt 內. • 檔案Original.txt 的內容是:I love you, certainly. • 檔案Converted.txt 的內容是:I LOVE YOU, CERTAINLY.
檔案資料流 fstream 的成員函數 表12.4fstream的成員函數
範例程式RWFiles.cpp • 從檔案 Record.txt 將字串資料和數值資料分別讀到字串陣列Name 和數值陣列Score裏,再單獨對Score進行計算,最後把字串和數值存到檔案 Saved.txt 裏。 • 這個程式示範輸入資料流的成員函數peek()的使用細節,它用來檢查即將讀入的字元是否為 EOF。
範例程式 檔案 RWFiles.cpp // RWFiles.cpp #include <iostream> #include <iomanip> #include <fstream> using namespace std; char* FileNameIn = "Record.txt"; char* FileNameOut = "Saved.txt"; // ---主程式------------------------ int main() { const int MaxNum = 40; const int MaxSize = 20; charName[MaxNum][MaxSize]; int Score[MaxNum];
fstreamFileInput(FileNameIn, ios::in); if (!FileInput) { cout << "檔案: " << FileNameIn << " 開啟失敗!" << endl; exit(1);} fstream FileOutput(FileNameOut, ios::out); if (!FileOutput) { cout << "檔案: " << FileNameOut << " 存檔失敗!" << endl; exit(1);} int Count=0; while (FileInput.peek()!= EOF && (Count < MaxNum)) { FileInput >> Name[Count] >> Score[Count]; Count++; }
for (int i=0; i< Count; i++) { Score[i] = Score[i]*0.8+20; FileOutput << '(' << i+1 << ')' << setw(12) << Name[i] << " " << setw(5) << Score[i] << endl; } FileOutput.close(); FileInput.close(); cout << "成功存於檔案 " << FileNameOut << " 內." << endl; return 0; }
操作結果 (在顯示器上會看到下列訊息:) 成功存於檔案Saved.txt 內. • 檔案Record.txt 的內容是: 王能治 87 李大為 92 蕭飛雪 78 張心怡 86 • 檔案Saved.txt 的內容是: (1) 王能治 89 (2) 李大為 93 (3) 蕭飛雪 82 (4) 張心怡 88
檔案內容的位置標記(File Position Marker) • 位置標記使用「相對位置」的觀念:從某個位置當起點,再加上偏移量(offset) 以到達所要的位置。 • 按照起點位置的不同,位置標記在使用上有下列三種模式 (modes of the file position marker): • ios::beg從檔案的開頭算起 (beg為begin的縮寫) • ios::cur從目前位置算起 (cur為current的縮寫) • ios::end從檔案結尾算起 (end為結尾的意思)
配合檔案內容的位置標記的成員函數 表12.5.1 配合檔案內容的位置標記的成員函數 • 「位置標記」以及「偏移量」的資料型態必需是長整數(long int)。 • 各成員函數名稱中的g是get (取得),而p是put (放入) 的簡寫。
位置標記的成員函數舉例 1. FileOutput.seekp(5L, ios::cur);:將位置標記在輸出檔案中從目前的位置往前移動5個字元的距離。 2. FileOutput.seekp(2L, ios::beg);:將位置標記在輸出檔案中移到第3個字元的位置。 3. FileOutput.seekp(-5L, ios::end);:將位置標記在輸出檔案中移到倒數第5個字元的位置。 4. FileInput.seekg(0L, ios::beg);:將位置標記移到輸入檔案開頭的位置。 5. FileInput.seekg(0L, ios::end);:將位置標記移到輸入檔案結尾的位置。
使用成員函數tellg()從檔案資料流中獲得已開啟檔案中的字元數目使用成員函數tellg()從檔案資料流中獲得已開啟檔案中的字元數目 • 例如: ifstream FileInput (“Data.txt”);// 開啟檔案 Data.txt long CharNum; FileInput.seekg(0L, ios::end);// 移到檔案結束的地方 CharNum = FileInput.tellg();// 獲得字元數目 就可以得到 Data.txt 中的字元數目,並將它儲存在長整數CharNum中。
使用seekg() 以移動位置標記在已開啟的檔案中游走 • 把位置標記先移到檔案結尾處,再逐一往前移動,把既有檔案中的字元次序顛倒過來: long Offset; char Ch; for (Offset=1L; Offset<= CharNum; Offset++) { FileInput.seekg(-Offset, ios::end); Ch = FileInput.get(); FileOutput << Ch; }
範例程式FileReverse.cpp • 我們將上述倒轉檔案內容的指令寫成完整的範例程式。 • 這個程式首先從檔案Original.txt 讀取資料,將檔案中的字元次序顛倒過來後,逐一存於檔案Reversed.txt中。
範例程式 檔案 FileReverse.cpp // FileReverse.cpp #include <iostream> #include <iomanip> #include <fstream> using namespace std; char* FileNameIn = "Original.txt"; char* FileNameOut = "Reversed.txt"; // ---主程式------------------------ int main() { long CharNum, Offset; char Ch; fstream FileInput(FileNameIn, ios::in);
if (!FileInput) { cout << “檔案“ << FileNameIn<< " 開啟失敗!" << endl; exit(1); } fstream FileOutput(FileNameOut, ios::out); if (!FileOutput) { cout << “檔案” << FileNameOut<< " 存檔失敗!" << endl; exit(1); } cout << “檔案” << FileNameIn<< " 開啟成功" << endl; FileInput.seekg(0L, ios::end); // 移到檔案結束的地方
CharNum = FileInput.tellg(); // 獲得字元數目 for (Offset=1L; Offset<= CharNum; Offset++) { FileInput.seekg(-Offset, ios::end); Ch = FileInput.get(); FileOutput << Ch; } FileOutput.close(); // 關閉FileOutput FileInput.close(); // 關閉FileInput cout << "倒轉檔案 " << FileNameIn << " 後的內容\n成功存於檔案 " << FileNameOut << " 內." << endl; return 0; }
範例程式FileAccess.cpp • WriteString():使用檔案以儲存字串的函數。 • WriteData():使用檔案以儲存數字的函數。 • 以字串為參數傳遞檔案名稱,參數Mode則是模式指示參數 (mode indicator),當Mode為1時是append (附加),Mode為0時是replace (取代)。