当前位置: 首页 > news >正文

C++ 的 IO 流

💬 :如果你在阅读过程中有任何疑问或想要进一步探讨的内容,欢迎在评论区畅所欲言!我们一起学习、共同成长~!

👍 :如果你觉得这篇文章还不错,不妨顺手点个赞、加入收藏,并分享给更多的朋友噢~!


1. C 语言的输入输出

1.1 常用输入输出函数

C 语言中最常用的输入输出方式是 scanf() 与 printf() 

  • scanf():从标准输入流(通常是键盘)读取格式化数据,按照指定的格式将输入数据存储到对应的变量中。
  • printf():将格式化的数据输出到标准输出流(通常是显示器)。使用时需注意宽度输出和精度输出控制。

1.2 输入输出缓冲区

C 语言借助相应的缓冲区来进行输入与输出,输入输出缓冲区作用如下:

  • 屏蔽低级 I/O 的实现:低级 I/O 的实现依赖操作系统本身内核,屏蔽这部分差异可使程序更具可移植性。
  • 实现 “行” 读取行为:计算机本身没有 “行” 的概念,通过缓冲区可定义 “行”,并解析其内容返回 “行”。


2. 流是什么

“流” 是对有序连续且具有方向性的数据的抽象描述。

C++ 流指信息从外部输入设备(如键盘)向计算机内部(如内存)输入,以及从内存向外部输出设备(显示器)输出的过程。

C++ 流的特性:

  • 有序连续:数据按顺序流动。
  • 有方向性:分为输入流和输出流。
  • 基于 I/O 标准类库:通过类对象实现输入输出操作。


3. C++  IO 流

C++ 中构建了一个体系庞大的类库,在这个类库的继承体系里,ios类是基类,其他所有相关类均直接或间接派生自ios类。

3.1 C++ 标准 IO 流

3.1.1 全局流对象

C++ 标准库提供了 4 个全局流对象:cincoutcerrclog

  • cin:标准输入流(键盘输入到程序),属于istream类。
  • cout:标准输出流(程序输出到显示器),属于ostream类。
  • cerr:标准错误输出流(无缓冲,直接输出)。
  • clog:标准日志输出流(有缓冲)。

coutcerrclogostream类的三个不同对象,应用场景不同,但基本功能类似。使用时必须包含头文件<iostream>,并引入std标准命名空间。

3.1.2 关键细节

3.1.2.1 缓冲机制
  • cin为缓冲流,输入数据先存入缓冲区,提取时从缓冲区读取。
  • 输入错误会设置流状态字state,但程序继续执行。
3.1.2.2 数据类型匹配
  • 输入类型需与提取类型一致,否则出错(如给int变量输入字符将出错)。
3.1.2.3 分隔符处理
  • 空格和回车可作为数据分隔符,字符型和字符串无法读取空格或回车。
3.1.2.4 内置类型支持
  • 标准库已重载<<>>运算符,可直接输入输出内置类型(如intdouble)。
3.1.2.5 自定义类型支持
  • 需重载<<>>运算符才能使用cin/cout
3.1.2.6 在线 OJ 输入输出

<1> IO类型的算法,一般都要循环输入:

(1)单个元素循环输入

适用场景:读取多组单一类型数据(如多组整数)。

​
int num;
while (cin >> num) 
{  // 输入整数,遇EOF结束cout << "输入的数:" << num << endl;
}

(2)多个元素循环输入

适用场景:读取每行包含多个数据的输入(如每行输入 “姓名 年龄 分数”)。

string name;
int age;
double score;
while (cin >> name >> age >> score) 
{  // 按顺序读取多个元素cout << name << " " << age << " " << score << endl;
}

(3)非整行字符串输入(不含空格)

适用场景:读取以空格 / 换行分隔的单词(如多个独立字符串)。

string word;
while (cin >> word) 
{  // 读取单个单词,遇空格/换行/EOF结束cout << "单词:" << word << endl;
}

(4)整行字符串输入(含空格)

适用场景:读取完整句子或段落(如 “Hello, world!”)。

