6.8 簡單文件輸入/輸出

有時候,通過鍵盤輸入並非最好的選擇。例如,假設您編寫了一個股票分析程序,並下載了一個文件,其中包含1000種股票的價格。

在這種情況下,讓程序直接讀取文件,而不是手工輸入文件中的所有的值,將方便得多。同樣,讓程序將輸出寫入到文件將更爲方便,這樣可得到有關結果的永久性記錄。

幸運的是,C++使得將讀取鍵盤輸入和在屏幕上顯示輸出(統稱爲控制檯輸入/輸出)的技巧用於文件輸入輸入/輸出(文件I/O)非常簡單。這裏簡單介紹的文本文件I/O。

6.8.1 文本I/O和文本文件

使用cin進行輸入時,程序將輸入視爲一系列的字節,其中每個字節都被解釋爲字符編碼。不管目標數據類型是什麼,輸入一開始都是字符數據——文本數據。然後cin對象負責將文本轉換爲其他類型。

爲說明這是如何完成的,來看一些處理同一個輸入行的代碼。
假設有如下示例輸入行:
38.5 19.2
來看下使用不同數據類型的變量存儲時,cin是如何處理該輸入行的。

首先,來看使用char數據類型的情況:

char ch;
cin>>ch;

輸入行中的第一個字符被賦給ch。在這裏,第一個字符是數字3,其字符編碼(二進制)被存儲在變量ch中。輸入和目標變量都是字符,因此不需要進行轉換。注意,這裏存儲的數值3,而是字符3的編碼。執行上述輸入語句後,輸入隊列中的下一個字符爲字符8,下一個輸入操作將對其進行處理。

接下來看int類型:
int n;
cin>>n;
在這種情況下,cin將不斷讀取,直到遇到非數字字符。也就是說,它將讀取3和8,這樣句點將成爲輸入隊列中的下一個字符。cin通過計算髮現,這兩個字符對應數值38,因此將38的二進制編碼複製到變量n中。

接下來看double類型:
double x;
cin>>x;
在這種情況下,cin將不斷讀取,直到遇到第一個不屬於浮點數的字符。也就是說,cin讀取3、8、句點和5,使得空格稱爲輸入隊列中行的下一個字符。cin通過計算髮現,這四個字符對應數值38.5,因此將38.5的二進制編碼(浮點格式)複製到變量x中。

接下來看看char數組的情況:
char word[50];
cin>>word;
在這種情況下,cin將不斷讀取,直到遇到空白字符。也就是說,它讀取3、8、句點和5,使得空格稱爲輸入隊列中的下一個字符。然後,cin將這4個字符的字符編碼存儲到數組word中,並在末尾加上一個空字符。這裏不需要任何轉換。

最後來看一下另一種使用char數組來存儲輸入的情況:
char word[50];
cin.getline(word,50);
在這種情況下,cin將不斷讀取,直到遇到換行符(示例輸入行少於50個字符)。所有字符都被存儲到數組word中,並在末尾加上一個空字符。換行符被丟棄,輸入隊列中的下一個字符是下一行中的第一個字符。這裏不需要進行任何轉換。

最後來看一下另一種使用char數組來存儲輸入的情況:
char word[50];
cin.getline(word,50);

在這種情況下,cin將不斷讀取,直到遇到換行符(示例輸入行少於50個字符)。所有字符都被存儲到數組word中,並在末尾加上一個空字符。換行符被丟棄,輸入隊列中的下一個字符是下一行中的第一個字符。這裏不需要進行任何轉換。

對於輸入,將執行相反的轉換。即整數被轉換爲數字字符序列,浮點數被轉換爲數字字符和其他字符組成的字符序列,浮點數被轉換爲數字字符和其他字符組成的字符序列(如284.53)。字符數據不需要做任何轉換。
輸入一開始爲文本。因此,控制檯輸入的文件版本是文本文件,即每個字節都存儲了一個字符編碼的文件。並非所有的文件都是文本文件。例如,數據庫和電子表格以數值格式(即二進制整數或浮點格式)來存儲數值數據。另外,字處理文件中可能包含文本信息,但也可能包含用於描述格式、字體、打印機等的非文本數據。
本章討論的文件I/O相當於控制檯I/O,因此僅適用於文本文件。

