MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C++ 文件输入输出深入解析

2022-03-082.7k 阅读

C++ 文件输入输出基础

流的概念

在 C++ 中,文件输入输出是通过流(stream)来实现的。流是一种抽象,它表示字节的序列。在文件输入输出的场景下,流连接了程序与外部文件。输入流(input stream)用于从文件读取数据到程序,而输出流(output stream)则用于将程序中的数据写入文件。

头文件

要进行文件输入输出操作,需要包含 <fstream> 头文件。这个头文件定义了三个主要的类:std::ifstream 用于输入文件流,std::ofstream 用于输出文件流,以及 std::fstream 用于既可以读又可以写的文件流。

打开和关闭文件

  1. 使用构造函数打开文件
    • 对于 std::ofstream,可以在构造对象时指定文件名来打开文件用于写入。例如:
    #include <iostream>
    #include <fstream>
    int main() {
        std::ofstream outFile("example.txt");
        if (outFile.is_open()) {
            outFile << "This is some text to write to the file." << std::endl;
            outFile.close();
        } else {
            std::cerr << "Unable to open file for writing." << std::endl;
        }
        return 0;
    }
    
    • 在上述代码中,std::ofstream outFile("example.txt"); 使用构造函数尝试打开名为 example.txt 的文件。如果文件成功打开,is_open() 函数返回 true,然后可以向文件中写入数据,最后通过 close() 函数关闭文件。
    • 对于 std::ifstream,同样可以在构造时指定文件名来打开文件用于读取。例如:
    #include <iostream>
    #include <fstream>
    #include <string>
    int main() {
        std::ifstream inFile("example.txt");
        if (inFile.is_open()) {
            std::string line;
            while (std::getline(inFile, line)) {
                std::cout << line << std::endl;
            }
            inFile.close();
        } else {
            std::cerr << "Unable to open file for reading." << std::endl;
        }
        return 0;
    }
    
  2. 使用 open() 函数打开文件
    • 也可以先创建流对象,然后使用 open() 函数来打开文件。例如:
    #include <iostream>
    #include <fstream>
    int main() {
        std::ofstream outFile;
        outFile.open("another_example.txt");
        if (outFile.is_open()) {
            outFile << "Writing to another file." << std::endl;
            outFile.close();
        } else {
            std::cerr << "Unable to open file for writing." << std::endl;
        }
        return 0;
    }
    
    • 这种方式在需要动态决定文件名等情况下更为灵活。

文件模式

  1. 输出模式
    • std::ios::out:默认的输出模式,用于写入文件。如果文件不存在,会创建文件;如果文件已存在,会截断文件(即清空原有内容)。例如:
    std::ofstream outFile("test.txt", std::ios::out);
    
    • std::ios::app:追加模式。文件打开后,写入操作会在文件末尾进行,不会截断原有内容。例如:
    std::ofstream outFile("test.txt", std::ios::app);
    outFile << "This is appended text." << std::endl;
    
    • std::ios::trunc:截断模式。确保文件被截断,即原有内容被清空,这与默认的 std::ios::out 效果类似,只是显式指定了截断操作。例如:
    std::ofstream outFile("test.txt", std::ios::trunc);
    
  2. 输入模式
    • std::ios::in:用于打开文件进行读取。例如:
    std::ifstream inFile("test.txt", std::ios::in);
    
  3. 读写模式
    • std::ios::in | std::ios::out:用于打开文件既可以读又可以写。例如:
    std::fstream ioFile("test.txt", std::ios::in | std::ios::out);
    
    • std::ios::ate:打开文件后定位到文件末尾。这在需要先读取文件末尾信息,然后进行读写操作时很有用。例如:
    std::fstream ioFile("test.txt", std::ios::in | std::ios::out | std::ios::ate);
    

基本文件输入操作

