<C++>8 I/O 流

本文最后更新于:2023年6月27日 上午

8 I/O 流

C++ 中没有输入/输出语句,但有一个面向对象的输入/输出软件包,即 I/O 流类库。

流是 I/O 流类库的核心概念。一般意义上说,凡是数据从一处传输到另一处都是流的操作。

#C++ 流类库关系图:

graph TD
a(ios) --> b(istream)
c --> e(ofstream)
a --> c(ostream) --> o(iostream)
b --> o
b --> d(ifstream)
o --> m(fstream)

图中箭头表示继承关系。从 ios 派生 istream 和 ostream 时,均使用了虚继承

istream 类提供了流的大部分输入操作,对系统预定义的所有输入流重载提取运算符 >>

ostream 类对系统预定义的所有输出流重载插入运算符 <<

C++ 的 iostream 类库提供了数百种 I/O 功能。iostream 类库接口部分包含在几个头文件中:

  • iostream:包含操作所有输入/输出流所需的基本信息。大多数 C++ 程序都应该包含该文件。

    该文件有 4 个标准流对象,提供了格式化和无格式化的 I/O 功能

  • iomanip:包含格式化 I/O 的带参数流操作符,可用于指定数据输入/输出的格式

  • fstream:包含处理文件的有关信息,提供建立文件、读/写文件的各种操作接口

8.1 标准流对象

C++ 在头文件 iostream 中定义了 4 个标准流对象:cin(标准输入流)、cout(标准输出流)、cerr(非缓冲错误输出流)、clog(缓冲错误输出流)

  • cin:与标准输入设备(键盘)相关联,用于读取数据,可以被重定向

  • cout:与标准输出设备(屏幕)相关联,用于输出数据,可以被重定向

  • cerr:与标准错误信息输出设备(屏幕)相关联(非缓冲),用于输出出错信息,不能重定向

  • clog:与标准错误信息输出设备(缓冲)相关联,用于输出出错信息,不能重定向

    其与 cerr 的区别是 cerr 不需要缓存区,直接向显示器输出信息。

程序中可以直接对上述 4 个预定义的标准流进行操作,而无需事先进行 “打开” 操作,也无需在事后进行 “关闭”。系统已经预先为每个程序都隐含了对他们的打开和关闭操作。

#错误状态字

C++ 在 ios 类中定义了相关的错误状态字来反映流的状态。

状态字的每一位对应流的一种错误状态,用一个对应的标识常量标记

标识常量 含义
goodbit 0x00 流状态正常
eofbit 0x01 文件结束符
failbit 0x02 I/O 操作失败,数据未丢失,可以恢复
badbit 0x04 非法操作,数据丢失,无法恢复

#常用函数:

  • FILE* freopen(const char* path, const char* mode, FILE* stream)

    将 stream 按照 mode 指定的模式重定向到 path 指向的文件。mode 可以是 w(写)或 r(读)

    重定向发生错误时,将关闭原本的 stream,并返回 NULL

  • int eof() const

    返回 eofbit 的值。当文本文件结束时,在输入流中会自动设置 eofbit

    抵达结尾时,该值为 1,否则为 0

    在标准输入流 cin 中,按下 ctrl + Z 组合键表示输入流的结束

  • int fail() const

    返回 failbit 状态,以判断流操作是否失败

  • int good() const

    int operator void*()

    返回 goodbit 状态,以判断流操作是否正常。

    当 eofbit、failbit、badbit 都未被置位时,返回 1,否则返回 0

  • int bad() const

    int operator void!()

    返回 badbit 状态,以判断流操作是否失败

    当 eofbit、failbit、badbit 都未被置位时,返回 0,否则返回 1

  • int rdstate() const

    返回流的当前状态

  • void clean(int nState = 0)

    更改当前错误状态字

8.2 控制 I/O 格式

不同的应用会对数据输入/输出的格式提出不同的要求。可以在使用 cout 输出时控制格式。

8.2.1 流操作符

为方便使用,C++ 中对所有的基本数据类型均设置了默认的数据输入/数据输出格式。

