【C++ 程序设计】第 8 章:文件操作

目录

一、文件基本概念和文件流类

(1)文件的概念 

(2)C++ 文件流类 

二、打开和关闭文件

(1)打开文件 

(2)关闭文件 

三、文件读写操作

(1)读写文本文件

(2)读写二进制文件 

① 用 ostream::write() 成员函数写文件

② 用 istream::read() 成员函数读文件

③ 用 ostream::gcount() 成员函数得到读取字节数

(3)用成员函数 put() 和 get() 读写文件

① int get();

② istream& get(char &rch);

③ istream& get(char *pch, int nCount, char delim=’\n’);

④ ostream& put(char ch);

(4)文本文件与二进制文件的异同 

四、随机访问文件

(1)类 istream 中与位置指针相关的函数

① 移动读指针函数

② 返回写指针当前位置的函数

(2)类 ostream 中与位置指针相关的函数 

① 移动写指针函数

② 返回写指针当前位置的函数




一、文件基本概念和文件流类

从不同的角度来看待文件就可以得到不同的文件分类:
  • 根据文件数据的编码方式不同分为:文本文件二进制文件
  • 根据存取方式不同分为:顺序存取文件随机存取文件
所谓 “文本文件” 和 “二进制文件” 是从文件格式的角度进行分类,是约定俗成的、从计算机用户角度出发进行的分类。

(1)文件的概念 

所谓的 “顺序存取文件” 和 “随机存取文件” 是根据访问文件中数据的方式来划分的。
  • 顺序存取文件:就是按照文件中数据存储次序进行顺序操作,为访问第 i 个数据,就首先要访问第 i-1 个数据,在整个文件操作过程中,将移动位置指针的工作交给系统自动完成。
  • 磁带文件:就是一个典型的顺序存取文件。
  • 随机访问文件:是根据应用的需要,通过命令移动位置指针直接定位到文件内需要的位置并进行数据操作。
对文件的基本操作分为读文件和写文件
  • 读文件:就是将文件中的数据读入内存之中,也称为 “输入” 。
  • 写文件:就是将内存中的数据存入文件之中,也称为 “输出” 。

(2)C++ 文件流类 

1. C++ 标准类库中有 3 个流类可以用于文件操作,这 3 个类统称为文件流类,分别如下:
  • ifstream:用于从文件中读取数据。
  • ofstream:用于向文件中写入数据。
  • fstream:既可用于从文件中读取数据,又可用于向文件中写入数据。
2. 使用这 3 个流类时,程序中需要包含  fstream  头文件。
  • 类 ifstream 和类 fstream 都是从类 istream 派生而来的,因此类 ifstream 拥有类 istream 的全部成员函数。
  • 同样,类 ofstream 和类 fstream 也拥有类 ostream 的全部成员函数。
  • 这 3 个类中有一些十分熟悉的成员函数可以使用,如 operator<<、operator>>、peek()、ignore()、getline()、get() 等。
3. 在程序中,要使用一个文件,必须包含 3 个基本步骤:
  • 打开(open)文件 —— 操作文件 —— 关闭(close)文件。
  • 操作文件就是对文件进行读/写。

4. C++ 文件流类有相应的成员函数来实现打开、读、写、关闭等文件操作。



二、打开和关闭文件

打开文件的方式有以下两种:
1. 先建立流对象,然后调用 open() 函数连接外部文件。格式如下:
流类名 对象名;
对象名.open(文件名,模式);
2. 调用流类带参数的构造函数,在建立流对象的同时连接外部文件。格式如下:
流类名 对象名(文件名,模式);
  • 其中的 “流类” 是 C++ 流类库定义的文件流类 ifstream、ofstream 或 fstream。
  • 若以读方式打开文件则应使用类 ifstream
  • 若以写方式打开文件则应使用类 ofstream
  • 若以读/写方式打开文件则应使用类 fstream

(1)打开文件 

模式标记 适用对象 作用
ios::in ifstream
fstream
以读方式打开文件。
如果文件不存在,则打开出错
ios::out ofstream
fstream
以写方式打开文件。
如果文件不存在,则新建该文件;
如果文件已经存在,则打开时淸除原来的内容
ios::app ofstream 以追加方式打开文件,用于在文件尾部添加数据。
如果文件不存在,则新建该文件
ios::ate ofstream 打开一个已有的文件,并将文件读指针指向文件末尾。
如果文件不存在,则打开出错
ios::trunc ofstream 删除文件现有内容。单独使用时与ios::out相同
ios::binary ifstream
ofstream
fstream
以二进制方式打开文件。
若不指定此模式,则以默认的文本模式打开文件
ios::in | ios::out fstream 打开已存在的文件,既可读取其内容,也可向其写入数据。文件刚打开时,原有内容保持不变。
如果文件不存在,则打开出错
ios::in | ios::out ofstream 打开已存在的文件,可以向其写入数据。
文件刚打开时,原有内容保持不变。
如果文件不存在,则打开出错
ios::in | ios::out | ios::trunc fstream 打开文件,既可读取其内容,也可向其写入数据。
如果文件本来就存在,则打开吋清除原来的内容;
如果文件不存在,则新建该文件

【示例一】打开一个输入文件流对象,并以读模式读取文件内容

ifstream inFile; //建立输入文件流对象
inFile.open("data.txt",ios::in); //连接文件,指定打开模式
// 也可以使用第二种方式打开,语句如下:
ifstream inFile("data.txt",ios::in);

