核心概念回顾:
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)
是处理潜在输入错误(如输入了非数字字符给整型变量)的标准方式。- 如果输入失败,流会进入失败状态 (
failbit
或badbit
置位),后续的输入操作将不会执行,直到你调用std::cin.clear()
清除错误标志。 std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
是一个常用的技巧,用于在输入失败或读取部分行后,忽略当前行的剩余内容,以便开始处理下一行输入。需要包含<limits>
头文件。
- 如果输入失败,流会进入失败状态 (
- 混合使用
>>
和getline
: 这是常见的陷阱。>>
操作会读取数据,但会把结尾的换行符留在输入缓冲区里。紧接着调用getline
时,它会立即读到这个遗留的换行符,导致读取到一个空字符串。解决办法是在>>
之后、getline
之前,调用std::cin.ignore()
来清理掉遗留的换行符。 - 选择
cerr
vsclog
:std::cerr
是无缓冲的,错误信息会立即显示,适合报告需要用户立即看到的关键错误。std::clog
是有缓冲的,输出可能会延迟,适合记录日志信息,性能可能略好。
- 刷新输出: 对于
std::cout
和std::clog
(缓冲流),数据不是一写进去就立即显示。你可以使用std::endl
或std::flush
来强制刷新缓冲区。对于需要确保信息立即出现(例如在程序崩溃前),使用std::endl
或std::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
)的常见且安全的方法。cppstd::string num_str = "123"; int number; std::istringstream iss(num_str); if (iss >> number) { // 转换成功,number = 123 } else { // 转换失败 (例如 num_str 是 "abc") }
-
数字到字符串转换:
ostringstream
是将数字或其他类型格式化为字符串的灵活方法。cppdouble 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
是按任意字符分割字符串的常用方法。cppstd::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;
}