c++流对象

核心概念回顾:

C++ 的流库 (<iostream>, <fstream>, <sstream>) 提供了一种统一的方式来处理输入和输出,无论数据是来自键盘、文件还是内存中的字符串。它们都基于 std::istream (输入流基类) 和 std::ostream (输出流基类),共享许多共同的接口和行为。


一、 标准流对象 (<iostream>)

这些是 C++ 标准库预先创建好的全局流对象,用于与标准输入/输出设备交互。

  • 包含头文件: <iostream>
  • 主要对象:
    • std::cin: 标准输入流(通常是键盘)。
    • std::cout: 标准输出流(通常是屏幕)。
    • std::cerr: 标准错误输出流(通常是屏幕,无缓冲)。
    • std::clog: 标准错误输出流(通常是屏幕,有缓冲)。

1. 用法总结:

  • 输入 (std::cin):

    • 使用提取操作符 >> 从标准输入读取数据。
    • >> 会根据变量类型自动解析输入,并自动跳过前面的空白字符(空格、制表符、换行符)。
    • 可以链式使用 >> 读取多个值:std::cin >> var1 >> var2;
    • 使用 std::getline(std::cin, string_var) 来读取包括空白字符在内的整行文本,直到遇到换行符。
    • 核心模式:if 条件中使用 std::cin 对象本身来检查读取是否成功:if (std::cin >> variable)
  • 输出 (std::cout, std::cerr, std::clog):

    • 使用插入操作符 << 将数据发送到标准输出或标准错误输出。
    • << 会根据数据类型自动格式化输出。
    • 可以链式使用 << 输出多个值:std::cout << data1 << data2;
    • 使用 std::endl 可以输出一个换行符并立即刷新(flush)缓冲区,确保内容立即显示。
    • 使用 '\n' 只输出换行符,通常效率比 std::endl 高(不强制刷新),刷新会延迟到缓冲区满或显式刷新时。
    • 可以使用 <iomanip> 中的操纵符进行更复杂的格式化(如设置精度、宽度、填充字符等)。

2. 使用技巧:

  • 检查输入状态: 务必 在每次输入操作后检查流的状态,特别是使用 >> 时。if (std::cin >> variable) 是处理潜在输入错误(如输入了非数字字符给整型变量)的标准方式。
    • 如果输入失败,流会进入失败状态 (failbitbadbit 置位),后续的输入操作将不会执行,直到你调用 std::cin.clear() 清除错误标志。
    • std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); 是一个常用的技巧,用于在输入失败或读取部分行后,忽略当前行的剩余内容,以便开始处理下一行输入。需要包含 <limits> 头文件。
  • 混合使用 >>getline: 这是常见的陷阱。>> 操作会读取数据,但会把结尾的换行符留在输入缓冲区里。紧接着调用 getline 时,它会立即读到这个遗留的换行符,导致读取到一个空字符串。解决办法是在 >> 之后、getline 之前,调用 std::cin.ignore() 来清理掉遗留的换行符。
  • 选择 cerr vs clog:
    • std::cerr 是无缓冲的,错误信息会立即显示,适合报告需要用户立即看到的关键错误。
    • std::clog 是有缓冲的,输出可能会延迟,适合记录日志信息,性能可能略好。
  • 刷新输出: 对于 std::coutstd::clog (缓冲流),数据不是一写进去就立即显示。你可以使用 std::endlstd::flush 来强制刷新缓冲区。对于需要确保信息立即出现(例如在程序崩溃前),使用 std::endlstd::flush 是必要的。
  • 格式化技巧: 结合 <iomanip> 头文件中的操纵符,可以控制输出的宽度 (std::setw), 精度 (std::setprecision), 浮点数表示 (std::fixed, std::scientific), 填充字符 (std::setfill), 数字基数 (std::dec, std::hex, std::oct) 等。

二、 字符串流类 (<sstream>)

