目录

深入IO

第 7 章笔记


IOStream 概述

  • 流式 IO
  • 记录 IO

需要处理的问题

int x = 100;
// 通过格式化的方式,将二进制的形式转换成了相应的字符序列的表示
std::cout << x << std::endl;
// 使用一块内存
// 用于保存 x 或者 y
union 
{
   int x;
   float y;
};
x = 100;

// 使用不同的方式去解析同一内存中的内容
// float 与 int 的解析方式不同
std::cout << x << std::endl;

std::cout << y << std::endl;

涉及的操作

采用的技术

输入和输出

非格式化 I/O

int x;
//对指定地址的内容的字符进行读取
//不解析,直接放入 x
//一共读取 4个字符 100加上回车,对应的就是每个字符的 ascii 值
//注意:read 要求 4个字符,在没有满足条件之前,系统会一直等待用户输入
std::cin.read(reinterpret_cast<char*>(&x), sizeof(x));

格式化 I/O

char c = '0';
// 针对 char 输出字符 0
std::cout << c << std::endl;

// 针对 Int 输出 ascii 48
int ci = static_cast<int>(c);
std::cout << ci << std::endl;

格式控制

char c = '0';
// 显示正负,输出 +48
// 只对数值产生影响
std::cout.setf(std::ios_base::showpos);

// 让输出占10个字符,默认往左边加空格
// 默认只生效一次,读取后会被 reset 为 0
std::cout.width(10);

// 使用 * 占位
std::cout.fill('*');

操纵符

// 等同与之前的 showpos 用法,输出 +48
// witdh 的替换,需要使用 iomanip 头文件,使用 setw()
// fill 的替换,使用 setfill()
std::cout << std::showpos << std::setw(10) << std::setfill('*') << ci << std::endl;

其他操纵符

输入相关

C风格字符串的内存越界

char y[5];
// 输入长度超过 4字节 (4+\0) 均会导致内存越界
// std::string 存在缓存机制,不受限制
std::cin >> y;

// 使用 setw() 控制输入的长度,提取前 4 个字符
std::cin >> std::setw(5) >> y;

文件与内存操作

文件流

简单示例

// 传入参数为文件名
// 输出流
std::ofstream outFile("test_file");
// 输出到文件 test_file
outFile << "hello";

// 输入流
std::ifstream inFile("test_file");
std::string x;
inFile >> x;
std::cout << x << std::endl;

文件流状态

文件流会处于打开 / 关闭状态:

  • 当与文件关联时,才会处于打开状态
  • 关联文件之后,无法关联另外一个文件。必须要先关闭流才能与其他文件绑定(再次打开)
  • 缺省定义下(无文件参数),文件流默认关闭。
系统会使用缓存优化文件流

{
    // outFile 会在大括号之后自动关闭流
    std::ostream outFile.open("test_file");
}

打开方式

标记名 作用
in 打开供读取
out 打开供写入
ate 起始位置位于文件末尾
app 附加文件,总是向文件末尾写入
trunc 截断文件,删除文件中内容
binary 二进制模式
组合使用的原理

上述的打开方式都是以二进制的形态设计的,其中每一种打开方式占一位。如果组合使用,则是以两种方式的按位或运算进行:

// in & ate
// 按位 或进行组合
std::ios_base::in;   // 0010
std::ios_base::ate; // 0001
// 得到 0011,在文件末尾进行读写

ate 的使用
  • ate 表示起始读写位置处于文件末尾,但该位置可以移动
  • app 表示起始读写位置处于文件末尾,但该位置不可移动
trunc 的使用

// 将文件中的 hello 替代为 word
std::ofstream outFile("test_file", std::ios_base::out | std::ios_base::trunc);
outFile << "word";

binary
常用组合
打开方式效果加结尾标记加二进制标记
in只读打开(读)初始位置位于末尾禁止系统转换
out|trunc / out文件存在则覆盖之前内容,否则建立文件(写)初始位置位于末尾禁止系统转换
out|app在文件末尾写入(写)初始位置位于末尾禁止系统转换
in|out打开文件供更新使用(读写)初始位置位于末尾禁止系统转换
in|out | trunc打开文件,删除已存在内容,并建立文件更新使用(读写)初始位置位于末尾禁止系统转换

out|truncout 行为一致。

内存流

都定义于 <sstream> 中:

基础用法示例

int main(int argc, char const *argv[])
{
    std::ostringstream myObj;
    // 写入内容到流 (整数)
    // 转换整数到字符串(格式化)
    myObj << 1234;
    // 获取 myObj 所对应内存,使用配套的 str() 返回内容(字符串)
    std::string ret = myObj.str();

    std::cout << ret << std::endl;
    //读取字符串,并格式化(转换字符串为 int)
    std::istringstream myObj2(ret);   
    int x;  
    //将 int 赋值给 x 并打印
    myObj2 >> x;
    std::cout << x << std::endl;
    return 0;
}

配合模式使用

// 默认的模式是从起始位置开始替换

// 默认输出流中的内容是 test
// 所有输出都会基于 test 输出
std::ostringstream myObj3("test", std::ios_base::ate);
myObj3 << '1';

// 打印结果为 test1
// stringstream 会自动管理内存
std::cout << myObj3.str();

str() 的使用注意
使用内存流进行拼接优化

// 类似 vector 的空间申请
// 可能每次都会进行 allocate
std::string s;
s += "hello";
s += "world";
s += "hello";

// 改良
// 利用 ostringstream 的大缓存进行拼接
// 不会频繁的 allocate
std::ostringstream word;
word << "hello";
word << "world";
word << "hello";

// 结果相同
std::cout << s;
std::cout << word.str();

流的状态

三种异常状态

检测异常状态的方法

bit 的组合与返回结果的关系

// 使用 cin 的成员检测
std::cout << std::cin.good() 
          << std::cin.fail()
          << std::cin.bad()
          << std::cin.eof();
// 转换为 bool 值检测
          <<static_cast<bool>(std::cin) << std::endl;

利用状态

// 判断之前的输入提取是否成功
if(std::cin >> x) { //}

复位流的状态

std::cin.clear();

捕获流的异常

流的定位

提取写入时,需要考虑提取写入的位置(定位)。C++ 为此引入了流的定位:

获取流位置

设置流位置

两个重载版本

流的同步

系统默认的 “缓冲区满再输入到设备的” 行为很可能带来一些问题:

// 如果 some test 所在流没有满,导致没有被输出到终端
std::cout << "some test";
std::string name;
// 导致输入时没有提示
std::cin >> name;
这种情况下需要刷新缓冲区,也就是强制输出缓冲区内容送到设备上。

基于方法的同步

// 成员调用
std::cout.flush();
// 操纵符
std::cout << x << std::flush;

基于绑定的同步

C++ 中,任意流都可以绑定到一个输出流上。当绑定时,绑定流会记录被绑定流中的信息。当绑定时:

这种方法实际上从另外的角度解决了之前提到过的问题:

// 如果 some test 所在流没有满,导致没有被输出到终端
std::cout << "some test";
std::string name;
// 导致输入时没有提示
std::cin >> name;
此时如果 std::cin 绑定了 std::cout,那么当 std::cin 输入时,就会直接情况输出流的缓存,将我们需要的 some test 文本输入终端。

与 C 标准 I/O 的同步