Bootstrap

IO流(C++)

1. IO流的基本概念

1)什么是流(Stream)?

流是一种数据传输的抽象概念,表示数据从一个地方流向另一个地方的过程。可以将流比作一条管道,数据就像水一样通过它流动。

  • 输入流(Input Stream):数据从外部设备(如键盘、文件)流入到程序。
  • 输出流(Output Stream):数据从程序流出到外部设备(如屏幕、文件)。

2)IO流的作用

简化数据的输入输出操作。抽象化底层设备操作,提供一致的编程接口。通过多态和继承实现灵活扩展,比如文件流、自定义流等。

3)IO流的分类

  • 标准IO流:负责与控制台交互的数据流,如cin, cout, cerr
  • 文件IO流:用于处理文件的输入输出,如ifstream, ofstream, fstream
  • 字符串流:处理内存中的字符串数据,如istringstream, ostringstream, stringstream(位于<sstream>中)。

4)流的特点

  1. 类型安全:通过流操作符和函数模板,避免了直接操作设备带来的风险。
  2. 支持链式操作:如cout << "Hello" << " World" << endl;
  3. 可扩展性:通过继承和多态,可以自定义流类型以满足特殊需求。
  4. 状态管理:流提供了一组状态标志(如eof, fail),方便检测操作是否成功。

2. 标准输入

在使用C++标准输入流(std::cin)时,需要注意以下几点:
*以下的代码中均包含<iostream>的头文件,展开std的命名空间

a. 流的状态管理

std::cin 在读取数据时可能会遇到错误,这时需要检查和处理流的状态:

  • 常见状态标志

    • eof():是否到达输入结束标志(End of File)。
    • fail():是否发生了格式化错误。更广泛的错误状态,包括“bad”错误和其他非严重错误。
    • bad():是否发生了不可恢复的输入输出错误。
    • good():流是否处于正常状态。

    示例:

    int main() {
        int num;
        cout << "Enter a number: ";
        cin >> num;
    
        if (cin.fail()) 
            cout << "Invalid input! Please enter a valid number." << endl;
        else 
            cout << "You entered: " << num << endl;
        return 0;
    }
    

b. 输入缓冲区的处理

  • std::cin 会从输入缓冲区读取数据。如果缓冲区中有多余的数据,可能会导致意外行为:

    • 如果输入了错误的类型(如输入字母而不是数字),缓冲区会保留错误数据,导致后续输入失败。
    • std::cin.ignore() 可以丢弃缓冲区中的多余数据。

    示例:

    int main() {
        int num;
        cout << "Enter a number: ";
        cin >> num;
    
        if (cin.fail()) {
            cin.clear();              // 清除错误状态
            cin.ignore(100, '\n');    // 丢弃缓冲区中多余的数据
            cout << "Invalid input. Try again." << endl;
        } else {
            cout << "You entered: " << num << endl;
        }
        return 0;
    }
    
  • 如果输入数据的类型与预期不符,std::cin 会进入失败状态,导致后续读取失败。

  • 使用显式类型转换或验证输入类型可以避免这类问题。

    示例:

    int main() {
        int num;
        cout << "Enter a number: ";
        while (!(cin >> num)) {    // 输入非数字时失败
            cin.clear();           // 清除错误状态
            cin.ignore(100, '\n'); // 丢弃缓冲区内容
            cout << "Invalid input. Please enter a number: ";
        }
        cout << "You entered: " << num << endl;
        return 0;
    }
    

c. 读取整行输入

  • 使用 std::cin 读取字符串时会遇到问题,因为它只读取空格前的部分,遇到空格就停止读入。如果需要读取整行,可以使用 std::getline()

  • 在混合使用 std::cinstd::getline() 时,需要注意缓冲区中可能残留换行符。

    示例:

    #include <string>
    
    int main() {
        string name;
        int age;
    
        cout << "Enter your age: ";
        cin >> age;                 // 读取年龄
    
        cin.ignore();               // 清除换行符
        cout << "Enter your name: ";
        getline(cin, name);         // 读取整行
    
        cout << "Hello, " << name << ". You are " << age << " years old." << endl;
        return 0;
    }
    