#默认的输出/输入格式:

I/O 的数据类型 默认输入格式 默认输出格式
short、int、long 与整型常数相同 一般整数形式,负数前带有负号
float、double、long double 与浮点数相同 浮点或指数格式。取决于哪个较短
char 第一个非空白字符 单个字符(无引号)
char* 从第一个空白字符起,到下一个空白字符结束 字符序列(无引号)
void* 无前缀的十六进制数 无前缀的十六进制数
Bool true 或 1 识别为 true
false 或 0 识别为 false
1 或 0

C++ 在 iostream 中提供了一些常用的无参数的流操作符(也称格式控制符)

流操作符可以直接用在 >><< 后,使用便捷。

#常用的格式控制符:

使用以下格式控制符时要包含头文件 iostream

流操作符 作用 输入/输出
endl 输出一个换行符,并清空流 O
ends 输出字符串结束,并清空流 O
flush 清空流缓冲区 O
dec 以十进制形式输入/输出整数(默认设置) I/O
hex 以十六进制形式输入/输出整数 I/O
ost 以八进制形式输入/输出整数 I/O
ws 提取空白字符 O

#常用的格式控制流操作符:

使用以下格式控制符时要包含头文件 iomanip

流操作符 作用
fixed 以普通小数形式输出浮点数
scientific 以科学计数法形式输出浮点数
left 左对齐。即宽度不足时将字符添加到右边
right 右对齐。即宽度不足时将字符添加到在边(默认)
setbase(int b) 设置输出时的进制。b 可以是 8、10、16
setw(int w) 仅一次有效:设置输出宽度为 w 字符,或输入时读入 w 个字符。
setfill(int c) 输出指定宽度时,不足宽度的场合以 c 字符填充(默认以空格填充)
setprecision(int n) 设置输出浮点数的精度为 n。
非 fixed、scientific 方式的场合,n 为有效数字最多的位数。超过时四舍五入或变为科学计数法并保留最多 n 位
fixed、scientific 方式的场合,n 是小数点后保留的位数
setiosflags(fmtflags f) 通用操作符。将格式标志 f 对应的格式标志位置置为 1
resetiosflags(fmtflags f) 通用操作符。将格式标志 f 对应的格式标志位置置为 0(清除)
boolapha 把 true 和 false 输出为字符串
noboolapha 把 true 和 false 输出为 1 和 0(默认)
showbase 输出表示数值进制的前缀
noshowbase 不输出表示数值进制的前缀(默认)
showpoint 总是输出小数点
noshowpoint 小数存在时才输出小数点(默认)
showpos 在非负值中显示 +
noshowpos 在非负值中不显示 +(默认)
skipws 输入时跳过空白字符(默认)
noskipws 输入时不跳过空白字符(默认)
uppercase 十六进制数中使用 A ~ E
若输出前缀,则前缀输出 0X、0B
科学计数法中输出 E
nouppercase 十六进制数中使用 a ~ e
若输出前缀,则前缀输出 0x、0b
科学计数法中输出 e(默认)
internal 数值的符号(正负号)在指定宽度内左对齐,数值右对齐,中间由填充字符填充

以下是一个使用示范:

#include<iostream>
#include<iomanip>
using namespace std;

int main() {
    cout << uppercase << showbase << setbase(8);
    cout << 100 << endl;							// 输出 0X64
}

8.2.2 标志字

为满足不同用户对数据输入/输出 格式的要求,C++ 提供了通过 setiosflags() 设置标志字进行格式控制的方式。函数的参数为流的格式标志位

标志字是一个 long 型的数据,由若干系统定义的格式控制标志位组合而成

#常见标志