6.8.2寫入到文本文件中

對於文件輸入,C++使用類似於cout的東西。下面來複習一些有關將cout用於控制輸出的基本事實,爲文件輸出做準備。
 必須包含頭文件iostream。
 頭文件iostream定義了一個用處理輸出的ostream類。
 頭文件iostream聲明瞭一個名爲cout的ostream變量(對象)
 必須指明名稱空間std;例如,爲引用元素cout和endl,必須使用編譯指令using或前綴std::。
 可以結合使用cout和運算符<<來顯示各種類型的數據。

文件輸出與此極其相似。
 必須包含頭文件fstream。
 頭文件fstream定義了一個用於處理輸出的ofstream類。
 需要聲明一個或多個ofstream變量(對象),並以自己喜歡的方式對其進行命名,條件是遵守常用的命名規則。
 必須指明名稱空間std;例如,爲引用元素ofstream,必須使用編譯指令using或前綴std::。
 需要將ofstream對象與文件關聯起來。爲此,方法之一是使用open()方法。
 使用完文件後,應使用方法close()將其關閉。
 可結合使用ofstream對象和運算符<<來輸出各種類型的數據。

注意,雖然頭文件iostream提供了一個預先定義好的名爲cout的ostream對象,但您必須聲明自己的ofstream對象,爲其命名,並將其同文件關聯起來。
下面演示瞭如何聲明這種對象:
ofstream outFile; //outFile an ofstream object
ofstream fout; //fout an ofstream object

下面演示瞭如何將這種對象與特定的文件關聯起來:
outFile.open(“fish.txt”); //outFile used to write to the fish.txt file
char filename[50];
cin>>filename; //user specifies a name
fout.open(filename); //fout used to read specified file

注意,方法open()接受一個C-風格字符串作爲參數,這可以是一個字面字符串,也可以是存儲在數組中的字符串。
下面演示瞭如何使用這種對象:
double wt = 125.8;
outFile<<wt; //write a number to fish.txt
char line[81] = “Objects are closer than they appear.”;
fout<<line<<endl; //write a line of text

重要的是,聲明一個ofstream對象並將其同文件關聯起來後,便可以像使用cout那樣使用它,所有可用於cout的操作和方法(如<<、endl和setf())都可用於ofstream對象。

總之,使用文件輸出的主要步驟如下:
1. 包含頭文件fstream。
2. 創建一個ofstream對象。
3. 將該ofstream對象同一個文件關聯起來。
4. 就像使用cout那樣使用該ofstream對象。

程序清單6.15中的程序演示了這種方法。它要求用戶輸入信息,然後將信息顯示到屏幕上,再將這些信息寫入到文件中。讀者可以使用文本編輯器來查看該輸出文件的內容。

//outfile.cpp -- writing to a file
#include<iostream>
#include<fstream>	//for file I/O

int main()
{
	using namespace std;
	char automobile[50];
	int year;
	double a_price;
	double d_price;
	ofstream outFile;		//create object for output
	outFile.open("carinfo.txt");	//associate with a file

	cout << "Enter the make and model of automobile: ";
	cin.getline(automobile, 50);
	cout << "Enter the model year: ";
	cin >> year;
	cout << "Enter the original asking price: ";
	cin >> a_price;
	d_price = 0.913 * a_price;

//display information on screen with cout

	cout << fixed;
	cout.precision(2);
	cout.setf(ios_base::showpoint);
	cout << "Make and model: " << automobile << endl;
	cout << "Year: " << year << endl;
	cout << "Was asking $" << a_price << endl;
	cout << "Now asking $" << d_price << endl;

//now do exact same things using outFile instead of cout
	outFile << fixed;
	outFile.precision(2);
	outFile.setf(ios_base::showpoint);
	outFile << "Make and model: " << automobile << endl;
	outFile << "Year: " << year << endl;
	outFile << "Was asking $" << a_price << endl;
	outFile << "Now asking $" << d_price << endl;
	outFile.close();	//done with file
	return 0;
}