【示例代码】 要从当前文件夹中名为 data.txt 的文件中读取数据,可以使用如下语句打开文件:

#include <fstream>    // 包含文件流头文件
#include <iostream>
using namespace std;

int main() {
    ifstream inFile;  // 定义输入文件流对象

    inFile.open("data.txt", ios::in);  // 打开名为 "data.txt" 的输入文件,以读模式打开

    if (!inFile) {  // 如果打开文件失败
        cerr << "Can't open file!" << endl; // 输出错误信息到标准错误流
        return 1;  // 返回 1,表示程序异常结束
    }

    // ... 在此处可以进行文件读取操作 ...

    inFile.close();  // 关闭输入文件流

    return 0;  // 正常结束程序
}

【代码详解】

  • 需要注意的是,在使用文件流读写文件时,必须对打开的文件进行检查,以确保文件打开成功。这里使用了 if (!inFile) 来检查文件是否打开成功,它等价于 if (inFile.fail()),后者可以检查文件流的状态。
  • 除了输入文件流,C++ 标准库还提供了输出文件流、二进制文件流、内存文件流等多种文件流类型,可以满足不同情况下的文件读写需求。在进行文件操作时,应该按照安全有序的原则,首先打开文件,进行必要的检查,然后进行读写操作,最后关闭文件流。
  1. #include <fstream>,引入文件流头文件。

  2. #include <iostream>,引入输入输出流头文件。

  3. using namespace std;,使用标准命名空间。

  4. int main() { ,程序入口。

  5. ifstream inFile;,声明一个输入文件流对象 inFile

  6. inFile.open("data.txt", ios::in);,打开名为 “data.txt” 的文件,并以读模式打开文件流。打开文件时可以指定多个打开选项,常用的选项包括 ios::in(读模式)、ios::out(写模式)、ios::binary(二进制模式)等,这些选项可以通过按位或(|)操作进行组合。

  7. if (!inFile) { ,如果打开文件失败,则执行下面的代码块。

  8. cerr << "Can't open file!" << endl;,输出错误信息到标准错误流 cerr 上,指示打开文件失败。

  9. return 1;,返回结果为 1,表示程序异常结束。

  10. inFile.close();,关闭输入文件流。

  11. return 0;,返回结果为 0,表示程序正常结束。

【执行结果】

  • 这段程序没有具体的执行结果,它只是打开一个名为 “data.txt” 的文件,并以读模式打开输入文件流对象 inFile。此后,程序可以通过 inFile 对象进行文件读取操作,如果读取失败打印错误信息,最后关闭输入文件流对象。
  • 需要注意的是,由于没有具体的文件读取操作,这段程序只是打开了文件并走了一遍判断流状态的流程,因此程序的输出与标准输出没有任何区别。如果 “data.txt” 文件不存在或发生了其他错误,程序会返回 1,表示程序异常结束,否则程序会返回 0,表示程序正常结束。
  • 此外,还需要注意的是,在执行文件读取操作时,必须首先检查文件是否打开成功,避免进行无效的读取或写入操作。在这个例子中,如果打开文件失败,程序会输出错误信息到标准错误流 cerr 上,表示文件打开失败。

【示例二】打开一个输入文件流对象,并以默认读模式读取文件内容

ifstream inFile; //建立输入文件流对象
inFile.open("data.txt"); //没有指定打开模式,默认以in方式打开文本文件

【示例代码】 调用 ifstream 类带参数的构造函数,在建立流对象的同时,用参数形式连接外部文件并指定打开模式。要以读方式打开文本文件,还可以使用如下语句:

#include <fstream>   // 文件流头文件
#include <iostream>
using namespace std;

int main() {
    ifstream inFile;  // 建立输入文件流对象

    inFile.open("data.txt");  // 打开名为 "data.txt" 的输入文件,以默认读模式打开文件流

    if (!inFile) {   // 如果打开文件失败
        cerr << "Can't open file!" << endl;  // 输出错误信息到标准错误流
        return 1;    // 返回 1,表示程序异常结束
    }

    // ... 在此处可以进行文件读取操作 ...

    inFile.close();  // 关闭输入文件流

    return 0;  // 正常结束程序
}

【代码详解】

  • 需要注意的是,在使用文件流读取文件时,必须对打开的文件进行检查,以确保文件打开成功。这里使用了 if (!inFile) 来检查文件是否打开成功,它等价于使用 if (inFile.fail()) 检查文件流的状态。
  • 打开文件时需要注意选择文件打开模式,不同模式可以进行读取和写入操作,常用的文件打开模式包括 ios::in(读模式)、ios::out(写模式)、ios::app(追加模式)、ios::trunc(截断模式)、ios::binary(二进制模式)等。在打开文件时,如果没有指定打开模式,输入文件流对象会默认以读模式打开文件。
  1. #include <fstream>,引入文件流头文件。

  2. #include <iostream>,引入输入输出流头文件。

  3. using namespace std;,使用标准命名空间。

  4. int main() { ,程序入口。

  5. ifstream inFile;,声明一个输入文件流对象 inFile

  6. inFile.open("data.txt");,打开名为 “data.txt” 的文件,并以默认读模式进行打开。此处没有指定打开选项,因此文件流对象 inFile 会使用默认的读取模式 ios::in,这与指定了 ios::in 的效果是一样的。

  7. if (!inFile) { ,如果打开文件失败,则执行下面的代码块。

  8. cerr << "Can't open file!" << endl;,输出错误信息到标准错误流 cerr 上,指示打开文件失败。

  9. return 1;,返回结果为 1,表示程序异常结束。

  10. inFile.close();,关闭输入文件流。

  11. return 0;,返回结果为 0,表示程序正常结束。

【执行结果】

  • 这段程序没有具体的执行结果,它只是打开一个名为 “data.txt” 的文件,并以默认读模式打开输入文件流对象 inFile。此后,程序可以通过 inFile 对象进行文件读取操作,如果读取失败打印错误信息,最后关闭输入文件流对象。
  • 需要注意的是,由于没有具体的文件读取操作,这段程序只是打开了文件并走了一遍判断流状态的流程,因此程序的输出与标准输出没有任何区别。如果 “data.txt” 文件不存在或发生了其他错误,程序会返回 1,表示程序异常结束,否则程序会返回 0,表示程序正常结束。
  • 此外,还需要注意的是,在执行文件读取操作时,必须首先检查文件是否打开成功,避免进行无效的读取或写入操作。在这个例子中,如果打开文件失败,程序会输出错误信息到标准错误流 cerr 上,表示文件打开失败。

【示例三】打开一个输出文件流对象,并以输出和二进制模式写入文件内容

ofstream outFile; //建立输入文件流对象
outFile.open("c:\\c2019\\newfile",ios::out | ios::binary); //连接文件,指定打开模式
// 也可以使用如下语句打开文件:
ofstream outFile("c:\\c2019\\newfile",ios::out | ios::binary);

【示例代码】要在 c 盘的 c2019 文件夹中打开(创建)一个名为 newfile 的二进制文件,用于保存程序产生的数据,可以使用如下语句打开文件:

#include <fstream>   // 文件流头文件
#include <iostream>
using namespace std;

int main() {
    ofstream outFile;  // 建立输出文件流对象

    outFile.open("c:\\c2019\\newfile", ios::out | ios::binary);  // 打开名为 "c:\c2019\newfile" 的输出文件,以输出和二进制模式打开文件流

    if (!outFile) {   // 如果打开文件失败
        cerr << "Can't open file!" << endl;  // 输出错误信息到标准错误流
        return 1;    // 返回 1,表示程序异常结束
    }

    // ... 在此处可以进行文件写入操作 ...

    outFile.close();  // 关闭输出文件流

    return 0;  // 正常结束程序
}

【代码详解】

  • 需要注意的是,在使用文件流写入文件时,必须对打开的文件进行检查,以确保文件打开成功。这里使用了 if (!outFile) 来检查文件是否打开成功,它等价于 if (outFile.fail()),后者可以检查文件流的状态。
  • 除了输出文件流,C++ 标准库还提供了输入文件流、二进制文件流、内存文件流等多种文件流类型,可以满足不同情况下的文件读写需求。在进行文件操作时,应该按照安全有序的原则,首先打开文件,进行必要的检查,然后进行读写操作,最后关闭文件流。
  1. #include <fstream>,引入文件流头文件。

  2. #include <iostream>,引入输入输出流头文件。

  3. using namespace std;,使用标准命名空间。

  4. int main() { ,程序入口。

  5. ofstream outFile;,声明一个输出文件流对象 outFile

  6. outFile.open("c:\\c2019\\newfile", ios::out | ios::binary);,打开名为 “c:\c2019\newfile” 的文件,并以输出和二进制模式打开文件流。打开文件时可以指定多个打开选项,常用的选项包括 ios::in(读模式)、ios::out(写模式)、ios::binary(二进制模式)等,这些选项可以通过按位或(|)操作进行组合。

  7. if (!outFile) { ,如果打开文件失败,则执行下面的代码块。

  8. cerr << "Can't open file!" << endl;,输出错误信息到标准错误流 cerr 上,指示打开文件失败。

  9. return 1;,返回结果为 1,表示程序异常结束。

  10. outFile.close();,关闭输出文件流对象。

  11. return 0;,返回结果为 0,表示程序正常结束。

【执行结果】

  • 这段程序没有具体的执行结果,它只是打开一个名为 “c:\c2019\newfile” 的输出文件,并以输出和二进制模式打开输出文件流对象 outFile。此后,程序可以通过 outFile 对象进行文件写入操作,如果写入失败打印错误信息,最后关闭输出文件流对象。
  • 需要注意的是,由于没有具体的文件写入操作,这段程序只是打开了文件并走了一遍判断流状态的流程,因此程序的输出与标准输出没有任何区别。如果 “c:\c2019\newfile” 文件不存在或发生了其他错误,程序会返回 1,表示程序异常结束,否则程序会返回 0,表示程序正常结束。
  • 此外,还需要注意的是,在执行文件写入操作时,必须首先检查文件是否打开成功,避免进行无效的读取或写入操作。在这个例子中,如果打开文件失败,程序会输出错误信息到标准错误流 cerr 上,表示文件打开失败。需要注意的是,二进制文件流中写入的数据是按照二进制编码存储的,写入的数据必须是二进制数据,否则写入的数据可能无法正确解析。

(2)关闭文件 

使用 fstream 中的成员函数  close()  关闭文件。

【示例】使用文件流打开读取文件和写入文件

【示例代码】 

#include<iostream>   // 输入输出流头文件
#include<fstream>    // 文件流头文件
using namespace std;

int main()
{
    ifstream inFile("c:\\tmp\\test.txt", ios::in);  // 声明对象 inFile 并调用构造函数打开名为 "c:\tmp\test.txt" 的输入文件,以读取模式打开文件流

    if (inFile)  // 如果打开文件成功
    {
        cout << "成功打开文件: c:\\tmp\\test.txt\n";
        inFile.close();  // 关闭输入文件流
    }
    else
        cout << "打开文件失败: c:\\tmp\\test.txt\n";

    ofstream outFile("test1.txt", ios::out);  // 声明对象 outFile 并调用构造函数创建名为 "test1.txt" 的输出文件,以写入模式打开文件流

    if (!outFile)
        cout << "error1" << endl;
    else {
        cout << "成功打开文件: test1.txt\n";
        outFile.close();  // 关闭输出文件流
    }

    fstream outFile2("tmp\\test2.txt", ios::out | ios::in);  // 声明对象 outFile2 并调用构造函数创建名为 "tmp\\test2.txt" 的文件,以输入输出和写入模式打开文件流

    if (outFile2) {
        cout << "成功打开文件:tmp\\test2.txt\n";
        outFile2.close();  // 关闭文件流
    }
    else
        cout << "error2" << endl;

    return 0;  // 正常结束程序
}

【代码详解】

  • 需要注意的是,在使用文件流进行文件操作时,必须首先检查文件是否打开成功,避免进行无效的读取或写入操作。在这个例子中,程序使用了不同的方法创建了三个文件流,并通过检查文件流是否打开成功来判断文件创建是否成功。需要注意的是,在创建一个
  1. #include<iostream> 和 #include<fstream> 引入输入输出流头文件和文件流头文件。

  2. using namespace std;,使用标准命名空间。

  3. int main(),程序入口。

  4. ifstream inFile("c:\\tmp\\test.txt", ios::in);,声明对象 inFile 并调用构造函数打开名为 “c:\tmp\test.txt” 的输入文件,以读取模式打开文件流。

  5. if (inFile),如果打开文件成功,则执行下面的代码块。

  6. cout << "成功打开文件: c:\\tmp\\test.txt\n";,输出成功打开文件的提示信息。

  7. inFile.close();,关闭输入文件流。

  8. else cout << "打开文件失败: c:\\tmp\\test.txt\n";,否则输出打开文件失败的提示信息。

  9. ofstream outFile("test1.txt", ios::out);,声明对象 outFile 并调用构造函数创建名为 “test1.txt” 的输出文件,以写入模式打开文件流。

  10. if (!outFile) cout << "error1" << endl;,如果创建文件失败,则输出错误信息。

  11. else,否则执行下面的代码块。

  12. cout << "成功打开文件: test1.txt\n";,输出成功创建文件的提示信息。

  13. outFile.close();,关闭输出文件流。

  14. fstream outFile2("tmp\\test2.txt", ios::out | ios::in);,声明对象 outFile2 并调用构造函数创建名为 “tmp\test2.txt” 的文件,以输入输出和写入模式打开文件流。

  15. if (outFile2),如果创建文件成功,则执行下面的代码块。

  16. cout << "成功打开文件:tmp\\test2.txt\n";,输出成功创建文件的提示信息。

  17. outFile2.close();,关闭文件流。

  18. else cout << "error2" << endl;,否则输出错误信息。

  19. return 0;,正常结束程序。

【执行结果】

1. 如果 “c:\tmp\test.txt” 存在,“tmp\test2.txt” 不存在,执行上面的代码会输出如下结果:

成功打开文件: c:\tmp\test.txt
成功打开文件: test1.txt
error2
  • 其中,第一行输出成功打开文件 “c:\tmp\test.txt” 的提示信息,表示该文件打开成功,并关闭文件流。
  • 第二行输出成功创建文件 “test1.txt” 的提示信息,表示该文件创建成功,并关闭输出文件流。
  • 第三行输出错误信息 “error2”,表示尝试打开 “tmp\test2.txt” 失败,文件不存在或打开失败。

2. 如果 “c:\tmp\test.txt” 不存在,则第一行输出错误信息 “打开文件失败: c:\tmp\test.txt”,表示该文件打开失败。其他输出与文件是否存在或打开成功无关。

3. 需要注意的是,由于该程序只是打开文件,并没有进行具体的读写操作,因此输出与标准输出没有差别。在实际应用中,需要在文件打开成功后进行具体的读写操作,并在文件操作完成后关闭文件流,否则可能会对文件造成损坏,导致程序或系统出现问题。



三、文件读写操作

(1)读写文本文件

【示例一】将用户从标准输入中输入的信息,以字符串形式写入到文本文件 “score.txt” 中

【示例代码】 

  • 假定现在要实现一个程序,从键盘输入学生的学号、姓名和成绩,将它们存入文件 score.txt中。
  • 可以使用文本文件保存数据,文件中每一行保存一名学生的成绩信息,学生
  • 成绩信息的数据项之间通过空格符分隔,格式存储如下:学号 姓名 成绩
  • 为了方便程序实现,假设学号不超过 10 个字节,姓名不超过 20 个字节,成绩为整型
  • 对文本文件 score.txt 进行输入/输出:
    #include <iostream>     // 输入输出流头文件
    #include <fstream>      // 文件流头文件
    using namespace std;
    
    int main()              // 主函数
    {
        char id[11], name[21];
        int score;
        ofstream outFile;   // 创建输出文件流对象
        outFile.open("score.txt", ios::out);    // 以写方式打开文本文件
        if (!outFile)       // 判断文件是否打开成功
        {
            cout << "创建文件失败" << endl;     // 文件打开失败
            return 0;       // 程序正常结束
        }
        cout << "请输入: 学号 姓名 成绩 (以Ctrl+Z结束输入)\n";
        while (cin >> id >> name >> score)      // 从标准输入读入数据
        {
            outFile << id << " " << name << " " << score << endl;    // 将数据写入文件流
        }
        outFile.close();    // 关闭输出文件流
        return 0;           // 程序正常结束
    }

【代码详解】

  • 本程序的主要作用是将用户从标准输入中输入的信息,一行行写入到文本文件 “score.txt” 中。程序首先创建一个 ofstream 对象 outFile,并使用 open() 函数打开文本文件,以写方式打开。如果文件打开失败,就输出创建文件失败的提示信息,并返回值为 0,程序结束。否则,程序提示用户输入信息,然后使用 while 循环从标准输入流 cin 中读取信息,将之存放到定义好的变量 idname 和 score 中,然后把数据写入文件流中。循环执行直到输入结束。
  • 最后,调用 outFile.close() 关闭输出文件流。这个步骤是非常关键的,因为在程序结束之前必须保证文件流已经被关闭。这样才能确保数据已经被正确写入文件。
  • 需要注意的是,在写文件之前,必须确保文件是成功打开的。因此程序使用了条件判断,以确保文件成功创建并且打开,才可以执行文件写入操作。
  • 另外,程序中的 ios::out 表示以写方式打开(while (cin >> id >> name >> score),同时会清空原有文件内容),还有 ios::app 表示追加写入方式打开,这其中也包含其他选项,例如 ios::in 表示以读方式打开 at end of file。
  1. 首先使用 #include 引入 iostream 和 fstream 引擎头文件。

  2. 主函数 main() 开始,定义三个变量 idname 和 score 用于存储从标准输入流中读入的学号、姓名和成绩。

  3. 声明一个 ofstream 类型的对象 outFile,用于向文件中输出数据。

  4. 使用 outFile.open() 函数打开文件 “score.txt”,打开方式是以写方式打开,即如果文件存在则清空文件内容,不存在则新建文件。打开文件时需要指定打开方式,这里使用了 ios::out 标志。

  5. 判断文件流是否打开成功,如果打开失败,则输出错误信息,程序结束并返回值 0;否则提示用户输入,使用 while 循环从标准输入流中读取信息,将之存放到变量中。

  6. 最后,将读入的信息使用 outFile << 写入到文件流 outFile 中,同时将行末添加换行符。循环执行直到输入结束。

  7. 关闭输出文件流,释放资源。

  8. 程序正常结束。

【执行结果】

由于该程序需要从标准输入读取数据,因此需要手动键入数据并以 “Ctrl+Z” 结束。输入的数据应该包括学号、姓名和成绩,每个字段以空格隔开。

  • 例如,假设键盘输入以下数据:
    001 Alice 90
    002 Bob 85
    003 Charlie 78
    
  • 在 Windows 命令行窗口中执行程序,输出如下结果:
    请输入: 学号 姓名 成绩 (以Ctrl+Z结束输入)    // 程序提示信息
    001 Alice 90                                 // 手动输入的数据
    002 Bob 85
    003 Charlie 78
    
  • 输入数据之后,程序检测到输入流结束,自动结束输入并开始将数据写入到文件 “score.txt” 中。由于设置了每个信息项后面添加一个回车符,因此输出文件中的数据也是每行一条,格式化排列。
    001 Alice 90
    002 Bob 85
    003 Charlie 78
    
  • 最后,程序运行结束,正常退出。

【示例二】从文件 “score.txt” 中读取学生信息并输出到屏幕上

【示例代码】 

  • 假定现在要实现一个程序,从键盘输入学生的学号、姓名和成绩,将它们存入文件 score.txt 中。
  • 可以使用文本文件保存数据,文件中每一行保存一名学生的成绩信息,学生
  • 成绩信息的数据项之间通过空格符分隔,格式存储如下:学号 姓名 成绩
  • 为了方便程序实现,假设学号不超过 10 个字节,姓名不超过 20 个字节,成绩为整型
  • 对文本文件 score.txt 进行输入/输出:
    #include <iostream>     // 输入输出流头文件
    #include <fstream>      // 文件流头文件
    #include <iomanip>      // 格式控制头文件
    using namespace std;
    
    int main()              // 主函数
    {
        char id[11], name[21];
        int score;
        ifstream inFile;    // 创建输入文件流对象
        inFile.open("score.txt", ios::in);   // 以读方式打开文本文件
        if (!inFile)        // 判断文件是否打开成功
        {
            cout << "打开文件失败" << endl;  // 文件打开失败
            return 0;       // 程序正常结束
        }
        cout << "学生学号 姓名\t\t\t成绩\n";    // 输出表头
        while (inFile >> id >> name >> score)   // 从文件中读取数据
        {
            // 格式化输出文件中的数据
            cout << left << setw(10) << id << " " << setw(20) << name << " " << setw(3) << right << score << endl;
        }
        inFile.close();     // 关闭输入文件流
        return 0;           // 程序正常结束
    }

【代码详解】

  • 需要注意的是,该程序使用了 iomanip 头文件中的函数,实现了对输出格式的控制,使输出更加美观整洁。其中,setw() 可以控制输出数据的宽度,left 和 right 可以控制输出字符串的对齐方式。
  1. 在开头引入了 iostreamfstream 和 iomanip 三个头文件。

  2. int main() 是主函数,定义了三个变量 idname 和 score 分别用于存储学生学号、姓名和成绩。

  3. 创建一个 ifstream 类型的对象 inFile 用于从文件中读取信息。

  4. 使用 inFile.open() 函数打开文件 “score.txt”,打开方式是以读方式打开,如果文件不存在或者出现错误,将会返回 NULL,即打开失败。

  5. 判断文件流是否打开成功,如果打开失败,则输出错误信息,程序结束并返回值 0;否则输出表头信息,表头已经进行了格式化控制。

  6. 使用 while 循环从文件流 inFile 中读取信息,将之存放到变量中。

  7. 最后,使用 cout 实现屏幕输出,采用了格式化输出方式,使用了 setw() 和 leftright 控制输出宽度和对齐方式。循环执行直到读取到文件末尾。

  8. 关闭输入文件流,释放资源。

  9. 程序正常结束。

【执行结果】

  • 假设输入的是在 Windows 命令行窗口中输入的,输入时需要一行一行地输入并以回车键结束,键盘输入内容:
2018001001 zhangsan                90
2018001002 lisi                     9
2018001003 wangwu                  85
2018001004 zhangsanfeng           100
2008001005 zhouwuzhengwangyishi   100
  • 下面是程序输出的结果:
学生学号    姓名                   成绩
2018001001 zhangsan                90
2018001002 lisi                     9
2018001003 wangwu                  85
2018001004 zhangsanfeng           100
2008001005 zhouwuzhengwangyishi   100
  • 程序首先输出了一行表头,然后从文件中依次读取每行学生信息。由于 leftright 和 setw() 调整了输出宽度和对齐方式,因此输出的每行信息都会占据固定的宽度,同时保持左对齐或者右对齐,使得输出的整体结构具有良好的可读性。
  • 需要注意的是,如果输入文件格式有误,例如信息项之间没有按规定使用空格隔开,则程序无法正确地读取信息项。此外,如果输入的信息不能保证长度符合要求,例如学号只有 8 位,或者姓名超过了 20 个字符,则输出的信息可能会失衡,需要重新调整输出宽度。

【示例三】从用户输入的文件名(默认为当前目录下的文件),按照行的方式读取文件内容,并在每一行的开头进行行号的输出

【示例代码】

#include <iostream>     // 输入输出流头文件
#include <fstream>      // 文件流头文件
#include <iomanip>      // 格式控制头文件
using namespace std;

int main()              // 主函数
{
    char ch, filename[20];
    int count = 0;      // 行号计数器
    bool newline = true; // 开始一个新行的标志

    cout << "请输入文件名: ";
    cin >> filename;
    ifstream inFile(filename, ios::in);    // 以读方式打开文本文件
    if (!inFile)        // 判断文件是否打开成功
    {
        cout << "打开文件失败" << endl;  // 文件打开失败
        return 0;       // 程序正常结束
    }
    while ((ch = inFile.get()) != EOF)    // 从文件中读取每个字符
    {
        if (newline)    // 若是新行开始,则显示行号
        {
            cout << setw(4) << ++count << ": ";    // 输出行号并格式化对齐
            newline = false;    // 清除新行标志
        }
        if (ch == '\n') // 若为换行符,则表示将开始一个新行
            newline = true;     // 设置新行标志
        cout << ch;     // 输出字符
    }
    inFile.close();     // 关闭文件
    return 0;           // 程序正常结束
}

【代码详解】

  • 需要注意的是,该程序假定输入的文件中的每一行都以回车符和换行符结束。另外,行号部分使用了 setw() 进行对齐控制,确保行号以固定的列宽显示,方便用户查看。
  • 此外,该程序并没有检测文件名中包含非法字符或者文件不存在的情况,因此如果用户输入了非法的文件名或者访问了不存在的文件,程序将会抛出异常,这对程序的稳定性会产生影响。
  • 除此之外,需要注意的是,由于程序是按行读取文件的内容,因此如果一行的长度超过了程序规定的最大长度,可能会导致程序异常结束。为了避免这种情况,可以增加对每行的字符个数的限制,或者采用动态内存分配等技术,在运行时动态分配足够的内存空间,以便正确读取整个文件。
  1. 在开头引入了 iostreamfstream 和 iomanip 三个头文件。

  2. int main() 是主函数,定义了三个变量 chcount 和 newline,分别用于存储读入的字符、统计行号和判断是否为新行。

  3. 输出提示信息,要求用户键入文件名。然后,使用 ifstream 类型的对象 inFile 打开用户输入的文件,以读方式打开。如果文件不存在则返回 NULL,即打开失败。

  4. 判断文件流是否打开成功,如果打开失败,则输出错误信息,程序结束并返回值 0;否则执行读操作,从文件中读取每个字符。

  5. 如果遇到新的一行,则使用 setw() 进行格式化,输出行号并对齐。然后清除标志以指明不在新行上。

  6. 如果遇到换行符,则设置新行标志以记录下一个字符位于新行的位置。

  7. 无论何时都使用 cout 输出字符。

  8. 将 inFile 关闭以释放资源。

  9. 程序正常结束。

【执行结果】

  • 已知使用示例二的返回结果充当数据
    2018001001 zhangsan                90
    2018001002 lisi                     9
    2018001003 wangwu                  85
    2018001004 zhangsanfeng           100
    2008001005 zhouwuzhengwangyishi   100
    
  • 键盘输入文件名 “score.txt”
    score.txt
  • 程序从该文件中读取每一行的内容,并在每行开头输出行号。以下是程序输出的结果:
    请输入文件名: score.txt
    1: 2018001001 zhangsan                90
    2: 2018001002 lisi                     9
    3: 2018001003 wangwu                  85
    4: 2018001004 zhangsanfeng           100
    5: 2008001005 zhouwuzhengwangyishi   100
    
  • 可以看到,输出结果按照行号从小到大的顺序输出,每一行的开头都带有行号。且行号和行内容之间使用了 setw() 进行格式化对齐,使得每一行的输出都具有相同的格式和宽度。
  • 需要注意的是,如果文件名输入错误或者文件不存在,程序会输出 “打开文件失败” 的提示信息,并结束程序。另外,程序需要保证输入的每一行都以回车符和换行符结束,否则可能会导致程序读取的内容不完整。在使用本程序时,必须确保输入的文件可以正确地读取每一行的内容,并且每一行的内容符合程序的预期格式。

(2)读写二进制文件 

  • 对二进制文件进行读写不能使用前面提到的类似于 cin、cout 从流中读写数据的方法。
  • C++ 用 binary 方式打开二进制文件,调用 ifstream 或 fstream 的 read() 成员函数从文件中读取数据,调用 ofstream 或 fstream 的 write() 成员函数向文件中写入数据。

① 用 ostream::write() 成员函数写文件

ofstream 和 fstream 的 write() 成员函数继承自 ostream 类,原型如下:
ostream & write(char * buffer, int nCount);
  • 该成员函数将内存中 buffer 所指向的 nCount 个字节的内容写入文件,返回值是对函数所作用的对象的引用,如 obj.write(...) 的返回值就是对 obj 的引用。
  • 该函数是非格式化操作,将 buffer 所指的数据按字节序列直接存入文件中。
  • 在使用 write() 与 read() 进行数据读写时,不必在数据之间再额外 “插入” 分隔符,这是因为它们都要求提供第 2 个参数来指定读写长度。

【示例】利用自定义的 CStudent 类,实现从控制台输入学生信息,将其写入到二进制文件中

【示例代码】 

#include <iostream>     // 输入输出流头文件
#include <fstream>      // 文件流头文件
using namespace std;

class CStudent      // 自定义学生类
{
public:
    char id[11];        // 学号
    char name[21];      // 姓名
    int score;          // 成绩
};

int main()              // 主函数
{ 
    CStudent stu;       // 学生类对象
    ofstream outFile("students.dat", ios::out | ios::binary);  // 以二进制写方式打开文本文件
    if (!outFile)       // 判断文件是否打开成功
    {
        cout << "创建文件失败" << endl;    // 创建失败
        return 0;       // 程序结束并返回值 0
    }
    cout << "请输入: 学号 姓名 成绩 (以Ctrl+Z结束输入)\n";
    while (cin >> stu.id >> stu.name >> stu.score)    // 输入学生信息
        outFile.write((char*)&stu, sizeof(stu));      // 向文件中写入学生信息
    outFile.close();    // 关闭文件
    return 0;           // 程序结束
}

【代码详解】

  • 需要注意的是,由于本程序处理的是二进制文件,因此在输入学生信息时,需要保证每个学生的信息符合程序的预期格式,并且每个学生信息的字节数相同。否则可能会导致程序读取错误、解析错误或者输出错误的信息。在使用本程序时,务必保证输入的每个学生信息都符合规范要求,并使用正确的输入格式。
  1. 在开头引入了 iostream 和 fstream 两个头文件。

  2. class CStudent 定义了自定义的学生类,包含了每个学生的学号、姓名和成绩信息,用于记录学生的信息。

  3. 主函数 main() 中定义了一个类型为 CStudent 的学生对象 stu,并使用 ofstream 类型的对象 outFile 打开一个名为 “students.dat” 的二进制文件,以写入方式打开。如果文件打开失败,程序输出错误信息,程序结束并返回值 0。

  4. 程序输出提示信息,要求用户输入学生的学号、姓名和成绩。如果用户输入学生信息则将该学生信息写入文件中,否则结束输入过程。

  5. 使用 outFile.write() 向文件中写入当前输入的学生信息,sizeof(stu) 用于计算单个学生信息所占用的字节数。

  6. 开始下一轮输入操作,直到用户结束输入。

  7. 关闭文件以释放资源。

  8. 程序正常结束。

【执行结果】

  •  假设键盘输入以下内容 ,并以 Ctrl+Z 结束输入:
    2019001001 ZhangSan 90
    2019001002 LiSi 100
    2019001003 WangWu 78
  • 根据输入的内容,程序将录入三个学生的信息,分别是学号、姓名和成绩。以下是程序的执行结果:
    请输入: 学号 姓名 成绩 (以Ctrl+Z结束输入)
    2019001001 ZhangSan 90
    2019001002 LiSi 100
    2019001003 WangWu 78
    ^Z
  • 可以看到,程序提示用户输入学生信息,每行输入一个学生的信息,每个学生的信息包括学号、姓名和成绩,以空格分隔。
  • 由于输入的信息符合程序的预期格式要求,因此程序可以正常地将这些信息写入到二进制文件 “students.dat” 中。

② 用 istream::read() 成员函数读文件

ifstream 和 fstream 的成员函数read()实际上继承自类 istream,原型如下:
istream &read(char * buffer, int nCount);
  • 该成员函数从文件中读取 nCount 个字节的内容,存放到 buffer 所指向的内存缓冲区中,返回值是对函数所作用的对象的引用。
  • 该函数是非格式化操作,对读取的字节序列不进行处理,直接存入 buffer 中,由程序的类型定义解释。  

③ 用 ostream::gcount() 成员函数得到读取字节数

  • 如果要知道每次读操作成功读取了多少个字节,可以在 read() 函数执行后立即调用文件流对象的成员函数 gcount( ),其返回值就是最近一次 read() 函数执行时成功读取的字节数。
  • gcount() 成员函数原型如下:
    int gcount( );

(3)用成员函数 put() 和 get() 读写文件

成员函数 get() 和 put() 常用于读写字符或文本文件,但它们不仅仅可用于对字符的处理,而且对于二进制文件同样可以进行有效的处理。 

函数 get() 有 3 种主要形式:

int get( );
istream& get(char &rch);

istream& get(char *pch, int nCount, char delim=’\n’);

函数 put() 的语法格式如下:

ostream& put(char ch);

① int get();

  • 不带参数的 get() 函数从指定的输入流中提取一个字符(包含空白字符),函数的返回值即为该字符。
  • 当遇到文件结束符时,返回系统常量 EOF。

② istream& get(char &rch);

  • 从指定输入流中提取一个字符(包含空白字符),将该字符作为 rch 引用的对象。
  • 当遇到文件结束符时,函数返回 0;否则返回对 istream 对象的引用。

③ istream& get(char *pch, int nCount, char delim=’\n’);

  • 从流的当前字符开始,读取 nCount-1 个字符,或遇到指定的分隔符 delim 结束。
  • 函数把读取的字符(不包括分隔符)写入数组 pch 中,并在字符串后添加结束符 '\0'。

④ ostream& put(char ch);

函数的功能是向输出流中插入一个字节。

(4)文本文件与二进制文件的异同 

在输入/输出过程中,系统要对内外存的数据格式进行相文本文件是以 文本形式存储数据
  • 其优点是具有较高的兼容性
  • 缺点是存储一批纯数值信息时,要在数据之间人为地添加分隔符
  • 应转换
  • 文本文件的另一个缺点是不便于对数据进行随机访问
二进制文件是以 二进制形式存储数据
  • 其优点是便于对数据实行随机访问(相同数据类型的数据所占空间的大小均是相同的,不必在数据之间人为地添加分隔符)
  • 在输入/输出过程中,系统不需要对数据进行任何转换
  • 缺点是数据兼容性差
通常纯文本信息(如字符串)以文本文件形式存储,而将数值信息以二进制文件形式存储。


四、随机访问文件

⚫ 如果一个文件只能进行顺序存取操作,则称为顺序文件。

  • 典型的顺序文件(设备)是键盘、显示器和保存在磁带上的文件。
  • 如果一个文件可以在文件的任意位置进行存取操作,则称为随机文件。
  • 磁盘文件就是典型的随机文件。
在访问文件的过程中,若严格按照数据保存的次序从头到尾访问文件,则称为顺序访问。
在访问文件的过程中,若不必按照数据的存储次序访问文件,而是要根据需要在文件的不同位置进行访问,则称为随机访问。
显然,对于顺序文件只能进行顺序访问;对于随机文件既可以进行顺序访问,也可以进行随机访问。

(1)类 istream 中与位置指针相关的函数

① 移动读指针函数

istream & seekg(long pos);
  • 该函数的功能是将读指针设置为 pos,即将读指针移动到文件的 pos 字节处。
istream & seekg(long offset, ios::seek_dir dir);
  • 该函数的功能是将读指针按照 seek_dir 的指示(方向)移动 offset 个字节,其中 seek_dir 是在类 ios 中定义的一个枚举类型。
enum seek_dir {beg=0, cur, end};

seek_dir 的常量值含义如下:

  • ios::beg:表示流的开始位置。此时,offset 应为非负整数。
  • ios::cur:表示流的当前位置。此时,offset 为正数则表示向后(文件尾)移动,为
  • 负数则表示向前(文件头)移动。
  • ios::end:表示流的结束位置。此时,offset 应为非正整数。

② 返回写指针当前位置的函数

long tellg( );
  • 函数返回值为流中读指针的当前位置。

(2)类 ostream 中与位置指针相关的函数 

① 移动写指针函数

ostream & seekp(long pos);
  • 该函数的功能是将写指针设置为 pos,即将写指针移动到文件的 pos 字节处。
ostream & seekp(long offset, ios::seek_dir dir);
  • 该函数的功能是将写指针按 seek_dir 指示的方向移动 offset 个字节。

② 返回写指针当前位置的函数

long tellp( );
  • 函数的返回值为流中写指针的当前位置。
  • 注意:在类 fstream 中既提供了操作读指针函数 seekg() 和 tellg(),又提供了操作写指针的函数 seekp() 和 tellp(),实际上在文件中这两个指针是同一个指针。

猜你喜欢

转载自blog.csdn.net/qq_39720249/article/details/131401027