string line;
while (getline(cin, line)) 
{  // 读取整行,包括空格,遇EOF或空行结束if (line.empty()) break;  // 可选:跳过空行cout << "整行内容:" << line << endl;
}

<2> 输出必须与题目要求完全一致,包括空格、换行符、标点符号等,多一个或少一个字符均可能导致错误

<3> Windows 系统的 VS 系列编译器中,通过输入 Ctrl + Z  再按下 Enter ,就可发送 EOF(文件结束符) 信号,快捷终止输入循环。

3.1.2.7 流对象的逻辑判断
  • istream 对象能直接用于条件判断。若输入成功,则继续执行相关操作;若输入失败,则停止执行相关操作。
  • 自定义类型可通过重载 operator bool() 函数来添加额外的逻辑判断。这些额外逻辑判断通常用于设置业务层面的结束条件。

istream 对象可作为逻辑条件值,是因为该对象会调用 operator bool 函数。

当流提取成功时,operator bool返回true;当遇到文件结束符(EOF,如键盘输入Ctrl+Z)、提取类型不匹配(如给int变量输入字符)或流被关闭时,operator bool返回false,循环终止。

示例:

#include <iostream>
#include <string>
using namespace std;class Date 
{// 声明友元函数以访问私有成员friend ostream& operator<<(ostream& out, const Date& d);friend istream& operator>>(istream& in, Date& d);public:Date(int year = 1, int month = 1, int day = 1): _year(year), _month(month), _day(day) {}// 类型转换运算符:定义Date对象的逻辑判断条件explicit operator bool() const {// 假设输入_year为0时,返回falsereturn _year != 0;}private:int _year;int _month;int _day;
};// 重载流提取运算符:从输入流读取Date对象
istream& operator>>(istream& in, Date& d) 
{in >> d._year >> d._month >> d._day; return in; // 返回输入流对象,支持链式调用(如cin >> d1 >> d2)
}// 重载流插入运算符:向输出流写入Date对象
ostream& operator<<(ostream& out, const Date& d) 
{out << d._year << "-" << d._month << "-" << d._day; return out; // 返回输出流对象,支持链式调用(如cout << d << endl)
}int main() 
{// 输出内置类型(自动调用标准库重载的<<运算符)int i = 1;double j = 2.2;cout << "内置类型输出:" << endl;cout << "整数i = " << i << endl;cout << "浮点数j = " << j << endl;// 输出自定义类型Date(调用重载的<<运算符)Date d(2022, 4, 10); cout << "\n初始化的日期:" << d << endl;// 循环输入Date对象,直到输入年份为0时结束cout << "\n请输入日期(格式:年 月 日,输入0 0 0结束):" << endl;while (cin >> d) { // 用operator>>返回的istream对象调用operator bool// 输入成功后,检查Date对象的逻辑状态(_year是否为0)if (!d) { cout << "检测到结束标志(年份为0),退出程序。" << endl;break;}cout << "输入的日期为:" << d << endl; cout << "请继续输入(或输入0 0 0结束):" << endl;}return 0;
}

3.2 C++ 文件 IO 流

根据数据的组织形式,数据文件可分为二进制文件和文本文件。

  • 数据在内存中以二进制形式存储,若不加转换输出到外存文件中,就是二进制文件;
  • 若要求在外存上以 ASCII 码形式存储,则需在存储前转换,以 ASCII 字符形式存储的文件就是文本文件。

采用文件流对象操作文件的一般步骤:

  • (1)定义文件流对象
类名用途定义语法示例
ifstream只读取文件

1.直接构造并打开文件:

ifstream 对象名("文件名",模式);
2. 默认构造后打开文件:
ifstream 对象名;
对象名.open("文件名", 模式);

若未显式指定模式将默认ios_base::in

std::ifstream fin("data.txt");
std::ifstream fin;
fin.open("data.bin", std::ios_base::binary);
// 或
std::ifstream fin;
fin.open("data.bin", std::ios_base::in | std::ios_base::binary);

ofstream只写入文件