读取字符

  1. 使用 get() 函数
    • std::ifstream 类提供了 get() 函数来读取单个字符。例如:
    #include <iostream>
    #include <fstream>
    int main() {
        std::ifstream inFile("example.txt");
        if (inFile.is_open()) {
            char ch;
            while (inFile.get(ch)) {
                std::cout << ch;
            }
            inFile.close();
        } else {
            std::cerr << "Unable to open file for reading." << std::endl;
        }
        return 0;
    }
    
    • 在上述代码中,inFile.get(ch) 每次读取一个字符并存储到 ch 中。当到达文件末尾时,get() 函数返回 false,从而退出循环。
  2. 使用 getline() 函数读取一行
    • std::getline() 函数可以从输入流中读取一行文本。它有两种形式,一种是针对 std::string 的,另一种是针对字符数组的。
    • 针对 std::string 的形式:
    #include <iostream>
    #include <fstream>
    #include <string>
    int main() {
        std::ifstream inFile("example.txt");
        if (inFile.is_open()) {
            std::string line;
            while (std::getline(inFile, line)) {
                std::cout << line << std::endl;
            }
            inFile.close();
        } else {
            std::cerr << "Unable to open file for reading." << std::endl;
        }
        return 0;
    }
    
    • 这里 std::getline(inFile, line)inFile 中读取一行到 line 中,每次读取都会忽略换行符。
    • 针对字符数组的形式:
    #include <iostream>
    #include <fstream>
    int main() {
        std::ifstream inFile("example.txt");
        if (inFile.is_open()) {
            char buffer[100];
            while (inFile.getline(buffer, 100)) {
                std::cout << buffer << std::endl;
            }
            inFile.close();
        } else {
            std::cerr << "Unable to open file for reading." << std::endl;
        }
        return 0;
    }
    
    • inFile.getline(buffer, 100)inFile 中读取最多 99 个字符(为 '\0' 预留一个位置)到 buffer 中。

格式化输入

  1. 使用提取运算符 >>
    • 可以像从 std::cin 读取数据一样,使用 >> 运算符从文件输入流中读取格式化数据。例如:
    #include <iostream>
    #include <fstream>
    int main() {
        std::ifstream inFile("data.txt");
        if (inFile.is_open()) {
            int num;
            double dbl;
            inFile >> num >> dbl;
            std::cout << "Read integer: " << num << ", double: " << dbl << std::endl;
            inFile.close();
        } else {
            std::cerr << "Unable to open file for reading." << std::endl;
        }
        return 0;
    }
    
    • 假设 data.txt 文件内容为 10 3.14,上述代码会从文件中读取一个整数和一个双精度浮点数。

基本文件输出操作

写入字符和字符串

  1. 使用 put() 函数写入单个字符
    • std::ofstream 类的 put() 函数可以将单个字符写入文件。例如:
    #include <iostream>
    #include <fstream>
    int main() {
        std::ofstream outFile("example.txt");
        if (outFile.is_open()) {
            outFile.put('H');
            outFile.put('e');
            outFile.put('l');
            outFile.put('l');
            outFile.put('o');
            outFile.close();
        } else {
            std::cerr << "Unable to open file for writing." << std::endl;
        }
        return 0;
    }
    
    • 上述代码会在 example.txt 文件中写入字符串 Hello
  2. 使用插入运算符 << 写入字符串和其他数据类型
    • 可以使用 << 运算符将字符串、数字等数据类型写入文件。例如:
    #include <iostream>
    #include <fstream>
    int main() {
        std::ofstream outFile("example.txt");
        if (outFile.is_open()) {
            outFile << "This is a string." << std::endl;
            int num = 42;
            outFile << "The number is: " << num << std::endl;
            outFile.close();
        } else {
            std::cerr << "Unable to open file for writing." << std::endl;
        }
        return 0;
    }
    
    • 这里先写入一个字符串,然后写入一个整数。

格式化输出

  1. 控制输出格式
    • 可以使用 C++ 的流操纵符来控制输出格式。例如,设置浮点数的精度:
    #include <iostream>
    #include <fstream>
    #include <iomanip>
    int main() {
        std::ofstream outFile("example.txt");
        if (outFile.is_open()) {
            double pi = 3.14159265358979323846;
            outFile << std::fixed << std::setprecision(2) << pi << std::endl;
            outFile.close();
        } else {
            std::cerr << "Unable to open file for writing." << std::endl;
        }
        return 0;
    }
    
    • std::fixed 表示以固定小数位数形式输出,std::setprecision(2) 设置小数位数为 2。所以上述代码会在文件中写入 3.14

文件定位

