C++中的文件操作

使用C++标准库进行文件操作

1. 概述

C++标准库提供了 <fstream> 头文件中的几个类来进行文件操作,这些类封装了底层的文件操作,提供了面向对象和类型安全的接口,使得文件读写更加便捷和高效。主要的文件流类包括:

std::ifstream:用于从文件中读取数据。

std::ofstream:用于向文件中写入数据。

std::fstream:用于同时读取和写入文件。

这些文件流类(std::ifstream、std::ofstream、std::fstream)继承自 std::istream、std::ostream 和 std::iostream,这些类本身继承自 std::ios,从而提供了丰富的成员函数和操作符来处理文件I/O。

更详细的关于继承关系的介绍:C++标准库中文件流类的继承关系

2. 文件流类详解

2.1 std::ifstream:输入文件流

std::ifstream 用于从文件中读取数据。它继承自 std::istream,具备所有输入流的功能,同时添加了文件特有的操作。

例子:

#include <fstream>
#include <iostream>
#include <string>

int main() {
    // 创建并打开输入文件流
    std::ifstream infile("input.txt"); // "input.txt" 是要读取的文件名

    // 检查文件是否成功打开
    if (!infile) {
        std::cerr << "无法打开文件 input.txt" << std::endl;
        return 1;
    }

    // 读取并输出文件内容
    std::string line;
    while (std::getline(infile, line)) {
        std::cout << line << std::endl;
    }

    // 关闭文件流(可选,因为析构函数会自动关闭)
    infile.close();

    return 0;
}

要点:

  • 构造函数可以在创建对象时指定文件名和打开模式。

  • 检查文件是否成功打开,以避免后续操作错误。

  • 使用 std::getline 逐行读取文件内容。

补充:

std::ifstream infile("input.txt");的解释:

  1. std::ifstream

    定义:std::ifstream 是 C++ 标准库中用于输入文件流的类,定义在 <fstream> 头文件中。

    用途:用于从文件中读取数据。ifstream 代表“输入文件流”(input file stream)。

    继承关系:std::ifstream 继承自 std::istream,而 std::istream 又继承自 std::ios,提供了丰富的成员函数和操作符来处理文件输入操作。

  2. infile

    定义:infile 是一个变量名,是 std::ifstream 类的一个对象实例。在这里,infile 可以被认为是一个文件读取流,用于从指定的文件中读取数据。

    作用:通过 infile,你可以执行各种读取操作,例如读取单词、行、整个文件内容,甚至是二进制数据。

  3. 构造函数 std::ifstream infile("input.txt")

    功能:这行代码做了两件事:

    • 创建对象:声明并创建了一个 std::ifstream 对象,名为 infile。

    • 打开文件:在创建对象的同时,通过构造函数参数 "input.txt" 指定要打开的文件名。默认情况下,std::ifstream 会以只读模式打开文件。

    过程:

    • 当 infile 对象被创建时,std::ifstream 的构造函数会尝试打开 "input.txt" 文件。

    • 如果文件成功打开,infile 就可以用于后续的读取操作。

    • 如果文件无法打开(例如文件不存在或权限不足),infile 的状态会被设置为失败,你可以通过检查流的状态来处理这种情况。

  4. infile 的灵活性

    • 读取不同类型的数据:

      你可以使用不同的方法和操作符来读取文件内容。例如,使用 >> 操作符从文件中读取单词,或者使用 read() 方法读取二进制数据。

    • 错误处理:

      除了检查文件是否成功打开,还可以检查读取操作是否成功,例如使用 infile.good(), infile.eof(), infile.fail() 等成员函数。

    • 多种打开模式:

      std::ifstream 还支持多种打开模式,通过构造函数的第二个参数指定。例如,以二进制模式打开文件:

      std::ifstream infile("input.bin", std::ios::binary);
      

2.2 std::ofstream:输出文件流

std::ofstream 用于向文件中写入数据。它继承自 std::ostream,具备所有输出流的功能,同时添加了文件特有的操作。

例子:

#include <fstream>
#include <iostream>
#include <string>

