学而时习之:C++ 中的文件处理1

47 阅读8分钟

C++ 文件处理

文件处理是指利用 C++ 标准库 提供的类,从文件(如 .txt、.csv 等) 从文件中读取数据向文件中写入数据

程序在内存(RAM)中运行,因此数据仅在程序运行期间存在;程序结束后,RAM 中的所有数据会自动丢失。
文件处理允许将数据存储到二级存储(如 HDD 或 SSD)中,从而永久保存,即使程序终止后仍可再次访问。

C++ 在头文件 `` 中提供了文件流类:ofstreamifstreamfstream,用于文件操作。

A. 打开文件

在对文件进行读写之前,必须先打开它。打开文件会把文件加载到内存中。在 C++ 中,通过创建文件流对象来完成这一步:

fstream myfile(filename, mode);
或
fstream  myfile = fstream(filename, mode);
或
fstream myfile;
myfile.open(filename,  mode);

其中:

  • str:给该流对象起的名字
  • filename:文件名
  • mode:文件打开模式,指明我们将如何与该文件交互(读、写、追加等)

B. 文件打开模式

文件打开模式指明文件是以 “读”“写” 还是 “追加” 的方式打开。下面列出 C++ 中所有可用的文件模式:

模式说明
ios::in以读取方式打开文件。若文件不存在,打开失败。
ios::out以写入方式打开文件:流缓冲区支持输出操作。
ios::binary以二进制模式进行读写,而非文本模式。
ios::ate打开后文件指针直接定位到文件末尾(at end)。
ios::app所有写操作都在文件末尾进行,即追加写入。
ios::trunc打开时清空文件原有内容(截断)。

示例

  1. 只读打开:
fstream filein(file.txt, ios::in);
  1. 只写打开:
fstream fileout(file.txt, ios::out);
  1. 模式可借助按位或 | 组合使用。例如同时支持读写:
fstream str(file.txt, ios::in | ios::out);

补充说明

  • 写模式 (ios::out) 打开时,若文件不存在,系统会自动创建新文件。
  • 读模式 (ios::in) 打开时,若文件不存在,不会创建新文件,且会抛出异常(或设置错误标志,需通过 fail() 等函数检测)。

C. 其它文件流

fstream 并不是 C++ 提供的唯一文件流类,还有两个更专用的流类型:

流类含义默认等效模式
ifstreaminput file stream(输入文件流)相当于以 ios::in 模式打开的 fstream
ofstreamoutput file stream(输出文件流)相当于以 ios::out 模式打开的 fstream

这两个类的默认模式无法被移除,但可以通过按位或 | 追加其它模式进行组合。

使用示例

  1. 只读打开文件:
ifstream filein(file.txt);   // 默认已带 ios::in
  1. 只写打开文件:
ofstream fileout(file.txt);  // 默认已带 ios::out

D. 文本形式读写文件

D1. 向文件写入数据

当文件通过 fstreamofstream 以写模式打开后,就可以像用 cout 一样,借助 << 运算符把数据写入文件(文本写入形式)。

示例代码:

#include <bits/stdc++.h>
using namespace std;

int main() {
    // 打开文件(若不存在则自动创建)
    ofstream file(GFG.txt);
    
    // 将字符串写入文件
    file << “Welcome to GeeksforGeeks”;

    return 0;
}

image.png

D2. 从文件读取数据

当文件通过 fstreamifstream 以读模式打开后,就可以像用 cin 一样,借助 >> 运算符从文件中读取数据(文本读取形式)。

示例代码:

#include <bits/stdc++.h>
using namespace std;

int main() {
    // 以读模式打开文件
    ifstream file(GFG.txt);
    string s;

    // 从文件读取字符串(遇到空格结束)
    file >> s;

    cout << “Read String:” << s;

    return 0;
}
Read String: Welcome

这跟 cin 存在同样的问题:用 >> 读取时,遇到第一个空白字符就停止。若想读取整行(包含空格),可使用 getline() 函数,如下所示:

#include 
using namespace std;

