当前位置:嗨网首页>书籍在线阅读

23-读取文本文件

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

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()、fail()等方法来判断输入是否成功。
  • ifstream对象本身被用作测试条件时,如果最后一个读取操作成功,它将被转换为布尔值true,否则被转换为false。

注意,虽然头文件iostream提供了一个预先定义好的名为cin的istream对象,但您必须声明自己的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(),为此,可以使用类似于下面的代码:

inFile.open("bowling.txt");
if (!inFile.is_open())
{
    exit(EXIT_FAILURE);
}

如果文件被成功地打开,方法is_open()将返回true;因此如果文件没有被打开,表达式!inFile.is_open()将为true。函数exit()的原型是在头文件cstdlib中定义的,在该头文件中,还定义了一个用于同操作系统通信的参数值EXIT_FAILURE。函数exit()终止程序。

方法is_open()是C++中相对较新的内容。如果读者的编译器不支持它,可使用较老的方法good()来代替。正如第17章将讨论的,方法good()在检查可能存在的问题方面,没有is_open()那么广泛。

程序清单6.16中的程序打开用户指定的文件,读取其中的数字,然后指出文件中包含多少个值以及它们的和与平均值。正确地设计输入循环至关重要,详细请参阅后面的“程序说明”。注意,通过使用了if语句,该程序受益匪浅。

程序清单6.16 sumafile.cpp

// sumafile.cpp -- functions with an array argument
#include <iostream>
#include <fstream>        // file I/O support
#include <cstdlib>        // support for exit()
const int SIZE = 60;
int main()
{
    using namespace std;
    char filename[SIZE];
    ifstream inFile;        // object 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++在读取文件时将这两个字符转换为换行符,并在写入文件时执行相反的转换。有些文本编辑器(如Metrowerks CodeWarrior IDE编辑器),不会自动在最后一行末尾加上换行符。因此,如果读者使用的是这种编辑器,请在输入最后的文本后按下回车键,然后再保存文件。

下面是该程序的运行情况:

Enter name of data file: scores.txt
End of file reached.
Items read: 12
Sum: 204.5
Average: 17.0417

程序说明

该程序没有使用硬编码文件名,而是将用户提供的文件名存储到字符数组filename中,然后将该数组用作open()的参数:

inFile.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.fail())
    cout << "Input terminated by data mismatch.\n";
else
    cout << "Input terminated for unknown reason.\n";

这些代码紧跟在循环的后面,用于判断循环为何终止。由于eof()只能判断是否到达EOF,而fail()可用于检查EOF和类型不匹配,因此上述代码首先判断是否到达EOF。这样,如果执行到了else if测试,便可排除EOF,因此,如果fail()返回true,便可断定导致循环终止的原因是类型不匹配。

方法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中。

至此,读者对文件I/O有了初步的认识。