第7章 深入IO
1.IOStream 概述
(1)IOStream 采用流式 I/O
(2)处理的主要问题:
表示形式的变化:使用格式化 / 解析在数据的内部表示与字符序列间转换
与外部设备的通信:针对不同的外部设备(终端、文件、内存)引入不同的处理逻辑
(3)所涉及的操作
格式化/解析
缓存
编码转换
传输
2.输入和输出
(1)非格式化I/O:不涉及数据表示形式的变化
常用输入函数: get / read / getline / gcount
常用输入函数: put / write
int x;
std::cin.read(reinterpret_cast<char *>(&x), sizeof(x)); //非格式化输入
std::cout << x << std::endl;
(2)格式化I/O:使用移位操作符来进行的输入 (>>) 与输出 (<<)
C++ 通过操作符重载以支持内建数据类型的格式化 I/O
可以通过重载操作符以支持自定义类型的格式化 I/O
(3)格式控制
可接收位掩码类型( showpos )、字符类型( fill )与取值相对随意( width )的格式化参数
int main()
{
char x = '0';
std::cout.setf(std::ios_base::showpos);
std::cout << x << std::endl;
int y = static_cast<int>(x);
std::cout << y <<std::endl;
}
int main()
{
char x = '0';
std::cout.setf(std::ios_base::showpos);
std::cout.width(10);
std::cout << x << std::endl;
int y = static_cast<int>(x);
std::cout << y <<std::endl;
}
int main()
{
char x = '0';
std::cout.setf(std::ios_base::showpos);
std::cout.width(10);
std::cout.fill('.');
std::cout << x << std::endl;
int y = static_cast<int>(x);
std::cout << y <<std::endl;
}
注意 width 方法的特殊性:触发后被重置
(4)操纵符
#include <iomanip>
int main()
{
char x = '0';
int y = static_cast<int>(x);
std::cout << std::showpos << std::setw(10) << std::setfill('.') << x << '\n'
<< std::setw(10) << y << std::endl;
/*
std::cout.setf(std::ios_base::showpos);
std::cout.width(10);
std::cout.fill('.');
std::cout << x << std::endl;
std::cout << y <<std::endl;
*/
}
3.文件与内存操作
(1)文件操作
basic_ifstream / basic_ofstream / basic_fstream
文件流可以处于打开 / 关闭两种状态,处于打开状态时无法再次打开,只有打开时才能 I/O
#include <iostream>
#include <fstream>
int main()
{
std::oftream outFile("my_file");
outFile << "Hello\n";
}
#include <iostream>
#include <fstream>
#include <string>
int main()
{
std::oftream inFile("my_file");
std::string x;
inFile >> x;
std::cout << x << '\n '
}
(2)文件流的打开模式
每种文件流都有缺省的打开方式
注意 ate 与 app 的异同
binary 能禁止系统特定的转换
避免意义不明确的流使用方式(如 ifstream + out )
标记名 | 作用 |
---|---|
in | 打开以供读取 |
out | 打开以供写入 |
ate | 表示起始位置位于文件末尾 |
app | 附加文件,即总是向文件尾写入 |
trunc | 截断文件,即删除文件中的内容 |
binary | 二进制模式 |
#include <iostream>
#include <fstream>
int main()
{
std::oftream outFile("my_file",std::ios_base::out);
outFile << "Hello\n";
}
#include <iostream>
#include <fstream>
int main()
{
std::oftream outFile("my_file",std::ios_base::out | std::ios_base::trunc);
outFile << "Hello\n";
}
(3)合法的打开方式组合
打开方式 | 效果 | 加结尾模式标记 | 加二进制模式标记 |
---|---|---|---|
in | 只读方式打开文本文件 | 初始文件位置位于文件末尾 | 禁止系统转换 |
out|trunc out | 如果文件存在,将长度阶段为0;否则建立文件供写入 | 初始文件位置位于文件末尾 | 禁止系统转换 |
out|app | 附加;打开或建立文本文件,仅供文件末尾写入 | 初始文件位置位于文件末尾 | 禁止系统转换 |
in|out | 打开文本文件供更新使用(支持读写) | 初始文件位置位于文件末尾 | 禁止系统转换 |
in|out|trunc | 如果文件存在,将长度截断为0;否则建立文件供更新使用 | 初始文件位置位于文件末尾 | 禁止系统转换 |
(4)内存流
basic_istringstream / basic_ostringstream / basic_stringstream
#include <iostream>
#include <sstream>
int main()
{
std::ostringstream obj1;
obj1 << 1234;
std::string res = obj1.str();
std::cout << res << std::endl; //整数转换成了字符串
}
#include <iostream>
#include <sstream>
#include <iomanip>
int main()
{
std::ostringstream obj1;
obj1 << 1234;
std::string res = obj1.str();
std::istringstream obj2(res);
int x;
obj2 >> x;
std::cout << x << std::endl;
}
也会受打开模式: in / out / ate / app 的影响
#include <iostream>
#include <sstream>
int main()
{
std::ostringstream buf("test", std::ios_base::ate);
buf << 1;
std::cout << buf.str() << std::endl;
}
#include <iostream>
#include <sstream>
int main()
{
std::ostringstream buf("test");
buf << 1;
std::cout << buf.str() << std::endl;
}
使用 str() 方法获取底层所对应的字符串,小心避免使用 str().c_str() 的形式获取 C 风格字符串
基于字符串流的字符串拼接优化操作
#include <iostream>
#include <sstream>
int main()
{
std::string x;
x += "Hello";
x += " world";
x += " hello";
x += " world";
std::cout << x << std::endl;
std::ostringstream obj;
obj << "Hello";
obj << " world";
obj << " hello";
obj << " world";
std::cout << obj.str() << std::endl; //性能较好
}
4.流的状态
(1)iostate
failbit / badbit / eofbit / goodbit
goodbit 无错误
badbit 不可恢复的流错误
failbit 输入/输出操作失败(格式化或提取错误)
eofbit 关联的输出序列已抵达文件尾
(2)检测流的状态
good( ) / fail() / bad() / eof() 方法
转换为 bool 值
int x;
std::cin >> x;
std::cout << std::cin.good()
<< std::cin.fail()
<< std::cin.bad()
<< std::cin.eof()
<< static_cast<bool>(std::cin) << std::endl;
(3)注意区分fail与eof
可能会被同时设置,但二者含意不同
转换为 bool 值时不会考虑 eof
(4)复位流状态
clear :设置流的状态为具体的值(缺省为 goodbit )
setstate :将某个状态附加到现有的流状态上
5.流的定位
(1)获取流位置
tellg() / tellp() 可以用于获取输入 / 输出流位置 (pos_type 类型 )
两个方法可能会失败,此时返回 pos_type(-1)
#include <iostream>
#include <sstream>
int main()
{
std::ostringstream s;
std::cout << s.tellp() << '\n';
s << 'h';
std::cout << s.tellp() << '\n';
s << "ello, world ";
std::cout << s.tellp() << '\n';
s << 3.14 << '\n';
std::cout << s.tellp() << '\n' << s.str();
}
运行结果
0
1
13
18
hello, world 3.14
#include <iostream>
#include <string>
#include <sstream>
int main()
{
std::string str = "Hello, world";
std::istringstream in(str);
std::string word;
in >> word;
std::cout << "After reading the word \"" << word
<< "\" tellg() returns " << in.tellg() << '\n';
}
运行结果
After reading the word "Hello," tellg() returns 6
(2)设置流位置
seekg() / seekp() 用于设置输入 / 输出流的位置
#include <iostream>
#include <string>
#include <sstream>
int main()
{
std::string str = "Hello, world";
std::istringstream in(str);
std::string word1, word2;
in >> word1;
in.seekg(0); // 回溯
in >> word2;
std::cout << "word1 = " << word1 << '\n'
<< "word2 = " << word2 << '\n';
}
运行结果
word1 = Hello,
word2 = Hello,
#include <sstream>
#include <iostream>
int main()
{
std::ostringstream os("hello, world");
os.seekp(7);
os << 'W';
os.seekp(0, std::ios_base::end);
os << '!';
os.seekp(0);
os << 'H';
std::cout << os.str() << '\n';
}
运行结果
Hello, World!
6.流的同步
(1)基于 flush() / sync() / unitbuf 的同步
flush() 用于输出流同步,刷新缓冲区
#include <iostream>
#include <fstream>
#include <string>
int main()
{
std::cout << "What's your name?\n" << std::flush;
//\n + 刷新, c++提供了一个新的操纵符
//即 std::endl
std::string name;
std::cin >> name;
}
sync() 用于输入流同步,其实现逻辑是编译器所定义的
输出流可以通过设置 unitbuf 来保证每次输出后自动同步
(2)基于绑定 (tie) 的同步
流可以绑定到一个输出流上,这样在每次输入 / 输出前可以刷新输出流的缓冲区
比如: cin 绑定到了 cout 上
(3)与 C 语言标准 IO 库的同步
缺省情况下, C++ 的输入输出操作会与 C 的输入输出函数同步
可以通过 sync_with_stdio 关闭该同步
#include <iostream>
#include <cstdio>
int main()
{
std::ios::sync_with_stdio(false);
std::cout << "a\n";
std::printf("b\n");
std::cout << "c\n";
}
可能的输出:
可以确定的是a在c之前
b
a
c