输入流定位

  1. tellg() 函数
    • tellg() 函数用于获取输入流中当前的读取位置。例如:
    #include <iostream>
    #include <fstream>
    int main() {
        std::ifstream inFile("example.txt");
        if (inFile.is_open()) {
            char ch;
            inFile.get(ch);
            std::streampos pos = inFile.tellg();
            std::cout << "Current position after reading one character: " << pos << std::endl;
            inFile.close();
        } else {
            std::cerr << "Unable to open file for reading." << std::endl;
        }
        return 0;
    }
    
    • std::streampos 是一个表示流位置的类型。上述代码读取一个字符后,获取当前的读取位置并输出。
  2. seekg() 函数
    • seekg() 函数用于移动输入流中的读取位置。它有两种重载形式。
    • 第一种形式接受一个偏移量和一个参照位置:
    #include <iostream>
    #include <fstream>
    int main() {
        std::ifstream inFile("example.txt");
        if (inFile.is_open()) {
            inFile.seekg(5, std::ios::beg); // 从文件开头移动 5 个字节
            char ch;
            inFile.get(ch);
            std::cout << "Character at position 5: " << ch << std::endl;
            inFile.close();
        } else {
            std::cerr << "Unable to open file for reading." << std::endl;
        }
        return 0;
    }
    
    • 这里 std::ios::beg 表示文件开头,5 是偏移量,即从文件开头移动 5 个字节后读取一个字符。
    • 第二种形式接受一个绝对位置:
    #include <iostream>
    #include <fstream>
    int main() {
        std::ifstream inFile("example.txt");
        if (inFile.is_open()) {
            std::streampos pos = 10;
            inFile.seekg(pos);
            char ch;
            inFile.get(ch);
            std::cout << "Character at position 10: " << ch << std::endl;
            inFile.close();
        } else {
            std::cerr << "Unable to open file for reading." << std::endl;
        }
        return 0;
    }
    
    • 这里直接指定了绝对位置 10

输出流定位

  1. tellp() 函数
    • tellp() 函数用于获取输出流中当前的写入位置。例如:
    #include <iostream>
    #include <fstream>
    int main() {
        std::ofstream outFile("example.txt");
        if (outFile.is_open()) {
            outFile << "Hello";
            std::streampos pos = outFile.tellp();
            std::cout << "Current position after writing 'Hello': " << pos << std::endl;
            outFile.close();
        } else {
            std::cerr << "Unable to open file for writing." << std::endl;
        }
        return 0;
    }
    
    • 上述代码写入字符串 Hello 后,获取当前的写入位置并输出。
  2. seekp() 函数
    • seekp() 函数用于移动输出流中的写入位置,用法与 seekg() 类似。例如:
    #include <iostream>
    #include <fstream>
    int main() {
        std::ofstream outFile("example.txt");
        if (outFile.is_open()) {
            outFile << "Hello World";
            outFile.seekp(6); // 移动到第 6 个位置
            outFile << "C++";
            outFile.close();
        } else {
            std::cerr << "Unable to open file for writing." << std::endl;
        }
        return 0;
    }
    
    • 这里先写入 Hello World,然后移动到第 6 个位置,再写入 C++,最终文件内容为 Hello C++ld

文件操作中的错误处理

检测流状态

  1. 使用 fail() 函数
    • fail() 函数用于检测流操作是否失败。例如,在读取文件时,如果格式不正确导致读取失败,fail() 会返回 true
    #include <iostream>
    #include <fstream>
    int main() {
        std::ifstream inFile("data.txt");
        int num;
        inFile >> num;
        if (inFile.fail()) {
            std::cerr << "Failed to read integer from file." << std::endl;
        }
        inFile.close();
        return 0;
    }
    
    • 假设 data.txt 文件内容为 abc,上述代码读取整数时会失败,fail() 函数返回 true
  2. 使用 bad() 函数
    • bad() 函数用于检测流是否发生了严重错误,比如硬件故障等。例如:
    #include <iostream>
    #include <fstream>
    int main() {
        std::ifstream inFile("nonexistent_file.txt");
        if (inFile.bad()) {
            std::cerr << "A serious error occurred while opening the file." << std::endl;
        }
        inFile.close();
        return 0;
    }
    
    • 如果尝试打开一个不存在的文件,并且系统底层发生了更严重的错误(不仅仅是文件不存在这种常规错误),bad() 函数可能返回 true
  3. 使用 eof() 函数
    • eof() 函数用于检测是否到达文件末尾。例如:
    #include <iostream>
    #include <fstream>
    int main() {
        std::ifstream inFile("example.txt");
        char ch;
        while (!inFile.eof()) {
            inFile.get(ch);
            std::cout << ch;
        }
        inFile.close();
        return 0;
    }
    
    • 上述代码在未到达文件末尾时,持续读取并输出字符。不过,这种使用 eof() 的方式不太推荐,更好的方式是在读取操作的返回值上进行判断,如之前 get() 函数示例中的方式。

