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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值