在 C++ 中执行文件操作

C++Beginner
立即练习

介绍

在这个 Lab 中,你将学习如何在 C++ 中执行各种文件操作。本 Lab 涵盖了打开文件进行读取和写入、向文件写入文本数据、从文本文件读取数据、检查文件打开状态、处理文件读/写错误、使用二进制文件、定位文件指针以及关闭文件以释放资源。你将获得使用 C++ 标准库中的基本文件流类 ofstreamifstream 与文件交互的实践经验。完成本 Lab 后,你将对 C++ 编程中的文件处理有一个扎实的理解。

这是一个实验(Guided Lab),提供逐步指导来帮助你学习和实践。请仔细按照说明完成每个步骤,获得实际操作经验。根据历史数据,这是一个 初级 级别的实验,完成率为 88%。获得了学习者 100% 的好评率。

使用 ofstream 和 ifstream 打开文件

在这一步,你将学习如何使用两个基本的文件流类在 C++ 中打开文件:ofstream 用于写入文件,ifstream 用于读取文件。这些类是 C++ 标准库的一部分,提供了基本的文件处理能力。

首先,在 WebIDE 终端中导航到项目目录:

cd ~/project

创建一个名为 file_operations.cpp 的新 C++ 文件:

touch file_operations.cpp

将以下代码添加到 file_operations.cpp 文件中:

#include <iostream>
#include <fstream>  // 包含文件流头文件

int main() {
    // 使用 ofstream 打开文件进行写入
    std::ofstream outputFile("example.txt");

    // 检查文件是否成功打开
    if (outputFile.is_open()) {
        std::cout << "File opened for writing successfully!" << std::endl;
        outputFile.close();  // 关闭文件
    } else {
        std::cout << "Unable to open file for writing." << std::endl;
    }

    // 使用 ifstream 打开文件进行读取
    std::ifstream inputFile("example.txt");

    // 检查文件是否成功打开
    if (inputFile.is_open()) {
        std::cout << "File opened for reading successfully!" << std::endl;
        inputFile.close();  // 关闭文件
    } else {
        std::cout << "Unable to open file for reading." << std::endl;
    }

    return 0;
}

让我们分解一下关键概念:

  1. #include <fstream>: 包含文件流库
  2. std::ofstream: 用于写入文件的输出文件流
  3. std::ifstream: 用于读取文件的输入文件流
  4. is_open(): 检查文件是否成功打开
  5. close(): 操作完成后关闭文件

编译程序:

g++ file_operations.cpp -o file_operations

运行可执行文件:

./file_operations

示例输出:

File opened for writing successfully!
File opened for reading successfully!

关于文件流的一些重要注意事项:

  • 在执行操作之前,务必检查文件是否成功打开
  • 记得在使用完文件后关闭它们
  • ofstream 用于向文件写入
  • ifstream 用于从文件读取
  • 这两个类都属于 <fstream> 头文件

你可以将文件流想象成打开和关闭通往文件的门。ofstream 让你向房间内写入内容,而 ifstream 允许你读取房间内有什么。

向文件写入文本数据

在这一步,你将学习如何使用 C++ 中的 ofstream 类向文件写入文本数据。在前一步的基础上,你将探索向文件写入字符串、数字和多行的不同方法。

创建一个名为 write_files.cpp 的新 C++ 文件:

touch ~/project/write_files.cpp

将以下代码添加到 write_files.cpp 文件中:

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

int main() {
    // 打开文件进行写入
    std::ofstream outputFile("student_data.txt");

    // 检查文件是否成功打开
    if (outputFile.is_open()) {
        // 写入单个字符串
        outputFile << "John Doe" << std::endl;

        // 写入多个数据项
        std::string name = "Alice Smith";
        int age = 22;
        double gpa = 3.75;
        outputFile << name << ", " << age << " years old, GPA: " << gpa << std::endl;

        // 写入多行
        outputFile << "Computer Science Student" << std::endl;
        outputFile << "University of Programming" << std::endl;

        // 关闭文件
        outputFile.close();
        std::cout << "Data written to file successfully!" << std::endl;
    } else {
        std::cout << "Unable to open file for writing." << std::endl;
    }

    return 0;
}