該程序的最後一部分與cout部分相同,只是將cout替換爲outFile而已。下面是該程序的運行情況:
在這裏插入圖片描述
屏幕輸出是使用cout的結果。如果您查看該程序的可執行文件所在的目錄,將看到一個名爲carinfo.txt的文新建(根據編譯器的配置,該文件也可能位於其他文件夾),其中包含使用outFile生成的輸出。如果使用文本編譯器打開該文件,將發現其內容如下:
在這裏插入圖片描述
正如讀者看到的,outFile將cout顯示到屏幕上的內容寫入到了文件carinfo.txt中。

 程序說明

在程序清單6.15的程序中,聲明一個ofstream對象後,便可以使用方法open()將該對象特定文件關聯起來:

ofstream outFile; //create object for output
outFile.open(“carinfo.txt”); //associate with a file

程序使用完該文件後,應該將其關閉:
outFile.close();

注意,方法close()不需要使用文件名作爲參數,這是因爲outFile已經同特定的文件關聯起來。如果您忘記關閉文件,程序正常終止時將自動關閉它。
outFile可以使用cout可使用的任何方法。他不但能夠使用運算符<<,還可以使用各種格式化方法,如setf()和precision()。這些方法隻影響調用它們的對象。例如,對於不同對象,可以提供不同的值:
cout.precision(2); //use a precision of 2 for the display
outFile.precision(4); //use a precision of 4 for file output
讀者需要記住的點是,創建好ofstream對象後,便可以像cout那樣使用它。
回到open()方法:
outFile.open(“carinfo.txt”);
在這裏,該程序運行之前,文件carinfo.txt並不存在。在這種情況下,方法open()將新建一個名爲carinfo.txt的文件。如果在此運行該程序,文件carinfo.txt將存在,此時情況將如何呢?默認情況下,open()將首先截斷該文件,即將其長度截短到零¬——丟棄原有的內容,然後將新的輸出加入到該文件中。

打開文件用於接受輸入時可能失敗。例如,指定的文件可能已經存在,但禁止對其進行訪問。
因此細心的程序員將檢查打開文件的操作是否成功,這將在下一個例子中介紹。

6.8.3讀取文本文件

接下來介紹文本文件輸入,它是基於控制檯輸入的。控制檯輸入涉及多個方面,下面首先總結這些方面:

 必須包含頭文件iostream。  頭文件iostream定義了一個用處理輸入的istream類。
 頭文件iostream聲明瞭一個名爲cin的istream變量(對象)。
 必須指明名稱空間std;例如,爲引用元素cin,必須使用編譯指令using或前綴std::。
 可以結合使用cin和運算符>>來讀取各種類型的數據。
 可以使用cin和get()方法來讀取一個字符,使用cin和getline()來讀取一行字符。
 可以結合使用cin和eof()、fail()方法來判斷輸入是否成功。
 對象cin本身被用作測試條件時,如果最後一個讀取操作成功,它將被轉換爲布爾值true,否則被轉換爲false。