int main() {
    // 创建并打开输出文件流
    std::ofstream outfile("output.txt"); // "output.txt" 是要写入的文件名

    // 检查文件是否成功打开
    if (!outfile) {
        std::cerr << "无法打开文件 output.txt" << std::endl;
        return 1;
    }

    // 写入数据到文件
    outfile << "Hello, World!" << std::endl;
    outfile << "这是第二行文本。" << std::endl;

    // 关闭文件流(可选,因为析构函数会自动关闭)
    outfile.close();

    return 0;
}

要点:

  • 默认情况下,std::ofstream 以写入模式打开文件。如果文件不存在,会创建新文件;如果文件存在,会截断文件内容。

  • 使用 << 操作符向文件中插入数据。

2.3 std::fstream:文件流(读写)

std::fstream 用于同时读取和写入文件。它继承自 std::iostream,结合了 std::istream 和 std::ostream 的功能

例子:

#include <fstream>
#include <iostream>
#include <string>

int main() {
    // 创建并打开文件流,设置为读写模式
    std::fstream file("file.txt", std::ios::in | std::ios::out | std::ios::app);

    // 检查文件是否成功打开
    if (!file) {
        std::cerr << "无法打开文件 file.txt" << std::endl;
        return 1;
    }

    // 写入数据到文件
    file << "追加一行内容。" << std::endl;

    // 移动读指针到文件开头
    file.seekg(0, std::ios::beg);

    // 读取并输出文件内容
    std::string line;
    while (std::getline(file, line)) {
        std::cout << line << std::endl;
    }

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

    return 0;
}

要点:

  • 通过构造函数的第二个参数指定文件打开模式,可以组合多个模式。

  • 适用于需要同时进行读取和写入操作的场景。

3. 打开和关闭文件

文件流类提供了多种方式来打开和关闭文件。

使用构造函数打开文件

可以在创建文件流对象时,通过构造函数直接指定文件名和打开模式。

比如:

std::ifstream infile("input.txt"); // 以默认模式(即只读方式)打开 input.txt
std::ofstream outfile("output.txt", std::ios::out | std::ios::trunc); // 以写入和截断模式打开 output.txt

使用 open() 成员函数

如果在创建对象时没有指定文件名,可以使用 open() 成员函数在后续打开文件。

比如:

#include <fstream>
#include <iostream>

int main() {
    std::ifstream infile; // 创建输入文件流对象
    infile.open("input.txt"); // 打开文件

    if (!infile.is_open()) {
        std::cerr << "无法打开文件 input.txt" << std::endl;
        return 1;
    }

    // 进行读取操作...

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

    return 0;
}

关闭文件

使用 close() 成员函数可以显式关闭文件流。尽管在对象销毁时,析构函数会自动关闭文件,但在需要提前释放资源时,显式调用 close() 是有用
的。

比如:

infile.close();
outfile.close();

4. 文件打开模式

文件打开模式通过 std::ios::openmode 枚举类型指定,可以组合使用多个模式。

常用的打开模式:

  • std::ios::in:以读取模式打开文件。

  • std::ios::out:以写入模式打开文件。

  • std::ios::app:以追加模式打开文件,写入操作将添加到文件末尾。

  • std::ios::ate:打开文件后,将文件指针定位到文件末尾。

  • std::ios::trunc:如果文件已存在,则截断文件长度为0(默认与 std::ofstream 相关)。

  • std::ios::binary:以二进制模式打开文件。

比如:

// 以读写模式打开文件,不截断现有内容
std::fstream file("data.txt", std::ios::in | std::ios::out);

// 以二进制模式写入数据, 截断现有文件内容

std::ofstream binaryOut("data.bin", std::ios::out | std::ios::binary | std::ios::trunc);

说明:

  • 组合使用:通过按位或操作符 | 组合多个打开模式,如 std::ios::in | std::ios::out 表示同时具备读取和写入权限。

  • 二进制模式:对于非文本文件(如图片、音频等),应使用 std::ios::binary 模式,以防止数据在读取或写入过程中被转换。

5. 读取文件

5.1 使用 >> 操作符读取单词

>> 操作符用于从文件中提取数据,自动跳过空白字符(如空格、换行符、制表符)。

比如:

假设 words.txt 文件内容如下:

img

运行以下程序:

#include <fstream>
#include <iostream>
#include <string>