编译程序:

g++ write_files.cpp -o write_files

运行可执行文件:

./write_files

示例输出:

Data written to file successfully!

验证文件内容:

cat student_data.txt

示例文件内容:

John Doe
Alice Smith, 22 years old, GPA: 3.75
Computer Science Student
University of Programming

关于向文件写入的关键点:

  • 使用 << 运算符向文件写入数据
  • std::endl 添加一个新行
  • 可以写入字符串、数字和变量
  • 在写入之前,务必检查文件是否已打开
  • 写入操作完成后关闭文件

你可以将向文件写入想象成在一本笔记本上书写。ofstream 是你的笔,而文件是你要记录信息的页面。

从文本文件读取数据

在这一步,你将学习如何使用 C++ 中的 ifstream 类从文本文件读取数据。在前几步的基础上,你将探索读取行、单词和整个文件的方法。

创建一个名为 read_files.cpp 的新 C++ 文件:

touch ~/project/read_files.cpp

将以下代码添加到 read_files.cpp 文件中:

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

int main() {
    // 打开在上一步中创建的文件
    std::ifstream inputFile("student_data.txt");

    // 检查文件是否成功打开
    if (inputFile.is_open()) {
        // 读取整行
        std::string line;
        std::cout << "Reading entire lines:" << std::endl;
        while (std::getline(inputFile, line)) {
            std::cout << line << std::endl;
        }

        // 重置文件指针到开头
        inputFile.clear();
        inputFile.seekg(0, std::ios::beg);

        // 读取单个单词
        std::cout << "\nReading individual words:" << std::endl;
        std::string word;
        while (inputFile >> word) {
            std::cout << word << " ";
        }
        std::cout << std::endl;

        // 关闭文件
        inputFile.close();
    } else {
        std::cout << "Unable to open file for reading." << std::endl;
    }

    return 0;
}

编译程序:

g++ read_files.cpp -o read_files

运行可执行文件:

./read_files

示例输出:

Reading entire lines:
John Doe
Alice Smith, 22 years old, GPA: 3.75
Computer Science Student
University of Programming

Reading individual words:
John Doe Alice Smith, 22 years old, GPA: 3.75 Computer Science Student University of Programming

关于文件读取的关键点:

  • 使用 std::getline() 读取整行
  • 使用 >> 运算符读取单个单词
  • clear()seekg() 用于重置文件指针
  • 在读取之前,务必检查文件是否已打开
  • 读取操作完成后关闭文件

你可以将文件读取想象成从书中提取信息。ifstream 是你的书签,帮助你浏览文本内容。

检查文件打开状态

在这一步,你将学习如何使用各种方法在 C++ 中检查文件操作的状态,以验证文件是否已成功打开并准备好进行读取或写入。

创建一个名为 file_status.cpp 的新 C++ 文件:

touch ~/project/file_status.cpp

将以下代码添加到 file_status.cpp 文件中:

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

int main() {
    // 尝试打开一个已存在的文件进行读取
    std::ifstream inputFile("student_data.txt");

    // 方法 1: 使用 is_open() 检查文件状态
    if (inputFile.is_open()) {
        std::cout << "File opened successfully for reading." << std::endl;

        // 执行读取操作
        std::string line;
        while (std::getline(inputFile, line)) {
            std::cout << "Read line: " << line << std::endl;
        }

        inputFile.close();
    } else {
        std::cout << "Failed to open file for reading." << std::endl;
    }

    // 方法 2: 使用 good() 检查文件状态
    std::ofstream outputFile("test_status.txt");

    if (outputFile.good()) {
        std::cout << "File opened successfully for writing." << std::endl;
        outputFile << "Testing file status" << std::endl;

        // 额外的状态检查
        if (outputFile) {
            std::cout << "File is in a good state for writing." << std::endl;
        }

        outputFile.close();
    } else {
        std::cout << "Failed to open file for writing." << std::endl;
    }

    // 方法 3: 多种状态检查方法
    std::ifstream checkFile("non_existent_file.txt");

    std::cout << "File status checks:" << std::endl;
    std::cout << "is_open(): " << (checkFile.is_open() ? "True" : "False") << std::endl;
    std::cout << "good(): " << (checkFile.good() ? "True" : "False") << std::endl;
    std::cout << "fail(): " << (checkFile.fail() ? "True" : "False") << std::endl;

    return 0;
}