d. 输入流同步问题

  • 默认情况下,std::cin 和 C 风格的输入(如 scanf)是同步的,可能会影响性能。

  • 如果只使用 C++ 风格的流操作,可以通过 std::ios::sync_with_stdio(false) 关闭同步以提高速度。 std::ios::sync_with_stdio(false) 被调用来关闭同步,然后程序分别使用C++的IO流和C语言的IO函数输出信息。由于关闭了同步,两个输出的顺序可能不确定。

    示例:

    int main() {
        ios::sync_with_stdio(false); // 关闭同步
        cin.tie(nullptr);            // 解绑 `cin` 和 `cout`
        
        int num;
        cout << "Enter a number: ";
        cin >> num;
        cout << "You entered: " << num << endl;
        return 0;
    }
    

e. 特殊输入情况

  • Ctrl+D(Linux/macOS)或 Ctrl+Z(Windows):表示输入结束(EOF)。
  • 空输入:需要额外处理,例如在读取整行时判断空行。

3. 标准输出

在使用 C++ 标准输出流(std::cout)时,有一些重要的注意点和技巧,可以帮助更高效地进行输出操作,同时避免潜在问题。
*以下的代码中均包含<iostream>的头文件,展开std的命名空间。

a. 输出格式控制

  • 使用 流操纵符(Manipulators)可以调整输出格式:

    • std::endl:换行并刷新缓冲区。
    • std::setw(n):设置字段宽度(需要引入 <iomanip>)。
    • std::setprecision(n):设置浮点数精度。
    • std::fixedstd::scientific:控制浮点数的输出格式。

    示例:

    #include <iomanip>
    
    int main() {
        double num = 3.14159265358979;
    
        cout << "Default: " << num << endl;
        cout << "Fixed: " << fixed << setprecision(2) << num << endl;
        cout << "Scientific: " << scientific << num << endl;
    
        cout << setw(10) << 42 << endl; // 设置字段宽度
        return 0;
    }
    

b. 缓冲区与刷新

  • 自动刷新std::endl 除了换行,还会刷新缓冲区(flush 操作)。

    • 刷新操作可能会降低性能,因此如果仅需换行,建议使用 \n
  • 手动刷新:可以用 std::cout.flush() 手动刷新缓冲区。

    示例:

    int main() {
        cout << "Processing..." << flush; // 不换行,立刻输出
        // 模拟延迟
        for (int i = 0; i < 3; i++) {
            cout << "." << flush;
        }
        cout << "\nDone!" << endl;
        return 0;
    }
    

c. 标准错误输出

  • 使用 std::cerr 进行错误消息输出:

    • 不带缓冲区,因此输出效率较高,适合实时错误提示。
  • 使用 std::clog 进行日志记录:

    • 带缓冲区,适合输出非关键的日志信息。

    示例:

    int main() {
        cerr << "Error: Something went wrong!" << endl;
        clog << "Log: Program started." << endl;
        return 0;
    }
    

d. 国际化支持

  • C++ 支持基于本地化的输出,例如数字和货币格式。

  • 使用 std::locale 可以改变流的本地化行为。

    示例:

    #include <locale>
    
    int main() {
        cout.imbue(locale("en_US.UTF-8")); // 使用美国英语本地化
        cout << 1234567.89 << endl;
        return 0;
    }
    

常见问题总结

  1. 缓冲区刷新:避免过度使用 std::endl 影响性能,尽量使用 \n 或显式刷新。
  2. 混用 printfstd::cout:如无必要,建议避免混用,或关闭同步确保性能。
  3. 格式控制:注意浮点数精度与字段宽度设置,避免输出不符合预期。

通过正确管理 std::cout,可以确保输出高效且美观,同时避免常见的输出问题。

以下是对 C++ 文件 IO 流 的详细介绍:


4. 文件IO流

a. 文件 IO 流的基本概念

  • 文件流是 C++ 提供的用于对文件进行输入输出操作的机制,主要包含以下三种流类:

    • std::ifstream:输入文件流,用于从文件中读取数据。
    • std::ofstream:输出文件流,用于向文件中写入数据。
    • std::fstream:文件流,可以同时支持文件的输入和输出操作。
  • 文件流通过继承自标准流(如 std::istreamstd::ostream)来复用流操作功能。

b. 文件流状态检查

文件流提供状态检查方法以确保文件操作的正确性:

  • is_open():检查文件是否成功打开。
  • eof():是否到达文件末尾。
  • fail():是否发生格式化错误。
  • bad():是否发生严重错误。
  • good():是否一切正常。

示例:

std::ifstream inputFile("example.txt");
if (!inputFile.is_open()) {
    std::cerr << "Failed to open the file." << std::endl;
}

c. 文件操作的基本步骤