标志常量名 含义 输入/输出
ios::skipws 0x0001 跳过输入中的空白 I
ios::left 0x0002 按输出域左对齐,用填充字符填充右边 O
ios::right 0x0004 按输出域右对齐,用填充字符填充左边(默认) O
ios::internal 0x0008 在符号位或基数指示符后填入字符 O
ios::dec 0x0010 转换为十进制基数格式(默认) I/O
ios::oct 0x0020 转换为八进制基数格式 I/O
ios::hex 0x0040 转换为十六进制基数格式 I/O
ios::showbase 0x0080 在输出中显示基数指示符 O
ios::showpoint 0x0100 输出浮点数时必须带小数点和尾部的 0 O
ios::uppercase 0x0200 以大写字母表示十六进制数,科学计数法使用 E O
ios::showpos 0x0400 正数前加 + 符号 O
ios::scientific 0x0800 以科学计数法表示浮点数 O
ios::fixed 0x1000 定点形式表示浮点数 O
ios::unitbuf 0x2000 插入操作后立即刷新流 O

当要设置多个标志时,使用 | 运算符连接

流格式标志的每一位表示一种格式,标志位间有相互制约关系。设置了某个标志,又要设置与其矛盾的标志时,应使用函数 resetiosflags() 清除原先的标志

为便于清除同类互斥位,ios 定义了几个公有静态符号常量:

ios::basefield 值为 dec|oct|hex
ios::adjustifield 值为 left|right|internal
ios::floatfield 值为 scientific|fixed

8.3 cin/cout 常用函数

#cin

  • long flags(long lFlags):用传入关键字替换原先标志字。返回那个原来的标志字

    long flags():仅返回那个原来的标志字

    long setf(long lFlags):用传入关键字置位原先标志字。返回那个原来的标志字

    long setf(long lFlags, long lMask):将关键字指定标志位清零,再置位关键字

    long unsetf(long lMask):将关键字指定标志位清零

    这些函数和流操作符 setiosflags、resetiosflags 作用相同

  • int width(int nw):设置输出宽度,返回旧输出宽度

    该函数没有持续性。输出一项数据后输出宽度恢复默认值

    int width() const:返回输出宽度

    这些函数和流操作符 setw 作用相同

  • char fill(char cFill):设置填充字符,返回旧填充字符

    char fill() const:返回填充字符

    这些函数和流操作符 setfill 作用相同

  • int percision(int np):设置数据显示精度,返回旧显示精度

    int percision() const:返回显示精度

    这些函数和流操作符 setpercision 作用相同

ostream 中还有一些输出流通用的成员函数:

  • ostream& put(char):向流中插入字符
  • ostream& write(const char* pch, int nCount):向流中插入 pch 指向的长度为 nCount 的序列

#cout

  • int get():从输入流中读取一个字符。

    遇到结束符时,返回值是系统常量 EOF

  • istream& getline(char* buf, int bufSize):从输入流读取字符到缓冲区 buf

    读取到 \n 为止。一次最多读取 bufSize - 1 个字符。函数会在数据结尾自动添加 \0

    istream& getline(char* buf, int bufSize, char delim):从输入流读取字符到缓冲区 buf

    读取到 delim 为止。一次最多读取 bufSize - 1 个字符。函数会在数据结尾自动添加 \0

  • bool eof():判断是否输入流已结束

  • istream& ignore(int n = 1, int delim = EOF):直到遇到 delim 前,跳过流中的 n 个字符

  • int peek():返回流中的当前字符,但并不取出该字符

8.4 文件操作

文本类型分为两种:

  • 文本文件:以文本的 ASCII码 形式储存在计算机中
  • 二进制文件:以文本的 二进制 形式储存在计算机中

操作文件的三大类(需包含头文件 fstream):

  • ofstream:写操作
  • ifstream:读操作
  • fstream:读写操作

在程序中,要使用一个文件,必须包含 3 个步骤:打开文件、操作文件、关闭文件

8.4.1 打开和关闭文件

对文件进行读写操作前,要先打开文件。打开文件有两个目的:

  • 建立关联。通过指定文件名,建立起文件与文件流对象的关联。以后进行操作时,可以通过与其相关的流对象来进行

  • 说明文件的使用方式和文件格式。

    使用方式有:只读、只写、既读又写、在末尾追加

    文件格式是:文本方式、二进制方式