编译程序:

g++ file_status.cpp -o file_status

运行可执行文件:

./file_status

示例输出:

File opened successfully for reading.
Read line: John Doe
Read line: Alice Smith, 22 years old, GPA: 3.75
Read line: Computer Science Student
Read line: University of Programming
File opened successfully for writing.
File is in a good state for writing.
File status checks:
is_open(): False
good(): False
fail(): True

关于文件状态检查的关键点:

  • is_open(): 检查文件是否成功打开
  • good(): 检查是否没有发生错误
  • fail(): 检查是否发生了错误
  • 在执行操作之前,务必检查文件状态
  • 不同的方法提供了验证文件状态的各种方式

你可以将文件状态检查想象成旅程开始前的安全检查。这些方法有助于确保你的文件操作是安全且成功的。

处理文件读写错误

在这一步,你将学习如何在 C++ 中处理文件读写操作期间可能发生的潜在错误。理解错误处理对于创建健壮的文件操作程序至关重要。

创建一个名为 file_error_handling.cpp 的新 C++ 文件:

touch ~/project/file_error_handling.cpp

将以下代码添加到 file_error_handling.cpp 文件中:

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

void writeToFile(const std::string& filename) {
    std::ofstream outputFile(filename);

    // 检查文件是否打开
    if (!outputFile) {
        // 如果文件无法打开,则抛出异常
        throw std::runtime_error("Unable to open file for writing: " + filename);
    }

    try {
        // 尝试写入数据
        outputFile << "Hello, Error Handling!" << std::endl;
        outputFile << "This is a sample text." << std::endl;

        // 模拟写入错误(不推荐这样做)
        if (outputFile.bad()) {
            throw std::runtime_error("Write operation failed");
        }
    }
    catch (const std::exception& e) {
        std::cerr << "Error during writing: " << e.what() << std::endl;
    }

    outputFile.close();
}

void readFromFile(const std::string& filename) {
    std::ifstream inputFile(filename);
    std::string line;

    // 检查文件是否打开
    if (!inputFile) {
        // 如果文件无法打开,则抛出异常
        throw std::runtime_error("Unable to open file for reading: " + filename);
    }

    try {
        // 读取并打印文件内容
        std::cout << "File contents:" << std::endl;
        while (std::getline(inputFile, line)) {
            std::cout << line << std::endl;
        }

        // 检查读取错误
        if (inputFile.bad()) {
            throw std::runtime_error("Read operation encountered an error");
        }
    }
    catch (const std::exception& e) {
        std::cerr << "Error during reading: " << e.what() << std::endl;
    }

    inputFile.close();
}

int main() {
    try {
        // 写入文件
        writeToFile("error_handling_example.txt");

        // 从文件读取
        readFromFile("error_handling_example.txt");

        // 尝试从不存在的文件读取
        readFromFile("non_existent_file.txt");
    }
    catch (const std::exception& e) {
        std::cerr << "Main error: " << e.what() << std::endl;
        return 1;
    }

    return 0;
}

编译程序:

g++ file_error_handling.cpp -o file_error_handling

运行可执行文件:

./file_error_handling

示例输出:

File contents:
Hello, Error Handling!
This is a sample text.
Error during reading: Unable to open file for reading: non_existent_file.txt

关于文件错误处理的关键点:

  • 使用 try-catch 块来处理潜在的异常
  • 在操作之前和之后检查文件流的状态
  • 使用 std::runtime_error 来抛出有意义的错误消息
  • 处理不同类型的文件相关错误
  • 提供信息丰富的错误消息