文件輸出與此極其相似:

 必須包含頭文件fstream。  頭文件fstream定義了一個用於處理輸入的ifstream類。
 需要生命一個或多個ifstream變量(對象),並以自己喜歡的方式對其進行命名,條件是遵守常用的命名規則。
 必須指明名稱空間std;例如,爲引用元素ifstream,必須使用編譯指令using或前綴std::。
 需要將ifstream對象與文件關聯起來。爲此,方法之一是使用open()方法。  使用完文件後,應使用close()方法將其關閉。
 可結合使用ifstream對象和運算符>>來讀取各種類型的數據。
 可以使用ifstream對象和get()方法來讀取一個字符,使用ifstream對象和getline()來讀取一行字符。
 可以結合使用ifstream和eof()、fali()等方法來判斷輸入是否成功。
 ifstream對象本身被用作測試條件時,如果最後一個讀取操作成功,它將被轉換爲布爾值true,否則被轉換爲false。

注意,雖然頭文件iostream提供了一個預先定義好的名爲cin的iostream對象,但您必須聲明自己的ifstream對象,爲其命名,並將其同文件關聯起來。

下面演示瞭如何聲明這種對象。
ifstream inFile; //inFile an ifstream object
ifstream fin; //fin an ifstream object

下面演示瞭如何將這種對象與特定的文件關聯起來:
inFile.open(“bowling.txt”); //inFile used to read bowling.txt file
char filename[50];
cin>>filename; //user specifies a name
fin.open(filename); //fin used to read specified file

注意,方法open()接受一個C-風格字符串作爲參數,這可以是一個字面字符串,也可以是存儲在數組中的字符串。
下面演示瞭如何使用這種對象:
double wt;
inFile >> wt; //read a number from bowling.txt
char line[81];
fin.getline(line,81); //read a line of text

重要的是,聲明一個ifstream對象並將其同文件關聯起來後,便可以像使用cin那樣使用它。所有可用於cin的操作和方法都可用於ifstream對象(如前述示例的inFile和fin)。
如果試圖打開一個不存在的文件用於輸入,情況將如何呢?這種錯誤將導致後面使用ifstream對象進行輸入時失敗。檢查文件是否被成功打開的首先方法是使用方法is_open(),爲此,可以使用類似於下面的代碼:

ifFilen.open(“bowling.txt”);
if ( !inFile.is_open() )
{
exit ( EXIT_FAILURE) ;
}

如果文件被成功打開,方法is_open()將返回true;因此如果文件沒有被打開,表達式 !inFile.isopen()將爲true。函數exit()的原型是在頭文件cstdlib中定義的,在該頭文件中,還定義了一個用於同操作系統通信的參數值EXIT_FAILURE。函數exit()終止程序。
方法is_open( )是C++中相對較新的內容。如果讀者的編譯器不支持他,可使用較老的方法good()來代替。
程序清單6.16中的程序打開用戶指定的文件,讀取其中的數字,然後指出文件中包含多少個值以及它們的和與平均值。正確地設計輸入循環至關重要。

//sumafile.cpp -- funxxtions with an array argument
#include<iostream>
#include<fstream>		//file I/O supprot
#include<cstdlib>		//support for exit( )
const int SIZE = 60;
int main()
{
	using namespace std;
	char filename[SIZE];
	ifstream inFile;		//objext for handling file input
	cout << "Enter name of data file: ";
	cin.getline(filename, SIZE);
	inFile.open(filename);	//associate inFile with a file
	if (!inFile.is_open())	//failed to open file
	{
		cout << "Could not open the file " << filename << endl;
		cout << "Program terminating.\n";
		exit(EXIT_FAILURE);
	}
	double value;
	double sum = 0.0;
	int count = 0;	//number of items read

	inFile >> value;	//get first value
	while (inFile.good())	//while input good and not at EOF
	{
		++count;	//one more item read
		sum += value;	//calculate running total
		inFile >> value;	//get next value
	}if (inFile.eof())
		cout << "End of file reached.\n";
	else if (inFile.fail())
		cout << "Input terminated by data mismatch.\n";
	else
		cout << "Input terminated for unknown reason.\n";
	if (count == 0)
		cout << "No data processed.\n";
	else
	{
		cout << "Items read: " << count << endl;
		cout << "Sum: " << sum << endl;
		cout << "Average: " << sum / count << endl;
	}inFile.close();		//finished with the file
	return 0;
}