1. 直接构造并打开文件(覆盖写入):
ofstream 对象名("文件名",模式);
2.默认构造后打开文件:
ofstream 对象名;
对象名.open("文件名", 模式);

若未显式指定模式将默认ios_base::out

类似 ifstream
fstream读写文件fstream 对象名("文件名", 模式);
模式必须包含 ios_base::in | ios_base::out )
std::fstream file("data.txt", std::ios_base::in | std::ios_base::out);
// 追加读写:
std::fstream file("data.txt", std::ios_base::in | std::ios_base::out | std::ios_base::app);

注意ifstream / ofstream / fstream 均需包含头文件 <fstream> ;ifstream 、ofstream 、fstream 、ios_base 要展开命名空间或使用 std:: 前缀。

  • (2)打开文件
    • 成员函数:open(const char* filename, ios_base::mode)
    • 常用打开模式:
      • ios_base::in:输入模式(读文件)。
      • ios_base::out:输出模式(写文件,覆盖原有内容)。
      • ios_base::app:追加模式(写文件,内容追加到末尾)。
      • ios_base::binary:二进制模式(默认为文本模式)。
  • (3)读写文件
    • 文本读写:使用<<>>运算符(需重载自定义类型运算符)。
    • 二进制读写:使用write()和read()成员函数(按字节操作)。
  • (4)关闭文件:调用close()成员函数。

3.2.1 模拟服务器配置信息的读写

#include <iostream>       
#include <fstream>        
#include <string>         
using namespace std;      class Date 
{
public:Date(int year = 1, int month = 1, int day = 1): _year(year), _month(month), _day(day) {}friend ostream& operator<<(ostream& out, const Date& d);friend istream& operator>>(istream& in, Date& d);private:int _year;   int _month;  int _day;    
};ostream& operator<<(ostream& out, const Date& d) 
{out << d._year << "-" << d._month << "-" << d._day;return out;
}istream& operator>>(istream& in, Date& d) 
{char separator1, separator2; // 用于读取日期中的分隔符(如'-')in >> d._year >> separator1 >> d._month >> separator2 >> d._day;return in;
}// 服务器信息结构体
struct ServerInfo 
{char _address[32];  // 地址(字符数组)int _port;          // 端口号Date _date;         // 日期(已重载IO运算符)
};// 配置管理类
class ConfigManager 
{
public:// 构造函数:初始化文件名explicit ConfigManager(const char* filename) : _filename(filename) {}// 二进制写入文件void WriteBin(const ServerInfo& info) {ofstream ofs(_filename, ios_base::out | ios_base::binary); if (!ofs.is_open()) { // 检查文件是否打开成功cerr << "Error: Failed to open file for writing (binary)." << endl;return;}// 写入结构体数据(需保证结构体无虚函数、无动态成员,否则需自定义序列化逻辑)ofs.write(reinterpret_cast<const char*>(&info), sizeof(info));ofs.close();}// 二进制读取文件void ReadBin(ServerInfo& info) {ifstream ifs(_filename, ios_base::in | ios_base::binary);if (!ifs.is_open()) { cerr << "Error: Failed to open file for reading (binary)." << endl;return;}// 读取结构体数据ifs.read(reinterpret_cast<char*>(&info), sizeof(info));ifs.close();}// 文本写入文件void WriteText(const ServerInfo& info) {ofstream ofs(_filename);if (!ofs.is_open()) { cerr << "Error: Failed to open file for writing (text)." << endl;return;}// 使用流插入运算符写入数据(需保证Date类已重载<<)ofs << info._address << " " << info._port << " " << info._date << endl;ofs.close();}// 文本读取文件void ReadText(ServerInfo& info) {ifstream ifs(_filename);if (!ifs.is_open()) { cerr << "Error: Failed to open file for reading (text)." << endl;return;}// 使用流提取运算符读取数据(需保证Date类已重载>>)ifs >> info._address >> info._port >> info._date;ifs.close();}private:string _filename; // 配置文件名
};int main() 
{// 初始化服务器信息ServerInfo winfo = {"192.0.0.1",    // 地址80,             // 端口号{2022, 4, 10}   // 日期(使用Date构造函数初始化)};// 创建配置管理器对象(二进制和文本文件)ConfigManager cf_bin("test.bin");ConfigManager cf_text("test.txt");// ---------------------- 二进制操作 ----------------------// 写入二进制文件cf_bin.WriteBin(winfo);cout << "二进制文件写入完成。" << endl;// 读取二进制文件ServerInfo rbinfo;cf_bin.ReadBin(rbinfo);cout << "二进制读取结果:" << endl;cout << "地址:" << rbinfo._address << endl;cout << "端口:" << rbinfo._port << endl;cout << "日期:" << rbinfo._date << endl;cout << endl;// ---------------------- 文本操作 ----------------------// 写入文本文件cf_text.WriteText(winfo);cout << "文本文件写入完成。" << endl;// 读取文本文件ServerInfo rtinfo;cf_text.ReadText(rtinfo);cout << "文本读取结果:" << endl;cout << "地址:" << rtinfo._address << endl;cout << "端口:" << rtinfo._port << endl;cout << "日期:" << rtinfo._date << endl;return 0;
}