int main() {
    std::ifstream infile("words.txt");

    if (!infile) {
        std::cerr << "无法打开文件 words.txt" << std::endl;
        return 1;
    }

    std::string word;
    while (infile >> word) { // 逐词读取
        std::cout << "读取到的单词: " << word << std::endl;
    }

    infile.close();
    return 0;
}

输出如下:

img

说明:

  • 适用于逐词读取数据,适合处理由空白字符分隔的数据。

  • 无法读取包含空格的完整句子或短语。

5.2 使用 std::getline 逐行读取

std::getline 函数用于从文件中逐行读取数据,适用于处理文本文件中的行数据。

比如:

假设 lines.txt 文件内容如下:

img

执行以下代码:

#include <fstream>
#include <iostream>
#include <string>

int main() {
    std::ifstream infile("lines.txt");

    if (!infile) {
        std::cerr << "无法打开文件 lines.txt" << std::endl;
        return 1;
    }

    std::string line;
    while (std::getline(infile, line)) { // 逐行读取
        std::cout << "读取到的行: " << line << std::endl;
    }

    infile.close();
    return 0;
}

输出如下:

img

说明:

  • 适用于逐行处理文件内容,保留每行的完整信息。

  • 可以处理包含空格和其他特殊字符的行。

5.3 读取整个文件内容

有时需要一次性读取整个文件内容,尤其适用于小文件或需要对整个文件内容进行处理的场景。

比如:

假设 content.txt 文件内容如下:

img

执行以下代码:

#include <fstream>
#include <iostream>
#include <string>

int main() {
    std::ifstream infile("content.txt");

    if (!infile) {
        std::cerr << "无法打开文件 content.txt" << std::endl;
        return 1;
    }

    // 使用迭代器读取整个文件内容到字符串
    std::string content((std::istreambuf_iterator<char>(infile)),
                        std::istreambuf_iterator<char>());

    std::cout << "文件内容:\n" << content << std::endl;

    infile.close();
    return 0;
}

输出如下:

img

说明:

  • 使用 std::istreambuf_iterator 迭代器可以高效地读取整个文件内容。

  • 适用于小至中等大小的文件,对于非常大的文件,可能需要分块读取以避免内存问题。

  • 对此行代码的理解:

    std::string content((std::istreambuf_iterator<char>(infile)),
                        std::istreambuf_iterator<char>());
    
    • 这行代码使用了 std::istreambuf_iterator 来从输入文件流 infile 读取内容并构建一个 std::string 对象。具体理解如下:

    • std::istreambuf_iterator(infile):创建一个输入迭代器,从 infile 的缓冲区读取字符。

    • std::istreambuf_iterator():这是一个空的迭代器,用于表示输入结束。
      构造 std::string:通过传入两个迭代器,构造函数会从 infile 中读取所有字符,直到遇到结束迭代器。

    • 最终,这行代码的作用是将整个文件的内容读入到 content 字符串中。这样,你就可以方便地对文件内容进行操作。

5.4 读取二进制数据

对于非文本文件(如图片、音频、视频等),需要以二进制模式读取数据,以防止数据在读取过程中被转换或丢失。

比如:

假设需要读取一个图片文件 image.jpg 并输出其大小。

img

执行以下代码:

#include <fstream>
#include <iostream>
#include <vector>

int main() {
    // 以二进制模式打开文件
    std::ifstream infile("image.jpg", std::ios::binary);

    if (!infile) {
        std::cerr << "无法打开文件 image.jpg" << std::endl;
        return 1;
    }

    // 移动读指针到文件末尾以获取文件大小
    infile.seekg(0, std::ios::end);
    std::streamsize size = infile.tellg();
    infile.seekg(0, std::ios::beg);

    // 读取文件内容到缓冲区
    std::vector<char> buffer(size);
    if (infile.read(buffer.data(), size)) {
        std::cout << "成功读取 " << size << " 字节。" << std::endl;
    } else {
        std::cerr << "读取文件失败!" << std::endl;
    }

    infile.close();
    return 0;
}

输出如下:

img