这些类允许你将一个内存中的 std::string 对象当作输入或输出流来处理,是实现字符串与各种数据类型之间转换的强大工具。

  • 包含头文件: <sstream>
  • 主要类:
    • std::istringstream: 输入字符串流,从字符串读取
    • std::ostringstream: 输出字符串流,向字符串写入
    • std::stringstream: 读/写字符串流,既可以读又可以写同一个字符串。

1. 用法总结:

  • std::istringstream (用于解析/读取字符串):

    • 用一个 std::string 初始化一个 istringstream 对象:std::istringstream iss(my_string);
    • 使用 >> 操作符从 iss 中读取数据,其行为与 std::cin >> 类似(跳过空白、按类型解析)。
    • 使用 std::getline(iss, string_var)iss 中读取整行或以指定分隔符分隔的部分。
    • 同样,可以使用 if (iss >> variable) 检查读取是否成功。
    • 例子:std::istringstream iss("10 20.5 hello"); int i; double d; std::string s; iss >> i >> d >> s; // i=10, d=20.5, s="hello"
    • 例子:std::istringstream iss("part1,part2,part3"); std::string part; while(std::getline(iss, part, ',')) { ... } // 按逗号分割字符串
  • std::ostringstream (用于构建/写入字符串):

    • 创建一个 ostringstream 对象:std::ostringstream oss;
    • 使用 << 操作符向 oss 中写入数据,其行为与 std::cout << 类似(按类型格式化)。
    • 可以使用 <iomanip> 操纵符进行格式化。
    • 完成写入后,调用 oss.str() 成员函数获取最终构建好的 std::string
    • 例子:std::ostringstream oss; oss << "The answer is: " << 42; std::string result = oss.str(); // result = "The answer is: 42"
  • std::stringstream (用于读写同一个字符串):

    • 可以同时使用 >><< 操作符。
    • 通常用于更复杂的场景,需要在一个字符串中定位、读出部分内容,然后又在同一位置写入新的内容(较少见,且效率不如直接构建新字符串)。
    • 例子:std::stringstream ss("initial text"); ss << "modified "; std::string s = ss.str(); // s = "modified text" (写入从当前位置开始覆盖)

2. 使用技巧:

  • 字符串到数字转换: istringstream 是将字符串转换为数字或其他类型(如 int, double)的常见且安全的方法。

    cpp 复制代码
    std::string num_str = "123";
    int number;
    std::istringstream iss(num_str);
    if (iss >> number) {
        // 转换成功,number = 123
    } else {
        // 转换失败 (例如 num_str 是 "abc")
    }
  • 数字到字符串转换: ostringstream 是将数字或其他类型格式化为字符串的灵活方法。

    cpp 复制代码
    double pi = 3.14159;
    std::ostringstream oss;
    oss << std::fixed << std::setprecision(2) << "Pi is approximately: " << pi;
    std::string pi_str = oss.str(); // pi_str = "Pi is approximately: 3.14"

    注意:C++11 提供了 std::to_string() 用于简单的数字到字符串转换,C++17 提供了 <charconv> 用于更高效的转换,但 ostringstream 在需要复杂格式化时仍然非常有用。

  • 字符串分割: istringstream 结合 std::getline 是按任意字符分割字符串的常用方法。

    cpp 复制代码
    std::string csv_line = "Value1,Value2,Value3";
    std::istringstream iss(csv_line);
    std::string token;
    while (std::getline(iss, token, ',')) { // 以逗号为分隔符
        std::cout << "Token: " << token << std::endl;
    }
  • 重用或清空:

    • ostringstream 对象一旦使用 .str() 获取了字符串,底层缓冲区的内容不会自动清除。如果你想重新使用同一个 ostringstream 对象构建新的字符串,有几种方法:
      • 调用 oss.str(""):将内部字符串设置为空,但保留格式化状态。
      • 创建一个新的 ostringstream 对象:这是最干净的方法,也重置了所有的格式化标志。
    • istringstream 对象读取完毕后,通常需要重新用新的字符串初始化或者创建一个新的对象来处理新的输入。
  • 效率考虑: 对于简单的数字与字符串转换,C++11 的 std::to_string 或 C++17 的 <charconv> 通常比字符串流更高效。然而,对于需要自定义格式化或解析包含多种类型/分隔符的复杂字符串时,字符串流提供了更灵活和易读的解决方案。