4. 简单介绍 stringstream

传统C语言方法(如 sprintf / itoa )在进行类型转换与字符串操作时,存在缓冲区溢出风险且需手动管理内存空间,尤其在处理结构体数据的序列化与反序列化时不够安全便捷。

stringstream 通过流式操作实现自动类型推导,并利用安全的string缓冲区管理,有效简化了类型转换与字符串拼接过程,规避了上述风险,更适用于序列化场景。

4.1 头文件与类定义

  • 头文件:#include <sstream>
  • 相关类:
    • istringstream:从字符串读取数据(输入流)。
    • ostringstream:向字符串写入数据(输出流)。
    • stringstream:双向操作(读写字符串)。

4.2 核心功能

4.2.1 数值类型转字符串

  • 避免itoa()sprintf()的缓冲区溢出问题,自动推导类型。

示例代码:

#include <iostream>       
#include <sstream>        
#include <string>         // 包含string类型头文件using namespace std;      int main()
{int a = 12345678;     string sa;            // 定义用于存储转换结果的string对象stringstream s;       // 定义stringstream对象,用于数据转换// 第一次转换:将整数a转换为字符串s << a;               // 向stringstream中插入int类型数据(自动格式化)s >> sa;              // 从stringstream中提取数据到string对象sacout << "整数转字符串结果:" << sa << endl;  // ---------------------- 关键操作:清空流状态和底层缓冲区 ----------------------s.clear();            // 重置流状态标志(清除可能的badbit状态)s.str("");            // 清空stringstream底层维护的string对象,避免残留数据影响下次转换// ----------------------------------------------------------------------------// 第二次转换:将双精度浮点数d转换为字符串double d = 12.34;     s << d;               // 向stringstream中插入double类型数据(自动格式化)s >> sa;              // 从stringstream中提取数据到string对象sacout << "浮点数转字符串结果:" << sa << endl;  return 0;
}

4.2.2 字符串拼接

  • 方便合并多个字符串或变量。

示例代码:

#include <iostream>      
#include <sstream>        
#include <string>         using namespace std;      int main() 
{// 创建stringstream对象用于字符串操作stringstream sstream;// 向流中插入多个字符串进行拼接sstream << "Hello" << " " << "World," << " 你好!";// 从stringstream中提取拼接后的完整字符串string result = sstream.str();cout << "拼接结果:" << result << endl;  return 0;
}

4.2.3 序列化与反序列化结构数据

  • 将结构体数据转为字符串(如网络传输),或从字符串解析回结构体。

示例代码:

#include <iostream>
#include <sstream>
#include <string>
using namespace std;class Date 
{
public:Date(int year = 1, int month = 1, int day = 1): _year(year), _month(month), _day(day) {}friend istream& operator>>(istream& in, Date& d);friend ostream& operator<<(ostream& out, const Date& d);private:int _year;int _month;int _day;
};istream& operator>>(istream& in, Date& d) 
{char separator1, separator2; // 用于处理日期格式中的分隔符(如YYYY-MM-DD)in >> d._year >> separator1 >> d._month >> separator2 >> d._day;return in;
}ostream& operator<<(ostream& out, const Date& d) 
{out << d._year << "-" << d._month << "-" << d._day; return out;
}struct ChatInfo 
{string _name;int _id;Date _date; // 假设已重载<<和>>string _msg;
};int main() 
{// 序列化:结构体转字符串ChatInfo winfo = {"张三",135246,{2022, 4, 10}, // 使用Date构造函数初始化日期"晚上一起看电影吧"};ostringstream oss;// 按顺序输出结构体成员,用空格分隔(需注意字符串中的空格会导致反序列化问题)oss << winfo._name << " "<< winfo._id << " "<< winfo._date << " "<< winfo._msg; // 假设_msg中不含空格,否则需特殊处理(如转义或使用其他分隔符)string str = oss.str();cout << "序列化字符串:" << str << endl;// 反序列化:字符串转结构体ChatInfo rInfo;istringstream iss(str);// 按顺序读取数据,与序列化顺序严格一致iss >> rInfo._name >> rInfo._id >> rInfo._date >> rInfo._msg;// 检查反序列化是否成功(处理可能的读取错误)if (iss.fail()) {cerr << "反序列化失败:输入格式错误" << endl;return 1;}cout << "\n反序列化结果:" << endl;cout << "姓名:" << rInfo._name << "(" << rInfo._id << ")" << endl;cout << "时间:" << rInfo._date << endl;cout << "消息:" << rInfo._msg << endl;return 0;
}

4.3 注意事项

  1. 状态与缓冲区管理
    • clear():重置流状态(如badbit),但不清空底层字符串。
    • str(""):清空底层string对象,避免多次转换时数据累积。
  2. 安全性
    • 使用string代替字符数组,避免缓冲区溢出。
    • 自动类型推导,无需手动格式化,减少错误风险。

相关文章:

  • DAPP(去中心化应用程序)开发全解析:构建去中心化应用的流程
  • SVT-AV1编码器初始化函数
  • 力扣刷题Day 25:反转链表(206)
  • 【android bluetooth 协议分析 11】【AVDTP详解 1】【宏观感受一下avdtp是个啥东东】
  • 入住刚装修好的新房,房间隔音太差应该怎么办?
  • ERP系统多少钱一套?| 上海达策TECH-SONIC
  • 三维几何变换
  • 修改element UI 分页组件样式(解决样式不生效问题)
  • Java多线程编程初阶指南
  • 【实战】oninput 文本框输入实时查询防抖机制实现
  • 数字IC低功耗设计——基础概念和低功耗设计方法
  • 出现了锁等待或死锁现象怎么办?乐观锁?分布式锁了解一下?
  • 前端笔记-Vue3(中)
  • 输入框仅支持英文、特殊符号、全角自动转半角 vue3
  • Sqlserver安全篇之_Sqlcmd命令使用windows域账号认证sqlserver遇到问题如何处理的案例
  • JVM考古现场(二十四):逆熵者·时间晶体的永恒之战
  • 乐视系列玩机---乐视1 x600系列线刷救砖以及刷写第三方twrp 卡刷第三方固件步骤解析
  • 【AI News | 20250422】每日AI进展
  • Java 静态内部类面试题与高质量答案合集
  • 华为仓颉编程语言基础概述
  • 全国人大常委会调研组在宁波调研,张庆伟带队钟山易炼红参加
  • 人民网评“我愿意跟他挨着”热搜第一:充满温暖力量的七个字
  • 上海与丰田汽车签署战略合作协议,雷克萨斯纯电动汽车项目落子金山
  • 经济日报:锚定重点领域和关键环节,上海浦东谋划高水平对外开放
  • 著名世界语教育家、翻译家魏以达逝世
  • 同济研究生开发AI二维码拿下大奖,新一代00开发者掀起AI创业潮