说明:

  • 使用 std::ios::binary 模式打开文件,确保数据按原样读取。

  • 通过 seekg 和 tellg 获取文件大小,预分配缓冲区。

  • 使用 read 函数读取二进制数据到缓冲区。

  • 对这段代码的理解:

    infile.seekg(0, std::ios::end);
    std::streamsize size = infile.tellg();
    infile.seekg(0, std::ios::beg);
    

    这段代码用于确定并重置文件流 infile 的位置,具体含义如下:

    • infile.seekg(0, std::ios::end);

      这行代码将文件指针移动到文件的末尾。seekg 函数用于设置输入流的位置,0 是偏移量,std::ios::end 指定从文件的末尾开始偏移。因此,文件指针现在位于文件末尾。

    • std::streamsize size = infile.tellg();

      tellg 函数返回当前文件指针的位置(即文件的长度)。因为我们刚刚将指针移动到了文件末尾,size 将包含文件的字节数。

    • infile.seekg(0, std::ios::beg);

      这行代码将文件指针重置回文件的开头。std::ios::beg 指定从文件的开头开始偏移,因此现在指针又回到了文件的起始位置。

    总结:这段代码的主要目的是获取文件的大小,并在读取文件内容之前重置文件指针,以便可以从头开始读取数据。

6. 写入文件

6.1 使用 << 操作符写入数据

<< 操作符用于向文件中插入数据,类似于向标准输出流 std::cout 中插入数据。

比如:

将用户信息写入文件 users.txt。

执行以下代码:

#include <fstream>
#include <iostream>
#include <string>

int main() {
    std::ofstream outfile("users.txt");

    if (!outfile) {
        std::cerr << "无法打开文件 users.txt" << std::endl;
        return 1;
    }

    // 写入用户信息
    outfile << "用户名: Alice\n年龄: 30\n" << std::endl;
    outfile << "用户名: Bob\n年龄: 25\n" << std::endl;
    outfile << "用户名: Charlie\n年龄: 35\n" << std::endl;

    outfile.close();

    std::cout << "用户信息已写入 users.txt" << std::endl;
    return 0;
}

输出如下:

img

未执行前,目录如下:

img

执行后,目录如下:

img

自动创建了个 users.txt 文件,内容如下:

img

说明:

  • 使用 << 操作符可以方便地将各种数据类型写入文件。

  • std::endl 用于插入换行符并刷新缓冲区。

6.2 使用 std::ofstream::write 写入二进制数据

对于需要写入二进制数据的场景,使用 write() 成员函数更为合适。

比如:

将一个字符数组写入二进制文件 output.bin。

执行以下代码:

#include <fstream>
#include <iostream>
#include <vector>

int main() {
    std::ofstream outfile("output.bin", std::ios::binary | std::ios::trunc);

    if (!outfile) {
        std::cerr << "无法打开文件 output.bin" << std::endl;
        return 1;
    }

    // 准备二进制数据
    std::vector<char> data = {'H', 'e', 'l', 'l', 'o', '\0'};

    // 写入二进制数据到文件
    outfile.write(data.data(), data.size());

    if (!outfile) {
        std::cerr << "写入文件失败!" << std::endl;
    } else {
        std::cout << "成功写入 " << data.size() << " 字节到 output.bin" << std::endl;
    }

    outfile.close();
    return 0;
}

输出如下:

img

同理,在目录处也自动创建了一个 output.bin 文件,内容如下:

img

说明:

  • 使用 write() 可以指定要写入的数据地址和字节数,适用于二进制数据。

  • 确保文件以二进制模式打开,防止数据被意外转换。

  • outfile.write(data.data(), data.size());这行代码的理解:

    这行代码用于将数据写入输出文件流 outfile,具体含义如下:

    • data.data():获取 data 字符串的指针,指向字符串的首字符。这是写入的起始地址。

    • data.size():返回字符串 data 的长度,表示要写入的字节数。

    • outfile.write(data.data(), data.size());:调用 write 函数,将从 data 指针开始的 data.size() 字节写入 outfile 中。

    总体而言,这行代码的作用是将 data 字符串的内容写入到 outfile 文件中。

7. 错误处理

文件操作过程中可能会遇到各种错误,如文件不存在、权限不足、磁盘满等。因此,进行适当的错误处理至关重要。

7.1 检查文件是否成功打开