你可以将错误处理想象成一张安全网。它能捕获潜在的问题,防止你的程序意外崩溃。

使用 fstream 处理二进制文件

在这一步,你将学习如何使用 C++ 中的 fstream 类来处理二进制文件。二进制文件以其原始二进制格式存储数据,这与文本文件不同,适用于高效存储结构化数据。

创建一个名为 binary_files.cpp 的新 C++ 文件:

touch ~/project/binary_files.cpp

将以下代码添加到 binary_files.cpp 文件中:

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

// 用于演示二进制文件写入的简单结构体
struct Student {
    int id;
    char name[50];
    double gpa;
};

void writeBinaryFile() {
    std::fstream binaryFile("students.bin", std::ios::out | std::ios::binary);

    if (!binaryFile) {
        std::cerr << "Error opening file for writing!" << std::endl;
        return;
    }

    // 创建一些学生记录
    Student students[3] = {
        {1, "John Doe", 3.5},
        {2, "Alice Smith", 3.8},
        {3, "Bob Johnson", 3.2}
    };

    // 将整个结构体写入二进制文件
    binaryFile.write(reinterpret_cast<char*>(students), sizeof(students));

    binaryFile.close();
    std::cout << "Binary file written successfully!" << std::endl;
}

void readBinaryFile() {
    std::fstream binaryFile("students.bin", std::ios::in | std::ios::binary);

    if (!binaryFile) {
        std::cerr << "Error opening file for reading!" << std::endl;
        return;
    }

    Student students[3];

    // 从二进制文件读取整个结构体
    binaryFile.read(reinterpret_cast<char*>(students), sizeof(students));

    std::cout << "Student Records:" << std::endl;
    for (int i = 0; i < 3; ++i) {
        std::cout << "ID: " << students[i].id
                  << ", Name: " << students[i].name
                  << ", GPA: " << students[i].gpa << std::endl;
    }

    binaryFile.close();
}

int main() {
    // 写入二进制文件
    writeBinaryFile();

    // 读取二进制文件
    readBinaryFile();

    return 0;
}

编译程序:

g++ binary_files.cpp -o binary_files

运行可执行文件:

./binary_files

示例输出:

Binary file written successfully!
Student Records:
ID: 1, Name: John Doe, GPA: 3.5
ID: 2, Name: Alice Smith, GPA: 3.8
ID: 3, Name: Bob Johnson, GPA: 3.2

关于二进制文件的关键点:

  • 使用 std::ios::binary 标志来指定二进制模式
  • 使用 write()read() 方法处理二进制数据
  • 使用 reinterpret_cast 在类型之间进行转换
  • 有助于高效存储结构化数据
  • 保留数据精确的二进制表示

你可以将二进制文件想象成一份精确的蓝图。它们存储的数据与其在内存中的存在方式完全一致,无需进行任何文本转换。

使用 seekg/seekp 定位文件指针

在这一步,你将学习如何使用 C++ 中的 seekg()(用于读取)和 seekp()(用于写入)来操作文件指针。这些方法允许你在文件中导航和修改特定位置。

创建一个名为 file_pointer.cpp 的新 C++ 文件:

touch ~/project/file_pointer.cpp

将以下代码添加到 file_pointer.cpp 文件中:

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

void createSampleFile() {
    std::ofstream file("numbers.txt");
    for (int i = 1; i <= 10; ++i) {
        file << i << " ";
    }
    file.close();
}

void demonstrateSeekOperations() {
    // 以读写模式打开文件
    std::fstream file("numbers.txt", std::ios::in | std::ios::out);

    if (!file) {
        std::cerr << "Error opening file!" << std::endl;
        return;
    }

    // 读取操作
    std::cout << "Reading operations:" << std::endl;

    // 移动到第 5 个字节(字符位置)
    file.seekg(5);
    int value;
    file >> value;
    std::cout << "Value at 5th byte: " << value << std::endl;

    // 移动到文件开头
    file.seekg(0);
    file >> value;
    std::cout << "First value: " << value << std::endl;

    // 在特定位置写入
    std::cout << "\nWriting operations:" << std::endl;

    // 移动到特定位置并写入
    file.seekp(0);
    file << "100 ";

    // 重置文件指针并读取以验证
    file.seekg(0);
    file >> value;
    std::cout << "Modified first value: " << value << std::endl;

    file.close();
}

