深入IO
参考C++ IOStream
一、IOStream 概述
1.1 概述
IOStream 采用流式 I/O 而非记录 I/O ,但可以在此基础上引入结构信息
1.2 处理两个主要问题
- 表示形式的变化:使用格式化 / 解析在数据的内部表示与字符序列间转换(例如:二进制表示解析成字符)
- 与外部设备的通信:针对不同的外部设备(终端、文件、内存)引入不同的处理逻辑 这里的外部设备是只IOStream外的广义设备。
1.3 所涉及到的操作(输出流)
- 格式化 / 解析
- 缓存
- 编码转换
- 传输
1.4 采用模板来封装字符特性,采用继承来封装设备特性
- 常用的类型实际上是类模板实例化的结果
二、输入与输出
2.1 分类
- 格式化
- 非格式化
2.2 非格式化I/O
不涉及数据表示形式的变化
- 常用输入函数:get / read / getline / gcout
- 常用输入函数:put / write 提升性能,对人不友好。
2.3 格式化I/O
使用移位操作符来进行的输入 (>>) 与输出 (<<)
- C++ 通过操作符重载以支持内建数据类型的格式化 I/O
- 可以通过重载操作符以支持自 定义类型的格式化 I/O 对人 友好,可能牺牲一些性能。
2.4 格式控制
- 可接收位掩码类型( showpos )、字符类型( fill )与取值相对随意( width )的格式化参数
- 注意 width 方法的特殊性:触发后被重置
#include<iostream>
int main()
{
int x(22);
std::cout.setf(std::ios_base::showpos);//显示+
std::cout << x << std::endl;
std::cout.width(10);
std::cout << x << std::endl;
std::cout.width(10);//触发后被重置
std::cout.fill('.');
std::cout << x << std::endl;
return 0;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z2nlZ1OX-1639836672530)(d96a81291d7a0dbbdf596bdb414400e9.png)]
2.5 操纵符
- 简化格式化参数的设置
- 触发实际的插入与提取操作
#include<iostream>
#include<iomanip>
int main()
{
int x(22);
std::cout <<std::showpos<< std::setw(10)<<std::setfill('.')
<< x << std::endl;
return 0;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7Q9H4iCb-1639836672533)(98fb403f49c168ac5baa41ffe2ad921b.png)]
提取会放松对格式的限制
比如接受int 即使输入+10 空格10,接受的是10。
提取 C 风格字符串时要小心内存越界
#include<iostream>
#include<string>
int main()
{
std::string z;
std::cin >> z;
std::cout << z << std::endl;
char x[5];
std::cin >> x;// 输入太长会越界。
std::cin >> std::setw(4)>>x;// 这样可以限制。
std::cout << x << std::endl;
return 0;
}
三、文件操作
3.1 文件操作
- basic_ifstream / basic_ofstream / basic_fstream
- 文件流可以处于打开 / 关闭两种状态,处于打开状态时无法再次打开,只有打开时才能 I/O
#include<iostream>
#include<fstream>
using namespace std;
int main()
{
std::ofstream outFile("my_file");
// ...or
std::ofstream outFile;
outFile.open("my_file");
outFile << "hello "
outFile.close();
cout << outFile.is_open() << endl;
return 0;
}
3.2 文件流的打开方式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2sd0rfCk-1639836672534)(6feea5c5a101d6a8a7b0ac91c84fc21e.png)] 只输入文件名其实是缺省初始化
标记
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zy7ZAj4r-1639836672534)(514c67803e2d386fcdd1cc43ff40d8d2.png)]
- std::ios_base::ate 可以移动文件指针位置
- std::ios_base::app 只能在末尾
- std::ios_base::trunc 截断,删除之前的内容
- 这些内容标记可以“ | ”运算
- ::binary 能禁止系统特定的转换:: 比如"\n"不转换为回车
- 避免意义不明确的流使用方式(如 ifstream + out )
3.3 合法的打开方式组合(引自 C++ IOStream 一书)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xbk3Ha19-1639836672535)(1a4f05d49664b1334bf10ff214813a4e.png)]
四、内存操作
4.1 内存流
- 内存流: basic_istringstream / basic_ostringstream / basic_stringstream
#include<iostream>
#include<sstream>
#include<string>
using namespace std;
int main()
{
std::ostringstream obj1 ;
obj1 << 1234;//格式化 整数转字符串
string res = obj1.str();
cout << res << endl;//输出字符串1234
std::istringstream obj2(res) ;
int x;
obj2 >> x;
cout << x << endl;//输出int 1234
return 0;
}
4.2 也受打开模式: in / out / ate / app 的影响
Note: 使用 str() 方法获取底层所对应的字符串
Note: 小心避免使用 str().c_str() 的形式获取 C 风格字符串
#include<iostream>
#include<sstream>
#include<string>
using namespace std;
int main()
{
std::ostringstream obj1("1231231");
auto c_res = obj1.str().c_str();
//这是错误的 ,obj1.str()返回右值,用auto接受会变成指针,后续无法访问销毁的内存。
cout << c_res << endl;
}
Note: 基于字符串流的字符串拼接优化操作
通过string字符串 str += “…” 方法会不断开辟内存拷贝释放,浪费资源 使用内存流 obj << “…” 会提升性能
五、流
5.1 流的状态
std::ios_base::iostate 位掩码类型 例如:
goodbit – 0000,0000 #
badbit – 0000,0001 # 不可恢复的流错误
faibit – 0000,0010 # 输入输出操作失败(格式化或提取错误)
eofbit– 0000,0100 # 关联的输入序列已经抵达末尾
检测流的状态
- good( ) / fail() / bad() / eof() 方法
- static_cast<\bool>(std::cin) 参考cppreference
#include<iostream>
using namespace std;
int main()
{
int x;
cin >> x;
cout << cin.good();
...
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tJev7ViG-1639836672535)(b7397d59378785343151352f8758bf70.png)]
区分 fail 与 eof
– 可能会被同时设置,但二者含意不同
– 转换为 bool 值时不会考虑 eof
复位流状态
- clear :设置流的状态为具体的值(缺省为 goodbit )
- setstate :将某个状态附加到现有的流状态上
捕获流异常:exceptions方法
5.2 流的定位
5.2.1 获取流位置
– tellg() / tellp() 可以用于获取输入 / 输出流位置 (pos_type 类型 ) – 两个方法可能会失败,此时返回 pos_type(-1)
#include<iostream>
#include<sstream>
#include<string>
using namespace std;
int main()
{
std::ostringstream s;
cout << s.tellp() << '\n';
s << "h";
cout << s.tellp() << '\n';
s << "ello, world ";
cout << s.tellp() << '\n';
s << 3.14 <<"\n";
cout << s.tellp() << '\n'<< s.str();
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pILy9UiE-1639836672536)(582a4f410f9e839ba342aad15a96bea0.png)] tellp()/ tellg()返回的是下一个可以写入/读取的位置
5.2.2 设置流位置
- seekg() / seekp() 用于设置输入 / 输出流的位置
- 这两个方法分别有两个重载版本:
- 设置绝对位置:传入 pos_type 进行设置
- 设置相对位置:通过偏移量(字符个数 ios_base::beg ) + 流位置符号的方式设置
- ios_base::beg
- ios_base::cur
- ios_base::end ::注意标准io流 >> << 遇到空格回车等分隔符会停止::
5.3 流的同步
5.3.1 基于 flush() / sync() / unitbuf 的同步
– flush() 用于输出流同步,刷新缓冲区 – sync() 用于输入流同步,其实现逻辑是编译器所定义的 – 输出流可以通过设置 unitbuf 来保证每次输出后自动同步 cout<<std::unitbuf 关掉缓存区直接刷新。
5.3.2 基于绑定 (tie) 的同步
– 流可以绑定到一个输出流上,这样在每次输入 / 输出前可以刷新输出流的缓冲区 – 比如: cin 绑定到了 cout 上
5.3.3 与 C 语言标准 IO 库的同步
– 缺省情况下, C++ 的输入输出操作会与 C 的输入输出函数同步 – 可以通过 sync_with_stdio 关闭该同步