好的,我们来加入详细的代码示例,更具体地展示标准流对象 (<iostream>) 和字符串流类 (<sstream>) 的用法和使用技巧。


一、 标准流对象 (<iostream>) - 详细示例

这些对象用于标准的输入/输出,与控制台交互。

核心: std::cin, std::cout, std::cerr, std::clog

cpp 复制代码
#include <iostream> // 包含了 cin, cout, cerr, clog
#include <string>   // 用于 std::string
#include <limits>   // 用于 std::numeric_limits
#include <iomanip>  // 用于格式化操纵符

int main() {
    // --- 1. 基本输入/输出 (>>, <<) ---
    std::cout << "--- Basic I/O ---" << std::endl; // 使用 cout 输出字符串和 endl (换行并刷新)

    int age;
    std::string name;

    // 使用 >> 读取数据 (自动跳过空白)
    std::cout << "Enter your first name and age (e.g., Alice 30): ";
    // 尝试读取名字和年龄
    if (std::cin >> name >> age) { // 检查流状态:读取是否成功
        // 如果成功,使用读取到的变量
        std::cout << "Hello, " << name << "! You are " << age << " years old." << std::endl;
    } else {
        // 如果失败 (例如输入了非数字给 age)
        std::cerr << "Error: Invalid input format!" << std::endl; // 使用 cerr 输出错误信息 (无缓冲)

        // 使用技巧:清理错误标志并忽略当前行的剩余输入
        std::cin.clear(); // 清除流的错误标志
        // 忽略直到换行符(或最大可能的字符数)的所有内容,丢弃错误输入
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    }
    std::cout << std::endl; // 输出空行


    // --- 2. 读取整行 (getline) ---
    std::cout << "--- Reading Full Line ---" << std::endl;

    std::string full_line;
    std::cout << "Enter a sentence: ";

    // 使用技巧:处理 >> 留下的换行符
    // 如果上面的 >> 操作成功,它会留下一个换行符在缓冲区,
    // 紧接着的 getline 会立刻读到这个换行符并得到一个空行。
    // 所以这里需要先忽略掉那个遗留的换行符。
    // 注意:如果上面的 >> 失败,ignore 调用在 clear() 之后,也无害。
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

    // 使用 getline 读取整行,包括空格
    if (std::getline(std::cin, full_line)) { // 检查流状态:读取是否成功
        std::cout << "You entered line: \"" << full_line << "\"" << std::endl;
    } else {
         std::cerr << "Error reading sentence." << std::endl;
          // getline 失败通常是遇到 EOF 或其他严重的流错误
    }
     std::cout << std::endl;


    // --- 3. 输出到 cerr vs clog ---
    std::cout << "--- cerr vs clog ---" << std::endl;
    // cerr 无缓冲,信息会立即显示
    std::cerr << "This error message should appear immediately (cerr)." << std::endl;
    // clog 有缓冲,信息可能会延迟显示,直到缓冲区满或程序结束
    std::clog << "This log message might be buffered (clog)." << std::endl;
     std::cout << "Check if cerr message appeared before clog message." << std::endl;
     std::cout << std::endl;


    // --- 4. 输出格式化 (<iomanip>) ---
    std::cout << "--- Output Formatting ---" << std::endl;

    double price = 123.4567;
    int count = 7;

    // 设置浮点数精度和固定小数点表示
    std::cout << std::fixed << std::setprecision(2); // 设置小数点后两位,固定格式
    std::cout << "Price: $" << price << std::endl; // 输出: Price: $123.46

    // 设置输出宽度和填充字符
    std::cout << "Count: ";
    std::cout << std::setw(5) << std::setfill('0') << count << std::endl; // 输出: Count: 00007 (总宽度5,用'0'填充左边)

    // 重置填充字符
    std::cout << std::setfill(' '); // 恢复默认填充字符为空格
    std::cout << "Default fill: " << std::setw(5) << count << std::endl; // 输出: Default fill:     7

    // 输出布尔值 (默认是 0 或 1)
    bool flag = true;
    std::cout << "Bool (default): " << flag << std::endl; // 输出: Bool (default): 1
    std::cout << std::boolalpha << "Bool (alpha): " << flag << std::endl; // 输出: Bool (alpha): true
    std::cout << std::noboolalpha; // 恢复默认

    std::cout << std::endl;

    return 0;
}