每次打开文件后,都应该检查文件流对象的状态,以确保文件成功打开。

比如:

#include <fstream>
#include <iostream>

int main() {
    std::ifstream infile("nonexistent.txt");

    if (!infile.is_open()) { // 或者使用 !infile
        std::cerr << "无法打开文件 nonexistent.txt" << std::endl;
        return 1;
    }

    // 进行读取操作...

    infile.close();
    return 0;
}

说明:

  • 使用 is_open() 成员函数检查文件是否成功打开。

  • 也可以直接检查流对象的状态,如 if (!infile)

7.2 使用异常处理

C++文件流可以配置为在发生错误时抛出异常,提供更灵活的错误处理机制。

比如:

使用异常处理机制读取文件内容。

#include <fstream>
#include <iostream>
#include <string>

int main() {
    std::ifstream infile;

    // 设置文件流在 failbit 或 badbit 时抛出异常
    infile.exceptions(std::ifstream::failbit | std::ifstream::badbit);

    try {
        infile.open("data.txt");

        std::string line;
        while (std::getline(infile, line)) {
            std::cout << line << std::endl;
        }

        infile.close();
    } catch (const std::ifstream::failure& e) {
        std::cerr << "文件操作异常: " << e.what() << std::endl;
        return 1;
    }

    return 0;
}

输出如下(因为没有创建data.txt文件,所以抛出异常):

img