文件操作通常包括以下步骤:

c.1 打开文件

  • 使用构造函数打开文件:

    std::ifstream inputFile("example.txt");   // 打开文件进行读取
    std::ofstream outputFile("output.txt");  // 打开文件进行写入
    std::fstream file("data.txt", std::ios::in | std::ios::out); // 读写模式
    
  • 使用 open() 方法打开文件:

    std::ifstream inputFile;
    inputFile.open("example.txt");
    
    std::ofstream outputFile;
    outputFile.open("output.txt");
    
  • 打开模式(std::ios 标志)

    • std::ios::in:读模式(默认用于 std::ifstream)。
    • std::ios::out:写模式(默认用于 std::ofstream)。
    • std::ios::app:追加模式。
    • std::ios::ate:打开文件并将文件指针移到末尾。
    • std::ios::binary:二进制模式。
    • std::ios::trunc:如果文件存在,则清空文件内容。

c.2 读写文件

  • 写文件(std::ofstreamstd::fstream

    std::ofstream outputFile("output.txt");
    if (outputFile.is_open()) {
        outputFile << "Hello, World!" << std::endl;
        outputFile.close();
    }
    
  • 读文件(std::ifstreamstd::fstream

    std::ifstream inputFile("example.txt");
    if (inputFile.is_open()) {
        std::string line;
        while (std::getline(inputFile, line)) {
            std::cout << line << std::endl;  // 打印每一行
        }
        inputFile.close();
    }
    

c.3 关闭文件

  • 文件操作完成后需要关闭文件以释放资源:
    inputFile.close();
    outputFile.close();
    

d. 二进制文件操作

  • 对于二进制文件,使用 read()write() 方法。
  • 示例:
    #include <fstream>
    using namespace std;
    
    struct Data {
        int id;
        char name[50];
    };
    
    int main() {
        Data d = {1, "Alice"};
    
        // 写入二进制文件
        ofstream outFile("data.bin", ios::binary);
        if (outFile.is_open()) {
            outFile.write(reinterpret_cast<char*>(&d), sizeof(d));
            outFile.close();
        }
    
        // 从二进制文件读取
        Data readData;
        ifstream inFile("data.bin", ios::binary);
        if (inFile.is_open()) {
            inFile.read(reinterpret_cast<char*>(&readData), sizeof(readData));
            inFile.close();
        }
    
        cout << "ID: " << readData.id << ", Name: " << readData.name << endl;
        return 0;
    }
    

e. 文件流中的高级功能

e.1 文件指针的操作

  • 文件流支持文件指针的移动:

    • seekg(offset, dir):移动输入指针。
    • seekp(offset, dir):移动输出指针。
    • tellg():获取输入指针的位置。
    • tellp():获取输出指针的位置。

    示例:

    file.seekg(0, std::ios::end);  // 移动到文件末尾
    auto fileSize = file.tellg(); // 获取文件大小
    file.seekg(0, std::ios::beg); // 移回文件开头
    

e.2 自定义缓冲区

  • 文件流允许使用自定义缓冲区来优化性能(较为高级)。

f. 常见问题与注意事项

  • 未能打开文件:确保文件路径正确,必要时添加错误检查。
  • 文件操作权限:在某些操作系统上需要检查文件的读写权限。
  • 覆盖文件内容std::ofstream 默认会覆盖文件内容,如需追加,使用 std::ios::app 模式。
  • 二进制与文本模式的区别:文本模式可能会对换行符进行转换,而二进制模式不会。

g. 综合示例

以下是一个综合的文件读写示例:

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

int main() {
    // 写文件
    ofstream outFile("example.txt");
    if (outFile.is_open()) {
        outFile << "Hello, World!" << endl;
        outFile << "This is a test file." << endl;
        outFile.close();
    } else {
        cerr << "Failed to open file for writing." << endl;
    }

    // 读文件
    ifstream inFile("example.txt");
    if (inFile.is_open()) {
        string line;
        while (getline(inFile, line)) {
            cout << line << endl; // 输出每一行
        }
        inFile.close();
    } else {
        cerr << "Failed to open file for reading." << endl;
    }

    return 0;
}

【总结】 文件 IO 流是 C++ IO 系统的重要组成部分,提供了对文件的高效操作支持。在实际应用中,合理利用文件流可以极大提高程序的功能和鲁棒性。

如果记不住的话,可以在需要用的时候再回来查,记得收藏一波不迷路哦~

;