int main() {
    // 以读模式打开文件
    ifstream file(&#34;GFG.txt&#34;);
    string s;

    // 从文件读取一整行
    getline(file, s);

    cout << Read String:  << s;

    return 0;
}
Read String: Welcome to GeeksforGeeks.

E. read, write 读写文件

E.1 write方式 写入数据

代码示例1:

#include<fstream>
#include<string>
#include<iostream> 
#define D_PATH_WRITE_FILE "D:\\Log2\\log.txt"
using namespace std;

int main()
{
    fstream  myfile = fstream(D_PATH_WRITE_FILE, ios::out);
    string str = "welcom to geeksfeogeddksd";
    if (myfile.is_open())
    {   
        size_t len = str.length();
        myfile.write(str.c_str(), len);
        myfile.close();
    }else{
        return -1;
    }

    return 0;
}

要把数据写入二进制文件,首先需要以 ios::binary 模式打开或创建文件。

代码示例2:

#include <cstring>
#include <fstream>
#include <iostream>
using namespace std;

int main()
{
    string str = "Welcome to GeeksForGeeks"; // 准备要写入文件的字符串
    ofstream file("fileBin.bin", ios::binary); // 以二进制方式打开文件,用于写入(不存在则创建,存在则覆盖)

    if (!file)
    {
        cerr << "打开文件失败,无法写入。";
        return 1;
    }

    // 先把字符串的长度(size_t 类型)写进文件,方便之后读取时知道要读多少字节
    size_t strLength = str.length();
    file.write(reinterpret_cast<const char *>(&strLength), sizeof(strLength));
    file.write(str.c_str(), strLength); // 再把字符串本身的字节数据写进文件

    file.close();  // 关闭文件

    return 0;
}

一句话总结:这段代码把字符串“Welcome to GeeksForGeeks”以二进制格式序列化到 fileBin.bin 中,顺序是:先写长度,再写内容。

E.2 read方式 读取数据

代码示例1:

#include<fstream>
#include<string>
#include<iostream>
#define D_PATH_WRITE_FILE "D:\\Log2\\log.txt"
#define D_PATH_READ_FILE "D:\\Log2\\222.txt"
using namespace std;

int main(){

    fstream nyfile = fstream(D_PATH_READ_FILE, ios::in);

    if (nyfile.is_open())
    {
        nyfile.seekg(0, ios::end);  // 移动到文件末尾
        int fileSize = nyfile.tellg();  // 获取文件大小
        nyfile.seekg(0, ios::beg);  // 移回文件开头
        char* buffer = new char[fileSize + 1];  // 分配缓冲区
        buffer[fileSize] = '\0';  // 添加字符串结束符
        nyfile.read(buffer, fileSize);  // 读取整个文件
        cout << "文件内容:" << endl;
        cout << buffer << endl;
        delete[] buffer;  // 释放内存
    }else
    {
        return -1;
    }
    nyfile.close();
    return 0;
}

代码示例2:

#include<fstream>
#include<string>
#include<iostream>
#define D_PATH_WRITE_FILE "D:\\Log2\\log.txt"
#define D_PATH_READ_FILE "D:\\Log2\\222.txt"
using namespace std;

int main(){

    fstream nyfile = fstream(D_PATH_READ_FILE, ios::in);

    if (nyfile.is_open()){
        char buffer[1024];  // 一点点读取
        while(!nyfile.eof()) {
            nyfile.read(buffer, sizeof(buffer));        // 一次最多读 1024 字节
            streamsize bytesRead = nyfile.gcount();     // 实际读了多少
            if (bytesRead == 0){
                break;   // 读完了
            } 
            cout.write(buffer, bytesRead);   // 把这段数据写到控制台
        }
    }else{
        return -1;
    }
    nyfile.close();
    return 0;
}

与写入时一样,读取二进制文件也必须使用 ios::binary 标志,并以输入模式 ios::in 打开。

完整示例3:

#include <cstring>
#include <fstream>
#include <iostream>
using namespace std;

int main()
{
    string str;
    fstream file("fileBin.bin", ios::in | ios::binary);  // 以二进制方式打开文件,用于读取
    if (!file){
        cerr << "打开文件失败。";
        return 1;
    }

    // 先从文件里读出字符串的长度(size_t 类型)
    size_t strLength;
    file.read(reinterpret_cast<char *>(&strLength), sizeof(strLength));
    
    char *buffer = new char[strLength + 1];   // 根据长度分配内存,并多读一个位置留给 '\0'
    file.read(buffer, strLength); // 把真正的字符串数据读进 buffer

    buffer[strLength] = '\0';    // 手动补字符串结束符
    str = buffer;   // 把 C 风格字符串转成 C++ string
    cout << "文件内容: " << str; // 打印读到的内容
    delete[] buffer;  // 释放动态内存,关闭文件
    file.close();

    return 0;
}
   文件内容: Welcome to GeeksForGeeks

F. 关闭文件

关闭文件意味着关闭关联的流,并释放正在使用的资源。在完成文件操作后,尤其是在长时间运行的程序中,关闭文件非常重要,以避免内存泄漏、数据丢失等问题。

在 C++ 中,文件通过所有文件流中都提供的 close() 成员函数来关闭。

int main()
{
    // 以读取模式打开文件
    ifstream file(GFG.txt);
    string s;

    // 从文件中读取字符串
    getline(file, s);

    cout << "读取的字符串:" << s;

    // 关闭文件
    file.close();

    return 0;
}
读取的字符串: Welcome to GeeksforGeeks.

G. 文件处理中的错误

在文件处理过程中,可能会发生多种不同类型的错误,例如文件未找到、磁盘已满等。我们的程序应预见到这些常见错误,并能妥善地处理它们。以下是一些在文件处理过程中可能发生的常见错误:

G1.文件打开失败

在某些情况下,由于各种原因,文件可能无法打开,例如文件不存在,或者程序没有权限打开它等。在这种情况下,我们可以使用文件流类的 is_open() 成员函数来检查文件是否成功打开。

int main() {
    fstream file(不存在的文件.txt, ios::in);

    // 检查文件是否已打开
    if (!file.is_open()) {
        cerr << “错误:无法打开文件!” << endl;
        return 1;
    }

    file.close();
    return 0;
}
错误:无法打开文件!

G2.读/写数据失败

另一个常见的错误是由于诸如模式不正确等原因导致无法读取或写入数据。在这种情况下,我们可以在每次读/写操作后进行验证。例如,可以按如下方式验证使用 getline() 的读取操作是否成功:

#include 
using namespace std;

int main() {
    fstream file(&#34;GFG.txt&#34;, ios::out);

    if (!file.is_open()) {
        cerr << &#34;错误:无法打开文件!&#34; << endl;
        return 1;
    }
    string line;
    
    // 检查 getline() 是否成功读取数据
    if (!getline(file, line))
        cerr << &#34;错误:读取数据失败&#34; << endl;

    file.close();
    return 0;
}
    错误:读取数据失败

G3.文件结束(EOF)错误

如果试图读取超出文件末尾的内容,就会引发 EOF 错误。这通常发生在读取前未检查文件是否已结束的情况下。我们可以使用 eof() 成员函数来判断是否到达了文件末尾。

#include 
using namespace std;

int main()
{
    ifstream file(GFG.txt);

    if (!file.is_open())
    {
        cerr << “错误:无法打开文件” << endl;
        return 1;
    }
    string line;
    while (getline(file, line)){
        cout << line << endl;
    }

    // 检查是否到达文件末尾
    if (file.eof())
        cout << “已到达文件末尾” << endl;
    else
        cerr << “错误:文件读取失败!” << endl;

    file.close();
    return 0;
}
已到达文件末尾。

请注意,在检查 EOF 之前,我们先验证了读取操作是否成功,因为即使读取失败,getline() 也会返回空值。

H. 其他文件操作

我们还可以在 C++ 程序中对文件进行更多操作。常见的文件操作包括:

  • 删除文件
  • 在已有文件末尾追加字符串
  • 将一个文件的内容复制到另一个文件