说明:

  • 使用 exceptions() 成员函数设置文件流在特定错误状态下抛出异常。

  • 捕获 std::ifstream::failure 异常,进行错误处理。

  • 默认情况下,文件流不会抛出异常,需显式启用。

  • 对这段代码的解释:

    • std::ifstream infile;:声明一个输入文件流对象 infile,用于读取文件。

    • infile.exceptions(std::ifstream::failbit | std::ifstream::badbit);

      设置文件流在发生失败(failbit)或严重错误(badbit)时抛出异常,这样可以通过 try-catch 机制来捕获错误。

    • try {:开始一个异常处理块,尝试执行可能抛出异常的代码。

    • infile.open("data.txt");:打开名为 data.txt 的文件进行读取。如果文件无法打开,将抛出异常。

    • std::string line;:声明一个字符串变量 line,用于存储从文件中读取的每一行。

    • while (std::getline(infile, line)) {

      使用 getline 函数从 infile 中逐行读取数据,直到文件结束。每读取一行,将其存储在 line 中。

    • std::cout << line << std::endl;:将读取的每一行输出到控制台。

    • infile.close();:关闭文件流,释放资源。

    • } catch (const std::ifstream::failure& e) {

      捕获文件流的异常,处理文件操作错误。

    • std::cerr << "文件操作异常: " << e.what() << std::endl;

      输出错误信息到标准错误流,e.what() 提供具体的错误描述。

    • return 1;:如果发生异常,返回 1 表示程序异常结束。

    • return 0;:正常结束程序,返回 0。

8. 高级主题

8.1 文件指针位置控制

文件流对象维护了读写指针的位置,可以通过成员函数 seekg()(用于输入流)和 seekp()(用于输出流)来移动指针。

比如:

读取文件中指定位置的数据。

假设 data.txt 的文本文件,内容如下:

img

执行以下代码:

#include <fstream>
#include <iostream>
#include <string>

int main() {
    std::ifstream infile("data.txt", std::ios::binary);

    if (!infile) {
        std::cerr << "无法打开文件 data.txt" << std::endl;
        return 1;
    }

    // 获取文件大小
    infile.seekg(0, std::ios::end);
    std::streampos size = infile.tellg();
    std::cout << "文件大小: " << size << " 字节" << std::endl;

    // 移动指针到文件中间位置
    std::streampos mid = size / 2;
    infile.seekg(mid, std::ios::beg);
    std::cout << "移动到文件中间位置: " << mid << std::endl;

    // 读取接下来的10个字节
    char buffer[11] = {0}; // 10个字符 + 1个终止符
    infile.read(buffer, 10);
    std::cout << "读取的数据: " << buffer << std::endl;

    infile.close();
    return 0;
}

输出如下:

img

说明:

  • 使用 seekg() 移动读指针位置。

  • 使用 tellg() 获取当前读指针的位置。

  • 适用于需要随机访问文件内容的场景。

8.2 缓冲管理

文件流对象使用缓冲区来提高I/O效率。可以使用 flush() 成员函数来强制将缓冲区的数据写入文件。

比如:

在写入数据后立即刷新缓冲区。

#include <fstream>
#include <iostream>
#include <string>

int main() {
    std::ofstream outfile("buffered_output.txt");

    if (!outfile) {
        std::cerr << "无法打开文件 buffered_output.txt" << std::endl;
        return 1;
    }

    outfile << "这是一行数据。";
    outfile.flush(); // 强制刷新缓冲区

    outfile << "这是另一行数据。\n";

    outfile.close();
    return 0;
}

自动创建了buffered_output.txt文件,内容如下:

img

9. 举几个例子

为了更好地理解如何综合运用上述知识,以下提供几个完整的示例,涵盖读取、写入、错误处理和二进制数据操作。

9.1 读取文本文件并处理数据

任务: 从 students.txt 文件中读取学生信息,并输出到控制台。

假设 students.txt 内容如下:

img

执行以下代码:

#include <fstream>
#include <iostream>
#include <string>

int main() {
    std::ifstream infile("students.txt");

    if (!infile) {
        std::cerr << "无法打开文件 students.txt" << std::endl;
        return 1;
    }

    std::string name;
    int age;
    while (infile >> name >> age) {
        std::cout << "学生姓名: " << name << ", 年龄: " << age << std::endl;
    }

    if (infile.bad()) {
        std::cerr << "读取文件时发生严重错误!" << std::endl;
    } else if (infile.eof()) {
        std::cout << "已到达文件末尾。" << std::endl;
    } else if (infile.fail()) {
        std::cerr << "读取文件时发生格式错误!" << std::endl;
    }

    infile.close();
    return 0;
}

输出如下:

img

说明:

  • 使用 >> 操作符逐个读取学生姓名和年龄。

  • 通过检查 bad()eof()fail() 来处理不同类型的错误。

9.2 写入文本文件

任务: 将多个产品信息写入 products.txt 文件。

执行以下代码:

#include <fstream>
#include <iostream>
#include <string>

int main() {
    std::ofstream outfile("products.txt");

    if (!outfile) {
        std::cerr << "无法打开文件 products.txt" << std::endl;
        return 1;
    }

    // 写入产品信息
    outfile << "产品名称: 手机\n价格: 5000元\n\n";
    outfile << "产品名称: 笔记本电脑\n价格: 8000元\n\n";
    outfile << "产品名称: 电子书阅读器\n价格: 1200元\n\n";

    outfile.close();

    std::cout << "产品信息已写入 products.txt" << std::endl;
    return 0;
}

输出如下:

img

自动创建的products.txt文件,内容如下:

img

说明:

  • 使用 << 操作符将字符串和数值写入文件。

  • 通过插入 \n 实现换行和段落分隔。

9.3 复制二进制文件

任务: 复制一个二进制文件 source.jpg 到 destination.jpg。

假设 source.jpg 内容如下:

img

执行以下代码:

#include <fstream>
#include <iostream>

int main() {
    std::ifstream src("source.jpg", std::ios::binary);
    if (!src) {
        std::cerr << "无法打开源文件 source.jpg" << std::endl;
        return 1;
    }

    std::ofstream dest("destination.jpg", std::ios::binary | std::ios::trunc);
    if (!dest) {
        std::cerr << "无法创建目标文件 destination.jpg" << std::endl;
        return 1;
    }

    // 复制文件内容
    dest << src.rdbuf();

    if (!dest) {
        std::cerr << "复制文件失败!" << std::endl;
        return 1;
    } else {
        std::cout << "文件复制成功。" << std::endl;
    }

    src.close();
    dest.close();
    return 0;
}

输出如下:

img

自动创建了destination.jpg文件,内容如下:

img

说明:

  • 使用 rdbuf() 将源文件流的缓冲区内容直接传输到目标文件流。

  • 适用于高效复制文件内容,尤其是大文件。

posted @ 2024-10-18 00:45  hisun9  阅读(6)  评论(0编辑  收藏  举报