std::numeric_limits<std::streamsize>::max() 解释: 这是一个来自 <limits> 头文件的标准常量,表示 streamsize 类型(流用来表示大小或偏移量的类型)的最大可能值。在 ignore() 中用作忽略字符数的上限,配合一个停止字符(如 '\n'),意味着"忽略直到遇到换行符为止的所有字符,但最多不超过 streamsize 的最大值"。这是一种安全的清空缓冲区直到指定字符的方式。


二、 字符串流类 (<sstream>) - 详细示例

这些类用于在内存中的 std::string 和其他数据类型之间进行转换和处理。

核心: std::istringstream, std::ostringstream, std::stringstream

cpp 复制代码
#include <iostream>
#include <string>
#include <sstream> // 用于字符串流类
#include <iomanip> // 用于格式化操纵符 (如 std::fixed, std::setprecision)
#include <vector>  // 用于存储分割后的部分

int main() {
    // --- 1. istringstream - 从字符串读取/解析 ---
    std::cout << "--- istringstream (Parsing) ---" << std::endl;

    std::string data = "Item: Laptop Price: 1200.50 Quantity: 3 InStock: true";
    std::istringstream iss(data); // 用字符串初始化一个输入字符串流

    std::string item_label, item_name, price_label, stock_label;
    double price;
    std::string quantity_label; // 注意这里用string读取,因为后面是数字
    int quantity;
    bool in_stock; // 注意:>> 可以直接读取 boolalpha 格式的 true/false

    // 使用 >> 从字符串流中读取数据
    // istringstream 的 >> 行为与 cin >> 相同,会跳过空白
    // 检查流状态是重要的,即使是从字符串读取
    if (iss >> item_label >> item_name
            >> price_label >> price
            >> quantity_label >> quantity) // 链式读取并检查是否成功
    {
         // 为了读取 bool,我们需要设置流的格式化标志
         iss >> std::boolalpha >> stock_label >> in_stock; // 继续读取,并检查 boolalpha

         // 再次检查整体读取是否成功 (如果bool读取失败,iss.fail()会返回true)
         if (iss.good() || iss.eof()) { // good() 表示没有错误标志,eof() 表示已读到末尾
            std::cout << "Successfully parsed data:" << std::endl;
            std::cout << item_label << " " << item_name << std::endl;
            std::cout << price_label << " " << price << std::endl;
            std::cout << quantity_label << " " << quantity << std::endl;
            std::cout << stock_label << " " << std::boolalpha << in_stock << std::endl; // 打印 bool 时也用 boolalpha
         } else {
             std::cerr << "Partial parsing successful, but remaining data read failed." << std::endl;
         }

    } else {
        std::cerr << "Error parsing the initial data." << std::endl;
        // 可以检查 iss.fail() 或 iss.bad() 来确定失败原因
    }
    std::cout << std::endl;


    // 使用技巧:字符串分割 (结合 getline)
    std::cout << "--- String Splitting (istringstream + getline) ---" << std::endl;
    std::string csv_line = "apple,banana,orange,grape";
    std::istringstream csv_iss(csv_line); // 创建一个新的 istringstream
    std::string token;
    std::vector<std::string> tokens;

    // 使用 getline 结合指定分隔符 ',' 来从字符串流中读取
    while (std::getline(csv_iss, token, ',')) { // getline 返回流本身,在 while 中会自动转换为 bool
        tokens.push_back(token);
    }

    std::cout << "Original string: \"" << csv_line << "\"" << std::endl;
    std::cout << "Tokens found:" << std::endl;
    for (const auto& t : tokens) {
        std::cout << "- " << t << std::endl;
    }
    std::cout << std::endl;


    // --- 2. ostringstream - 向字符串构建/写入 ---
    std::cout << "--- ostringstream (Building) ---" << std::endl;

    std::ostringstream oss; // 创建一个输出字符串流对象

    std::string product = "Gadget";
    int quantity_ordered = 10;
    double unit_cost = 25.99;

    // 使用 << 向字符串流写入数据,可以像 cout 一样使用格式化
    oss << "Order Details:\n";
    oss << "Product: " << product << "\n";
    oss << "Quantity: " << quantity_ordered << "\n";
    oss << std::fixed << std::setprecision(2); // 设置浮点数格式
    oss << "Unit Cost: $" << unit_cost << "\n";
    oss << "Total Cost: $" << (quantity_ordered * unit_cost);

    // 获取构建好的字符串
    std::string order_summary = oss.str();

    std::cout << "Built String:\n" << order_summary << std::endl;
    std::cout << std::endl;

    // 使用技巧:重用 ostringstream (清空缓冲区)
    std::cout << "--- Reusing ostringstream ---" << std::endl;
    // oss 已经用过了,现在清空它来构建一个新的字符串
    oss.str(""); // 设置内部字符串为空,重置流的位置到开始
    oss.clear(); // 清除所有错误标志 (如果之前有错误的话)

    // 现在 oss 是空的,可以重新向它写入
    oss << "This is a new message: " << 456;
    std::string new_message = oss.str();
    std::cout << "Reused and built: " << new_message << std::endl;
    std::cout << std::endl;


    // --- 3. stringstream - 读写同一个字符串 (较少用,但示例) ---
    std::cout << "--- stringstream (Read/Write) ---" << std::endl;

    std::stringstream ss("123 Some Text 456");
    int num1;
    std::string text_part;
    int num2;

    // 从字符串流中读取
    ss >> num1 >> text_part >> num2;
    std::cout << "Read num1: " << num1 << ", text_part: '" << text_part << "', num2: " << num2 << std::endl;
     // 当前流的位置在 456 之后

    // 清空流状态,并设置新的内部字符串(这其实更像 istringstream/ostringstream 的组合用法)
    ss.str("Different Data"); // 改变内部字符串
    ss.clear(); // 清除 EOF 标志或其他错误标志

    std::string new_data;
    ss >> new_data;
    std::cout << "After changing string and clearing state, read: '" << new_data << "'" << std::endl;


    return 0;
}
相关推荐
我真的不会C33 分钟前
QT中的事件及其属性
开发语言·qt
Ethon_王41 分钟前
走进Qt--工程文件解析与构建系统
c++·qt
mit6.8241 小时前
[Lc_week] 447 | 155 | Q1 | hash | pair {}调用
算法·leetcode·哈希算法·散列表
2501_906314321 小时前
优化无头浏览器流量:使用Puppeteer进行高效数据抓取的成本降低策略
开发语言·数据结构·数据仓库
工藤新一¹2 小时前
C++/SDL进阶游戏开发 —— 双人塔防游戏(代号:村庄保卫战 13)
c++·游戏·游戏引擎·毕业设计·sdl·c++游戏开发·渲染库
jerry6092 小时前
优先队列、堆笔记(算法第四版)
java·笔记·算法
让我们一起加油好吗2 小时前
【C++】类和对象(上)
开发语言·c++·visualstudio·面向对象
好想有猫猫2 小时前
【Redis】服务端高并发分布式结构演进之路
数据库·c++·redis·分布式·缓存
不是杠杠2 小时前
驼峰命名法(Camel Case)与匈牙利命名法(Hungarian Notation)详解
c++
勤劳的牛马2 小时前
📚 小白学算法 | 每日一题 | 算法实战:加1!
算法