int main() {
    // 创建一个包含数字的示例文件
    createSampleFile();

    // 演示 seek 操作
    demonstrateSeekOperations();

    return 0;
}

编译程序:

g++ file_pointer.cpp -o file_pointer

运行可执行文件:

./file_pointer

示例输出:

Reading operations:
Value at 5th byte: 4
First value: 1

Writing operations:
Modified first value: 100

关于文件指针的关键点:

  • seekg(): 移动读取指针 (get pointer)
  • seekp(): 移动写入指针 (put pointer)
  • 第一个参数是字节位置
  • 有助于文件中的随机访问
  • 可以导航到特定位置

你可以将文件指针想象成文本编辑器中的光标。seekg()seekp() 帮助你精确地将此光标移动到你想要的位置。

关闭文件并释放资源

在这一步,你将学习如何正确关闭文件和管理 C++ 中的资源,以防止内存泄漏并确保高效的文件处理。你将探索不同的文件关闭方法,并了解资源获取即初始化 (RAII) 原则的应用。

创建一个名为 file_resources.cpp 的新 C++ 文件:

touch ~/project/file_resources.cpp

将以下代码添加到 file_resources.cpp 文件中:

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

// 手动文件关闭
void manualFileHandling() {
    std::ofstream outputFile("manual_file.txt");

    if (outputFile.is_open()) {
        outputFile << "Manually managed file resource" << std::endl;

        // 显式关闭文件
        outputFile.close();

        std::cout << "File closed manually." << std::endl;
    }
}

// 使用 unique_ptr 进行基于 RAII 的文件处理
void raii_fileHandling() {
    try {
        // 使用 unique_ptr 管理文件资源
        std::unique_ptr<std::ofstream> file(new std::ofstream("raii_file.txt"));

        if (file && file->is_open()) {
            *file << "RAII-managed file resource" << std::endl;
            std::cout << "RAII file handling successful." << std::endl;
        }
        // 当 unique_ptr 离开作用域时,文件会自动关闭
    }
    catch (const std::exception& e) {
        std::cerr << "Error in RAII file handling: " << e.what() << std::endl;
    }
}

// 基于作用域的文件处理
void scopeBasedFileHandling() {
    {
        // 文件在离开作用域时会自动关闭
        std::ofstream scopedFile("scoped_file.txt");

        if (scopedFile.is_open()) {
            scopedFile << "Scope-based file resource management" << std::endl;
            std::cout << "Scoped file handling successful." << std::endl;
        }
    } // 文件在此处自动关闭
}

int main() {
    // 演示不同的文件资源管理技术
    manualFileHandling();
    raii_fileHandling();
    scopeBasedFileHandling();

    return 0;
}

编译程序:

g++ file_resources.cpp -o file_resources

运行可执行文件:

./file_resources

示例输出:

File closed manually.
RAII file handling successful.
Scoped file handling successful.

关于关闭文件和管理资源的关键点:

  • 使用后务必关闭文件
  • 使用 close() 方法进行手动关闭
  • 利用 RAII 原则
  • 使用基于作用域的资源管理
  • 防止资源泄漏
  • 处理文件操作期间的异常

你可以将文件资源想象成从图书馆借书。用完后务必归还书(关闭文件),以保持图书馆(系统资源)的整洁有序。

总结

在这个实验中,你学习了如何使用标准库中的 ofstreamifstream 类在 C++ 中打开文件。你探索了检查文件打开状态、向文件写入文本数据以及从文本文件中读取数据的过程。此外,你还了解了如何处理文件读/写错误、使用 fstream 处理二进制文件、使用 seekg/seekp 定位文件指针,以及如何正确关闭文件和释放资源。这些基本的文件操作对于管理 C++ 应用程序中的数据存储和检索至关重要。