要運行程序清單6.16中的程序,首先必須創建一個包含數字的文本文件。爲此可用文本編輯器(如用於編寫源代碼的文本編輯器)假設該文件名爲scores.txt,包含的內容如下:

18 19 18.5 13.5 14
16 19.5 20 18 12 18.5
17.5

程序中還必須能夠找到這個文件。通常,除非在輸入的文件名中包含路徑,否則程序將在可執行文件所屬的文件夾中查找
警告:Windows文本文件的每行都以回車字符和換行符結尾;通常情況下,C++在讀取文件時將這兩個字符轉換爲換行符,並在寫入文件時執行相反的轉換。有些文本編輯器不會自動在最後一行末尾加上換行符。因此,如果讀者使用的是這種編譯器,請在輸入最後的文本按下回車鍵,然後再保存文件。
下面試程序的運行情況:
在這裏插入圖片描述

程序說明

該程序沒有使用硬編碼文件名,而是將用戶提供的文件名存儲到字符數組filename中,然後將該數組用作open()的參數:ifFile.open(filename);

檢查文件是否被成功打開至關重要。下面是一些可能出現問題的地方:
 指定的文件可能不存在;
 文件可能位於另一個目錄(文件夾)中;
 訪問可能被拒絕;
 用戶可能輸錯了文件名或者省略了文件擴展名。

很多初學者花了大量的時間檢查文件讀取循環的哪裏出了問題後,最終卻發現問題在於程序沒有打開文件。檢查文件是否被成功打開可避免將經歷放在錯誤地方的情況發生。

讀取文件時,有幾點需要檢查。
 首先,程序讀取文件時不應超過EOF。如果最後一次讀取數據時遇到EOF,方法eof()將返回true。

 其次,程序可能遇到類型不匹配的情況。
例如,程序清單6.16期望文件中只包含數字。如果最後一次讀取操作中發生了類型不匹配的情況,方法fail()將返回true(如果遇到EOF,該方法也將返回true)。

 最後,可能出現意外的問題,如文件受損或硬件故障。
如果最後一次讀取文件時發生了這樣的問題,方法bad()將返回true。不要分別檢查這些情況,一種更簡單的方法是使用good()方法,該方法在沒有發生任何錯誤時返回true:

while (inFile.good() ) 		//while input good and not at EOF
{}

然後,如果願意,可以使用其他方法來確定循環終止的真正原因:

if (inFile.eof() )
	cout<<”End of file reached.\n”;
else if(inFile.fali() )
	cout<<”Input terminated by data mismatch.\n”;
else 
	cout<<”Input terminated for unknown reason.\n”;

方法good()指出最後一次讀取輸入的操作是否成功,這一點至關重要。這意味着應該裝在執行讀取輸入的操作後,立刻應用這種測試。爲此,一種標準方法是,在循環之前(首次執行循環測試前)放置一條輸入語句,並在循環末尾(下次執行循環測試之前)放置另一條輸入語句:

//standard file-reading loop design
	inFile>>value;	//get first value
	while(inFile.good() )	//while input good and not at EOF
{
	//loop body goes here
	inFile>>value;	//get next value
}

鑑於以下事實,可以對上述代碼進行精簡:表達式inFile>>value的結果爲inFile,而在需要一個bool值的情況下,inFile的結果爲inFile.good(),即true或false。因此,可以將兩條輸入語句用一條用作循環測試的輸入語句代替。也就是說,可以將上述循環結構替換爲如下循環結構:

	//abbreviated file-reading loop design
	//omit pre-loop input
	while(inFile>>value)	//read and test for success
	{
		//loop body goes here
		//omit end-of-loop input
	}

這種設計仍然遵循了在測試之前進行讀取的規則,因爲要計算表達式inFile>>value的值,程序必須首先試圖將一個數字讀取到value中。

By——Suki
2020/5/20

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章