💬 :如果你在阅读过程中有任何疑问或想要进一步探讨的内容,欢迎在评论区畅所欲言!我们一起学习、共同成长~!
👍 :如果你觉得这篇文章还不错,不妨顺手点个赞、加入收藏,并分享给更多的朋友噢~!
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 个全局流对象:cin
、cout
、cerr
、clog
。
cin
:标准输入流(键盘输入到程序),属于istream
类。cout
:标准输出流(程序输出到显示器),属于ostream
类。cerr
:标准错误输出流(无缓冲,直接输出)。clog
:标准日志输出流(有缓冲)。
cout
、cerr
、clog
是ostream
类的三个不同对象,应用场景不同,但基本功能类似。使用时必须包含头文件<iostream>
,并引入std
标准命名空间。
3.1.2 关键细节
3.1.2.1 缓冲机制
cin
为缓冲流,输入数据先存入缓冲区,提取时从缓冲区读取。- 输入错误会设置流状态字
state
,但程序继续执行。
3.1.2.2 数据类型匹配
- 输入类型需与提取类型一致,否则出错(如给
int
变量输入字符将出错)。
3.1.2.3 分隔符处理
- 空格和回车可作为数据分隔符,字符型和字符串无法读取空格或回车。
3.1.2.4 内置类型支持
- 标准库已重载
<<
和>>
运算符,可直接输入输出内置类型(如int
、double
)。
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.直接构造并打开文件:
| |
ofstream | 只写入文件 | 1. 直接构造并打开文件(覆盖写入):
| 类似 ifstream |
fstream | 读写文件 | fstream 对象名("文件名", 模式); (模式必须包含 ios_base::in | ios_base::out ) | |
注意: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 注意事项
- 状态与缓冲区管理
clear()
:重置流状态(如badbit
),但不清空底层字符串。str("")
:清空底层string
对象,避免多次转换时数据累积。
- 安全性
- 使用
string
代替字符数组,避免缓冲区溢出。 - 自动类型推导,无需手动格式化,减少错误风险。
- 使用