#打开文件的方式

  • 先建立流对象,再调用 open 函数

    ofstream ofs;		// 流类名 对象名;
    ofs.open("D:\1.txt", ios::out | ios::app);    // 对象名.open(文件名, 模式);

    流类:ifstream(读)、ofstream(写)、fstream(读/写)

    文件名:用字符串标识的外部文件。可以是完整路径,也可以是相对路径

    模式:是类 ios 中定义的打开方式常量,用来表示文件打开方式

  • 使用流类自带的构造函数,在构造流对象时即连接外部文件

    ofstream ofs("D:\1.txt", ios::out | ios::app);

    可以用文件名是否为 true 以判断是否打开成功

    cout << (ofs ? "打开成功" : "打开失败") << endl;

#文件打开模式标记

模式标记 适用对象 作用
ios::in ifstream
fstream
以读方式打开文件。
如果文件不存在,则打开出错
ios::out ofstream
fstream
以写方式打开文件。
如果文件不存在,则创建该文件。否则在打开时清除原先内容
ios::app ofstream 以追加方式打开文件,用于在文件尾部添加数据。
如果文件不存在,则新建该文件
ios::ate ofstream 打开一个已有文件,文件指针指向末尾
如果文件不存在,则打开出错
ios::trunc ofstream 删除文件现有内容。单独使用时等同于 ios::out
ios::binary ofstream
ifstream
fstream
以二进制方式打开文件
若不指定此模式,则以默认的文本方式打开文件

标记可以组合使用。组合时,使用 | 符号分隔。如:

ios::in | ios::out:此时既可以读也可以写,且打开文件时原有内容保持不变

#关闭文件

发出关闭文件指令后,系统会将缓存区数据完整写入文件,并添加文件结束标记,切断流对象与外部文件的连接。

使用 fstream 的成员函数 close 关闭文件

ofstream ofs("D:\1.txt", ios::out | ios::app);
...
ofs.close();			// 关闭了文件

8.4.2 读写文件

每个文件都有一个结束标识。对于文本文件来说,C++ 在 iostream 中定义了一个标识文件结束的标识常量 EOF,其值为 0x1A 的字符。关闭文件流时,该字符被自动加入文件尾部。

在键盘操作时,按下 ctrl + Z 组合键即可在标准输入流 cin 中输入文件结束符

#写文件

ofstream ofs;					// [2] 创建流对象
ofs.open("D:/1.txt");			// [3] 打开文件
ofs << "OFS";					// [4] 写入数据
ofs.close();					// [5] 关闭文件
  1. 包含头文件:#include<fstream>

  2. 创建流对象:ofstream ofs;

  3. 打开文件:ofs.open("文件路径", 打开方式);

  4. 写数据:ofs << "写入的数据";

    也能使用 put(char c) 一次只放入一个字节

  5. 关闭文件:ofs.close();

#读文件

ifstream ifs;							// [2] 创建流对象
ifs.open("D:/1.txt", ios::in);			// [3] 打开文件
if (!ifs.is_open()) return;
char cs[1024];							// [4] 读取文件
while (ifs >> cs) cout << cs << endl;
ifs.close();							// [5] 关闭文件
  1. 打开文件

    其中 ifs.is_open() 用于判断是否打开文件。成功的场合返回 true

    判断文件是否为空的方法:

    char c;
    ifs >> c;									// 读取一个字符
    if (ifs.eof()) cout << "文件为空" << endl;	// 遇到标识符 EOF 则文件为空
  2. 读取文件。读取文件有 4 种方法:

    • 按数组长度读取至数组

      char cs[1024];
      while (ifs >> cs) cout << cs << endl;

      其中 ifs >> cs 如果读取到文件末尾,则返回 false

    • 按行读取至数组:

      char cs[1024];
      while (ifs.getline(cs, sizeof(cs))) cout << cs << endl;

      其中 ifs.getline(char*, int) 第一个形参是一个 char*,第二个形参是最大长度。该方法会把读取到的字符写入 char*

    • 按行读取至字符串:

      string str;
      while (getline(ifs, str)) cout << str << endl;
    • 按字符读取(不推荐使用):

      使用 get() 方法一次读取一个字节,或者 get(char& c) 读取字节并让 c 引用

      char c;
      while ((c = ifs.get()) != EOF) cout << c;