清除错误状态

  1. 使用 clear() 函数
    • 当流发生错误后,可以使用 clear() 函数清除错误状态,以便后续操作。例如:
    #include <iostream>
    #include <fstream>
    int main() {
        std::ifstream inFile("data.txt");
        int num;
        inFile >> num;
        if (inFile.fail()) {
            std::cerr << "Failed to read integer. Clearing error state." << std::endl;
            inFile.clear();
            inFile.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
            inFile >> num;
            if (inFile.fail()) {
                std::cerr << "Still failed to read integer after clearing error." << std::endl;
            } else {
                std::cout << "Successfully read integer: " << num << std::endl;
            }
        }
        inFile.close();
        return 0;
    }
    
    • 这里先尝试读取整数,如果失败,清除错误状态,并忽略当前行剩余的字符,然后再次尝试读取整数。

二进制文件操作

写入二进制数据

  1. 使用 write() 函数
    • std::ofstreamwrite() 函数用于将二进制数据写入文件。例如,将一个整数以二进制形式写入文件:
    #include <iostream>
    #include <fstream>
    int main() {
        std::ofstream outFile("binary_data.bin", std::ios::binary);
        if (outFile.is_open()) {
            int num = 42;
            outFile.write(reinterpret_cast<const char*>(&num), sizeof(int));
            outFile.close();
        } else {
            std::cerr << "Unable to open file for writing." << std::endl;
        }
        return 0;
    }
    
    • reinterpret_cast<const char*>(&num) 将整数 num 的地址转换为 const char* 类型,sizeof(int) 表示要写入的字节数。注意要以 std::ios::binary 模式打开文件,这样才能正确写入二进制数据。
  2. 写入结构体
    • 同样可以将结构体以二进制形式写入文件。例如:
    #include <iostream>
    #include <fstream>
    struct Point {
        int x;
        int y;
    };
    int main() {
        std::ofstream outFile("point_data.bin", std::ios::binary);
        if (outFile.is_open()) {
            Point p = {10, 20};
            outFile.write(reinterpret_cast<const char*>(&p), sizeof(Point));
            outFile.close();
        } else {
            std::cerr << "Unable to open file for writing." << std::endl;
        }
        return 0;
    }
    
    • 这里将 Point 结构体以二进制形式写入文件。

读取二进制数据

  1. 使用 read() 函数
    • std::ifstreamread() 函数用于从二进制文件中读取数据。例如,读取之前写入的整数:
    #include <iostream>
    #include <fstream>
    int main() {
        std::ifstream inFile("binary_data.bin", std::ios::binary);
        if (inFile.is_open()) {
            int num;
            inFile.read(reinterpret_cast<char*>(&num), sizeof(int));
            std::cout << "Read integer: " << num << std::endl;
            inFile.close();
        } else {
            std::cerr << "Unable to open file for reading." << std::endl;
        }
        return 0;
    }
    
    • reinterpret_cast<char*>(&num) 将整数 num 的地址转换为 char* 类型,read() 函数从文件中读取 sizeof(int) 字节的数据到 num 中。
  2. 读取结构体
    • 读取之前写入的 Point 结构体:
    #include <iostream>
    #include <fstream>
    struct Point {
        int x;
        int y;
    };
    int main() {
        std::ifstream inFile("point_data.bin", std::ios::binary);
        if (inFile.is_open()) {
            Point p;
            inFile.read(reinterpret_cast<char*>(&p), sizeof(Point));
            std::cout << "Read point: (" << p.x << ", " << p.y << ")" << std::endl;
            inFile.close();
        } else {
            std::cerr << "Unable to open file for reading." << std::endl;
        }
        return 0;
    }
    
    • 这里从二进制文件中读取 Point 结构体的数据。

通过以上对 C++ 文件输入输出的深入解析,包括基础操作、文件定位、错误处理以及二进制文件操作等方面,希望读者能够全面掌握 C++ 文件输入输出的相关知识,并在实际编程中灵活运用。