解密C++ I/O流的全新边界:高效操作与未来科技的完美融合
全面讲解了流的基本概念,C语言和C++的输入输出方式,详细描述了流的概念、标准流、文件IO流以及字符串(stringstream)的用法
C++ IO流详解:文件读写、字符串流
🌏个人博客主页:个人主页
1. C语言的输入与输出
C语言中我们用到的最频繁的输入输出方式就是scanf ()与printf()。 scanf(): 从标准输入设备(键
盘)读取数据,并将值存放在变量中。printf(): 将指定的文字/字符串输出到标准输出设备(屏幕)。
注意宽度输出和精度输出控制。C语言借助了相应的缓冲区来进行输入与输出。如下图所示:
这里只需要记住两个点就可以了。
输出:把内存中的数据写到设备(文件)当中。
输入:把设备(文件)中的数据读到内存当中。
2. 流是什么
流简单来说指的是数据从一个地方流向另一个地方。
C++流是指信息从外部输入设备(如键盘)向计算机内部(如内存)输入和从内存向外部输出设备(显示器)输出的过程。这种输入输出的过程被形象的比喻为“流”
3. C++IO流
为了实现这种流动,C++定义了I/O标准类库,这些每个类都称为流/流类,用以完成某方面的功能
C++标准库提供了4个全局流对象cin、cout、cerr、clog,使用cout进行标准输出,即数据从内存流向控制台(显示器)。使用cin进行标准输入即数据通过键盘输入到程序中,同时C++标准库还提供了cerr用来进行标准错误的输出,以及clog进行日志的输出,从上图可以看出,cout、cerr、clog是ostream类的三个不同的对象,因此这三个对象现在基本没有区别,只是应用场景不同。
C++ IO 流总结
类名 | 继承自 | 描述 | 实例对象 |
---|---|---|---|
std::ios_base | - | 所有 I/O 流类的基类,提供通用的 I/O 操作和状态管理功能 | - |
std::ios | std::ios_base | 抽象类,用于处理格式化的输入输出 | - |
std::istream | std::ios | 输入流类,用于从输入源读取数据 | std::cin |
std::ostream | std::ios | 输出流类,用于向输出目标写入数据 | std::cout , std::cerr , std::clog |
std::iostream | std::istream , std::ostream | 双向流类,同时支持输入和输出 | - |
std::fstream | std::iostream | 文件流类,用于文件的输入输出 | - |
std::ifstream | std::istream | 文件输入流类,专门用于从文件读取数据 | - |
std::ofstream | std::ostream | 文件输出流类,专门用于向文件写入数据 | - |
std::stringstream | std::iostream | 字符串流类,用于在内存中构建和解析字符串 | - |
std::istringstream | std::istream | 字符串输入流类,用于从字符串读取数据 | - |
std::ostringstream | std::ostream | 字符串输出流类,用于向字符串写入数据 | - |
实例对象说明
std::cin
:标准输入流对象,通常连接到键盘输入。std::cout
:标准输出流对象,通常连接到屏幕输出。std::cerr
:标准错误流对象,通常也连接到屏幕输出,但不缓冲,立即显示。std::clog
:标准日志流对象,通常连接到屏幕输出,缓冲后显示。
istream类型对象转换为逻辑条件判断值
在线OJ中的输入和输出:
- 对于IO类型的算法,一般都需要循环输入:
- 输出:严格按照题目的要求进行,多一个少一个空格都不行。
- 连续输入时,vs系列编译器下在输入ctrl+Z时结束
int main()
{
string str;
//ctrl + c 信号强杀进程
//ctrl + z + 换行 流对象提前提取到流结束标识
//iostream& operator >> (iostream& is,string& str)
//实际调用的时 while (operator>>(cin,str))
while (cin >> str)
{
cout << str << endl;
}
return 0;
}
实际上我们看到使用while(cin>>i)去流中提取对象数据时,调用的是operator>>(cin,i),返回值是istream类型的对象,那么这里可以做逻辑条件值,源自于istream的对象又调用了operator bool,operator bool调用时如果接收流失败,或者有结束标志,则返回false。
我们也可以自己重载一个operator bool,当我们输入的年为0时,就结束输入。
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)
{}
operator bool()
{
if (_year == 0)
{
return false;
}
return true;
}
private:
int _year;
int _month;
int _day;
};
istream& operator >> (istream& in, Date& d)
{
in >> d._year >> d._month >> d._day;
return in;
}
ostream& operator << (ostream& out, const Date& d)
{
out << d._year << " " << d._month << " " << d._day;
return out;
}
int main()
{
Date d(2022, 4, 10);
cout << d;
while (d)
{
cin >> d;
cout << d << endl;
}
return 0;
}
📢小知识:在io需求比较高的地方,如部分大量输入的竞赛题中,加上以下3行代码,可以提高C++IO效率。
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
C++文件IO流
C++根据文件内容的数据格式分为二进制文件和文本文件。
模式 | 描述 |
---|---|
in | Input mode (输入模式)。以读取模式打开文件用于输入操作。 |
out | Output mode (输出模式)。以写入模式打开文件用于输出操作。如果文件已存在,内容将被截断。 |
app | Append mode (追加模式)。在每次写入时,数据将被追加到文件的末尾,而不是覆盖现有内容。 |
binary | Binary mode (二进制模式)。以二进制方式打开文件,不进行任何字符转换。这对于非文本文件(如图像或可执行文件)是必要的。 |
ate | At end mode (文件末尾模式)。打开文件时,文件指针定位到文件末尾。 |
trunc | Truncate mode (截断模式)。打开文件时,将文件的内容清空(如果文件已存在)。 |
二进制读写
二进制读写很简单,只要把内容以二进制的方式一个一个写到文件里面,在从文件里面以二进制的方式一个一个读出来就可以了。
#include <fstream>
#include <string>
// 定义服务器信息结构体
struct ServerInfo
{
char _address[32]; // 存储服务器地址,最大长度为31个字符加上一个终止符
int _port; // 存储服务器端口号
Date _date; // 存储日期,假设 Date 是一个已定义的日期类
};
// 定义配置管理器类
struct ConfigManager
{
// 构造函数,初始化文件名
ConfigManager(const char* filename)
: _filename(filename) // 初始化成员变量 _filename
{}
// 将 ServerInfo 结构体写入二进制文件
void WriteBin(const ServerInfo& info)
{
std::ofstream ofs(_filename, std::ofstream::out | std::ofstream::binary);
// 打开文件,以二进制模式写入
// std::ofstream::out 表示写入模式
// std::ofstream::binary 表示二进制模式
ofs.write((char*)&info, sizeof(info));
// 将整个 ServerInfo 结构体的数据写入文件
// (char*)&info 将结构体指针转换为 char* 类型
// sizeof(info) 获取结构体的大小
}
// 从二进制文件读取 ServerInfo 结构体
void ReadBin(ServerInfo& info)
{
std::ifstream ifs(_filename, std::ifstream::in | std::ifstream::binary);
// 打开文件,以二进制模式读取
// std::ifstream::in 表示读取模式
// std::ifstream::binary 表示二进制模式
ifs.read((char*)&info, sizeof(info));
// 从文件中读取数据到 ServerInfo 结构体
// (char*)&info 将结构体指针转换为 char* 类型
// sizeof(info) 获取结构体的大小
}
private:
std::string _filename; // 存储配置文件的名称
};
int main()
{
ServerInfo winfo = { "192.0.0.1",80,{2024,11,18} };
ConfigManager cm("mytest.txt");
cm.WriteBin(winfo);
}
注意:这里的 char _address[32];
不能换成string _address
,因为二进制读写只会把对象里面的内容全部读出来或者写进去。
如果一个进程就会出现浅拷贝的问题,因为二进制读写只会把对象里面的内容原封不动读给另一个对象,那么析构的时候就会析构两次,从而报错。
不同进程就会出现野指针的问题,因为之前的进程结束,栈帧空间就会销毁,string对象的_str指向的空间就是无效的内容。
文本读写
文本读写就是在外存上以ASCII码的形式存储,那么久需要在存储前转换成字符串,C++里面有流插入流提取就很方便。
#include <fstream>
#include <string>
// 定义一个结构体来存储服务器信息
struct ServerInfo {
std::string _address; // 服务器地址
int _port; // 服务器端口
std::string _date; // 日期
};
// ConfigManager 类用于管理配置文件的读写操作
struct ConfigManager {
// 构造函数,接受一个文件名作为参数
ConfigManager(const std::string& con)
: _filename(con) // 初始化文件名成员变量
{}
// 将 ServerInfo 结构体中的信息写入文件
void WriteText(const ServerInfo& info) {
std::ofstream ofs(_filename); // 打开文件,用于写入
if (!ofs.is_open()) {
throw std::runtime_error("Failed to open file for writing.");
}
// 写入服务器地址
ofs << info._address << std::endl;
// 写入服务器端口
ofs << info._port << std::endl;
// 写入日期
ofs << info._date << std::endl;
}
// 从文件中读取信息到 ServerInfo 结构体中
void ReadText(ServerInfo& info) {
std::ifstream ifs(_filename); // 打开文件,用于读取
if (!ifs.is_open()) {
throw std::runtime_error("Failed to open file for reading.");
}
// 读取服务器地址
ifs >> info._address;
// 读取服务器端口
ifs >> info._port;
// 读取日期
ifs >> info._date;
}
private:
std::string _filename; // 存储配置文件的路径和名称
};
int main()
{
ServerInfo winfo = { "192.0.0.1",80,{2024,11,18} };
ConfigManager cm("mytest.txt");
cm.WriteText(winfo);
}
4. stringstream的简单介绍
在C语言中,如果想要将一个整形变量的数据转化为字符串格式,如何去做?
- 使用itoa()函数
- 使用sprintf()函数
但是两个函数在转化时,都得需要先给出保存结果的空间,那空间要给多大呢,就不太好界定,而且转化格式不匹配时,可能还会得到错误的结果甚至程序崩溃。
std::stringstream
是 C++ 标准库中的一种流类型,它可以用于字符串的输入和输出操作。它的两个常见子类分别是 std::istringstream
和 std::ostringstream
,它们分别用于从字符串读取数据和向字符串写入数据。
子类 | 功能描述 |
---|---|
std::istringstream | 用于从字符串中提取数据。类似于输入流(std::istream ),但数据来源是字符串而不是文件或标准输入。 |
std::ostringstream | 用于向字符串写入数据。类似于输出流(std::ostream ),但数据的目标是字符串而不是文件或标准输出。 |
// 定义一个表示聊天信息的结构体
struct ChatInfo {
std::string _name; // 用户名
int _id; // 用户ID
Date _date; // 日期
std::string _msg; // 消息内容
};
int main() {
// 创建一个 ChatInfo 对象并初始化
ChatInfo winfo = { "张三", 135246, {2024, 11, 6}, "晚上一起看电影吧!" };
// 使用 ostringstream 将 ChatInfo 对象的内容转换为字符串
std::ostringstream oss;
oss << winfo._name << std::endl; // 写入用户名
oss << winfo._id << std::endl; // 写入用户ID
oss << winfo._date << std::endl; // 写入日期
oss << winfo._msg << std::endl; // 写入消息内容
// 获取转换后的字符串
std::string str = oss.str();
// 输出字符串内容
std::cout << str << std::endl;
// 创建另一个 ChatInfo 对象用于读取
ChatInfo rinfo;
// 使用 istringstream 从字符串中读取 ChatInfo 对象的内容
std::istringstream iss(str);
iss >> rinfo._name >> rinfo._id >> rinfo._date >> rinfo._msg;
// 输出分割线
std::cout << "-------------------------------------------------------" << std::endl;
// 输出读取到的聊天信息
std::cout << "姓名:" << rinfo._name << "(" << rinfo._id << ") ";
std::cout << rinfo._date << std::endl;
std::cout << rinfo._name << ":>" << rinfo._msg << std::endl;
// 输出分割线
std::cout << "-------------------------------------------------------" << std::endl;
return 0;
}
注意:
-
stringstream实际是在其底层维护了一个string类型的对象用来保存结果。
-
可以使用s.str()将让stringstream返回其底层的string对象。
总结
本文全面讲解了流的基本概念,C语言和C++的输入输出方式,详细描述了流的概念、标准流、文件IO流以及字符串(stringstream)的用法
更多推荐
所有评论(0)