8.4.3 二进制文件

二进制文件也被称为 类型文件。

二进制文件以基本数据类型的二进制形式存放。其数据存储格式与内存一致,且长度仅与数据类型有关。二进制数据流不会对写入读出的数据做格式转换,便于高速处理数据。

#二进制文件与文本文件的区别

  • 文本文件兼容性强。但其存储信息时需要人为添加分隔符,占用空间大。读取时需要在内存、外存间进行格式转换,也不便于对数据进行随机访问 。

  • 二进制文件以二进制形式存储数据,便于对数据进行随机访问,读写速度快。但兼容性差。

  • 在 Windows 中,以文本方式读取文件,系统会将所有 \r\n 转换成 \n。即如果存在连续字节 0x0D0A,系统会将其转换为 0x0A

    同样的,写文件时,会将 \n 转换为 \r\n

    因此,使用文本方式打开二进制文件进行读写,其结果可能与原先存在出入。所以使用二进制方法打开文件总是更为安全。

#读写二进制文件

二进制文件打开方式要指定为 ios::binary

class Ele {			// 随便写的一个类
public:
	int n = 0;
};

void write_met() {
	ofstream ofs("D:/1.txt", ios::out | ios::binary);	//[2] 创建输出流对象
	Ele e = Ele();
	e.n = 100;
	ofs.write((char*)&e, sizeof(e));					//[3] 写入文件
	ofs.close();
}

Ele* read_met() {
	ifstream ifs("D:/1.txt", ios::in | ios::binary);	//[4] 创建输入流对象
	if (!ifs.is_open()) return NULL;
	Ele e;												//[5] 读取文件
	ifs.read((char*)&e, sizeof(e));
	return &e;
}
  1. 写入文件

    ofs.write(const char*, int):需求一个对象的 char* 类型的指针,因此要进行强制转型

    ifs.read(const char*, int):同理

    使用这些方法进行读写时,不需要额外插入分隔符。它们本身都要求指定读写长度

  2. 能使用 int gcount() 成员函数得到读取的字节数

    int num = ifs.gcount();			// 上一次读取的字节数

8.4.4 随机访问文件

如果一个文件只能进行顺序存取操作,则称为顺序文件。键盘、显示器、磁带就是典型的顺序文件设备。

能在文件任意位置进行存取操作的,称为随机访问文件。

C++ 中,每个流都有一个 流指针,其由系统控制,随着流的各种操作在字节流中移动。

文件打开后,系统会自动生成一个流指针,指向文件开头(以 ios::ate 形式打开时,指向结尾)。文件的 读/写 操作从指针位置开始。每次 读/写 操作后,指针会移动到下一个 读/写 分量的起始位置。

顺序访问方式下,流指针自动移动,无需人为改变。也能通过函数改变指针位置,实现随机访问。

istream:

  • istream& seekg(long offset, ios::seek_dir dir):让文件读指针向指定位置移动

    其中 ios::seek_dir 是 ios 中定义的一个枚举类型,值是:

    • ios::beg:流的开始位置。此时 offset 需是非负整数
    • ios::cur:表示流的当前位置。此时 offset 为负表示向前移动
    • ios::end:;流的结束位置。此时 offset 需是非正整数

    istream& seekg(long pos):设置文件读指针的位置

    等价于 seekg(pos, ios::beg)

  • long tellg():返回读指针的当前位置

ostream:

  • ostream& seekp(long offset, ios::seek_dir dir):让文件写指针向指定位置移动

    ostream& seekp(long pos):设置文件写指针的位置

  • long tellp():返回写指针的当前位置

在 fstream 中,读指针与写指针是同一指针。对其调用上述 读/写 函数是等价的


<C++>8 I/O 流
https://i-melody.github.io/2022/06/18/C++/入门阶段/8 IO 流/
作者
Melody
发布于
2022年6月18日
许可协议