目录
一、c++文件流IO体系
1.1 文件流类
相对于c语言中调用stdio.h下的文件处理函数(如fopen、fclose 等),在c++中主要是采用文件流IO类来处理与文件的交互,这些IO类主要在标准库<fstream>中实现,主要由ifstream、ofstream、fstream三个类为主,而这些类是C++IO流继承体系中类模板的特化实现。
namespace std {
//文件缓存
template<class charT, class traits = char_traits<charT>>
class basic_filebuf;
using filebuf = basic_filebuf<char>;
using wfilebuf = basic_filebuf<wchar_t>;
//输入
template<class charT, class traits = char_traits<charT>>
class basic_ifstream;
using ifstream = basic_ifstream<char>;
using wifstream = basic_ifstream<wchar_t>;
//输出
template<class charT, class traits = char_traits<charT>>
class basic_ofstream;
using ofstream = basic_ofstream<char>;
using wofstream = basic_ofstream<wchar_t>;
//输入输出
template<class charT, class traits = char_traits<charT>>
class basic_fstream;
using fstream = basic_fstream<char>;
using wfstream = basic_fstream<wchar_t>;
}
这和前文讲述的设备层IO流几乎是一样的风格,basic_filebuf对应着std::streambuf,而其他三个类模板和设备层IO的三个类模板相当。
一般来说,文件可分为文本文件、视频文件、音频文件、图像文件、可执行文件等多种类别,这是从文件的功能进行分类的。从数据存储的角度来说,所有的文件本质上都是一样的,都是由一个个字节组成的,归根到底都是 0、1 比特串。除了纯文本文件外,图像、视频、可执行文件等一般被称作“二进制文件”。二进制文件如果用“记事本”程序打开,看到的是一片乱码。所谓“文本文件”和“二进制文件”,只是约定俗成的,本质上所有的文件都是由二进制位组成的,都是二进制文件。文本文件和其他二进制文件只是格式不同而已。实际上,只要规定好格式,而且不怕浪费空间,用文本文件一样可以表示图像、声音、视频甚至可执行程序。只是用文本文件表示图像、音视频的方法是非常低效的,浪费了太多的空间,因此图像、音视频的格式才会设计比较复杂,通过编解码、压缩等作出优化。
C++ 标准库中还专门提供了 3 个类用于实现文件操作,它们统称为文件流类,他们是标准库IO类模板特化的类,这 3 个类分别为:
- ifstream:专用于从文件中读取数据;
- ofstream:专用于向文件中写入数据;
- fstream:既可用于从文件中读取数据,又可用于向文件中写入数据。
宽字节的前面加‘w’标识。
1.2 文件流类继承体系
这 3 个文件流类依赖的类模板的继承关系如下:
如果将CharT采用char模板参数特化后,简化为如下方式:
istream 类和 ostream 类是从 ios类派生而来的,ifstream 类继承了istream ,因此 ifstream 类拥有 istream 类的全部成员方法。同样地,ofstream 也拥有 ostream 类的全部成员方法。而iostream类继承了 istream 类和 ostream 类,fstream 类派生于iostream类,则fstream 类拥有输入输出的成员方法。另外三个文件流遥继承了 istream 类和 ostream 类,这也就意味着,istream 和 ostream 类提供的供 cin 和 cout 调用的成员方法,也同样适用于文件流。并且这 3 个类中有些成员方法是相同的,比如 operator <<()、operator >>()、peek()、ignore()、getline()、get() 等。
需要注意的是,和 <iostream> 头文件中定义有 ostream 和 istream 类的对象 cin 和 cout 不同,<fstream> 头文件中并没有定义可直接使用的 fstream、ifstream 和 ofstream 类对象。因此,如果我们想使用该类操作文件,需要自己创建相应类的对象。
#include <iostream>
#include <fstream>
void file_stream_first_test()
{
const char *url ="https://pyfree.blog.csdn.net/";
const char *filename = "test.txt";
//创建一个 fstream 类对象
std::fstream fs;
//将 test.txt 文件和 fs 文件流关联
fs.open(filename, std::ios::out);
if(fs.is_open()){
//向test.txt文件中写入 url 字符串
fs.write(url, 30);
fs.close();
}else{
std::cout << " open " << std::string(filename) << " fail!" << std::endl;
}
}
无论是读取文件中的数据,还是向文件中写入数据,最先要做的就是调用 open() 成员方法打开文件。同时在操作文件结束后,还必须要调用 close() 成员方法关闭文件。
二、std::fstream文件流功能
2.1 std::fstream功能
std::fstream 是标准库类模板std::basic_fstream的特化类
using std::fstream = std::basic_fstream<char>;
类模板 basic_fstream 实现基于文件的流上的高层输入/输出。它将 std::basic_iostream 的高层接口赋予基于文件的缓冲( std::basic_filebuf )。
std::basic_fstream的成员定义如下:
成员类型 定义
char_type CharT
traits_type Traits ;若 Traits::char_type 不是 CharT 则程序非良构。
int_type Traits::int_type
pos_type Traits::pos_type
off_type Traits::off_type
成员函数
(构造函数) 构造文件流(公开成员函数)
(析构函数) [虚](隐式声明)析构basic_fstream和关联的缓冲区,并关闭文件(虚公开成员函数)
operator= (C++11) 移动文件流(公开成员函数)
swap (C++11)交换二个文件流(公开成员函数)
rdbuf 返回底层未处理的文件设备对象(公开成员函数)
文件操作
is_open 检查流是否有关联文件(公开成员函数)
open 打开文件,并将它与流关联(公开成员函数)
close 关闭关联文件(公开成员函数)
非成员函数
std::swap(std::basic_fstream) (C++11)特化 std::swap 算法(函数模板)
***继承自 std::basic_istream
成员函数-有格式输入
operator>> 提取带格式数据(std::basic_istream<CharT,Traits> 的公开成员函数)
无格式输入
get 从流中读并取走(移除类似指针向下一个元素移动)一个字符
(std::basic_istream<CharT,Traits> 的公开成员函数)
peek 仅读出但不取走(不移除类似指针并未移动)一个字符
(std::basic_istream<CharT,Traits> 的公开成员函数)
unget 撤销流中刚取走(移除,类似指针向后退回一个位置)的字符
(std::basic_istream<CharT,Traits> 的公开成员函数)
putback 往输入流中退回一个字符
(std::basic_istream<CharT,Traits> 的公开成员函数)
getline 一直读并取走字符,直至找到给定字符
(std::basic_istream<CharT,Traits> 的公开成员函数)
ignore 读且取走并舍弃字符,直至发现给定字符
(std::basic_istream<CharT,Traits> 的公开成员函数)
read 读并取走一块字符
(std::basic_istream<CharT,Traits> 的公开成员函数)
readsome 读并取走已经可用的字符块
(std::basic_istream<CharT,Traits> 的公开成员函数)
gcount 返回上次无格式输出操作所取走的字符数量
(std::basic_istream<CharT,Traits> 的公开成员函数)
寻位
tellg 返回输入位置指示器
(std::basic_istream<CharT,Traits> 的公开成员函数)
seekg 设置输入位置指示器
(std::basic_istream<CharT,Traits> 的公开成员函数)
杂项
sync 与底层存储设备同步
(std::basic_istream<CharT,Traits> 的公开成员函数)
成员类
sentry 实现为输出操作准备流的基本逻辑
(std::basic_istream<CharT,Traits> 的公开成员类)
***继承自 std::basic_ostream
成员函数--有格式输出
operator<< 插入带格式数据(std::basic_ostream<CharT,Traits> 的公开成员函数)
无格式输出
put 插入字符(std::basic_ostream<CharT,Traits> 的公开成员函数)
write 插入字符块(std::basic_ostream<CharT,Traits> 的公开成员函数)
寻位
tellp 返回输出位置指示器(std::basic_ostream<CharT,Traits> 的公开成员函数)
seekp 设置输出位置指示器(std::basic_ostream<CharT,Traits> 的公开成员函数)
杂项
flush 与底层存储设备同步(std::basic_ostream<CharT,Traits> 的公开成员函数)
成员类
sentry 为输出操作实现流准备的基本逻辑(std::basic_ostream<CharT,Traits> 的公开成员类)
***继承自 std::basic_ios
成员类型 定义
char_type CharT
traits_type Traits
int_type Traits::int_type
pos_type Traits::pos_type
off_type Traits::off_type
成员函数--状态函数
good 检查是否没有发生错误,例如是否可执行I/O操作
(std::basic_ios<CharT,Traits> 的公开成员函数)
eof 检查是否到达了文件末尾(std::basic_ios<CharT,Traits> 的公开成员函数)
fail 检查是否发生了可恢复的错误(std::basic_ios<CharT,Traits> 的公开成员函数)
bad 检查是否已发生不可恢复的错误(std::basic_ios<CharT,Traits> 的公开成员函数)
operator! 检查是否有错误发生(fail() 的同义)(std::basic_ios<CharT,Traits> 的公开成员函数)
operator void* (C++11 前)
operator bool (C++11 起)检查是否没有发生错误(!fail()的同义词)
(std::basic_ios<CharT,Traits> 的公开成员函数)
rdstate 返回状态标志(std::basic_ios<CharT,Traits> 的公开成员函数)
setstate 设置状态标志(std::basic_ios<CharT,Traits> 的公开成员函数)
clear 修改状态标志(std::basic_ios<CharT,Traits> 的公开成员函数)
格式化
copyfmt 复制格式化信息(std::basic_ios<CharT,Traits> 的公开成员函数)
fill 管理填充字符(std::basic_ios<CharT,Traits> 的公开成员函数)
杂项
exceptions 管理异常掩码(std::basic_ios<CharT,Traits> 的公开成员函数)
imbue 设置本地环境(std::basic_ios<CharT,Traits> 的公开成员函数)
rdbuf 管理相关的流缓冲区(std::basic_ios<CharT,Traits> 的公开成员函数)
tie 管理绑定的流(std::basic_ios<CharT,Traits> 的公开成员函数)
narrow 窄化字符(std::basic_ios<CharT,Traits> 的公开成员函数)
widen 拓宽字符(std::basic_ios<CharT,Traits> 的公开成员函数)
***继承自 std::ios_base
成员函数--格式化
flags 管理格式标志(std::ios_base 的公开成员函数)
setf 设置特定格式标志(std::ios_base 的公开成员函数)
unsetf 清除特定格式的标志(std::ios_base 的公开成员函数)
precision 管理浮点操作的精度(std::ios_base 的公开成员函数)
width 管理域的宽度(std::ios_base 的公开成员函数)
本地环境
imbue 设置本地环境(std::ios_base 的公开成员函数)
getloc 返回当前本地环境(std::ios_base 的公开成员函数)
内部可扩展数组
xalloc [静态]返回能安全用作 pword() 和 iword() 下标的程序范围内独有的整数
(std::ios_base 的公开静态成员函数)
iword 如果有必要的话,调整私有存储的大小,并且访问位于提供的下标的long元素
(std::ios_base 的公开成员函数)
pword 若需要则重置私有存储的大小,并访问位于指定下标的 void* 元素
(std::ios_base 的公开成员函数)
杂项
register_callback 注册事件回调函数(std::ios_base 的公开成员函数)
sync_with_stdio [静态]设置C++和C的IO库是否可以互操作
(std::ios_base 的公开静态成员函数)
成员类
failure 流异常(std::ios_base 的公开成员类)
Init 初始化标准流对象(std::ios_base 的公开成员类)
成员类型和常量
类型 解释
openmode 流打开模式类型
亦定义下列常量(typedef) :
app 每次写入前寻位到流结尾
binary 以二进制模式打开
in 为读打开
out 为写打开
trunc 在打开时舍弃流的内容
ate 打开后立即寻位到流结尾
fmtflags 格式化标志类型
亦定义下列常量(typedef) :
dec 为整数 I/O 使用十进制底:见 std::dec
oct 为整数 I/O 使用八进制底:见 std::oct
hex 为整数 I/O 使用十六进制底:见 std::hex
basefield dec|oct|hex 。适用于掩码运算
left 左校正(添加填充字符到右):见 std::left
right 右校正(添加填充字符到左):见 std::right
internal 内部校正(添加填充字符到内部选定点):
见 std::internal adjustfield left|right|internal 。适用于掩码运算
scientific 用科学记数法生成浮点类型,或若与 fixed 组合则用十六进制记法:
见 std::scientific
fixed 用定点记法生成浮点类型,或若与 scientific 组合则用十六进制记法:
见 std::fixed
floatfield scientific|fixed 。适用于掩码运算
boolalpha 以字母数字格式插入并释出 bool 类型:见 std::boolalpha
showbase 生成为整数输出指示数字基底的前缀,货币 I/O 中要求现金指示器:
见 std::showbase
showpoint 无条件为浮点数输出生成小数点字符:见 std::showpoint
showpos 为非负数值输出生成 + 字符,见 std::showpos
skipws 在具体输入操作前跳过前导空白符:见 std::skipws
unitbuf 在每次输出操作后冲入输出:见 std::unitbuf
uppercase 在具体输出的输出操作中以大写等价替换小写字符:见 std::uppercase
iostate 流状态类型
亦定义下列常量(typedef) :
goodbit 无错误
badbit 不可恢复的流错误
failbit 输入/输出操作失败(格式化或提取错误)
eofbit 关联的输出序列已抵达文件尾
seekdir 寻位方向类型
亦定义下列常量(typedef) :
beg 流的开始
end 流的结尾
cur 流位置指示器的当前位置
event 指定事件类型(枚举)
event_callback 回调函数类型(typedef)
2.2 文件打开格式
可以看到fstream遥继承了ios_base,因此也具有其支持的openmode流打开格式。
app 每次写入前寻位到流结尾
binary 以二进制模式打开
in 为读打开
out 为写打开
trunc 在打开时舍弃流的内容
ate 打开后立即寻位到流结尾
fstream打开文件是通过这些标志组合明确打开模式的,如同通过以按下列方式确定的第二参数 (mode) 调用 std::fopen 打开文件:
mode openmode & ~ate 若文件已存在的动作 若文件不存在的动作
"r" in 从头读取 打开失败
"w" out, out|trunc 销毁内容 创建新文件
"a" app, out|app 后附到文件 创建新文件
"r+" out|in 从头读取 错误
"w+" out|in|trunc 销毁内容 创建新文件
"a+" out|in|app, in|app 写入到结尾 创建新文件
"rb" binary|in 从头读取 打开失败
"wb" binary|out, binary|out|trunc 销毁内容 创建新文件
"ab" binary|app, binary|out|app 写入结尾 创建新文件
"r+b" binary|out|in 从头读取 错误
"w+b" binary|out|in|trunc 销毁内容 创建新文件
"a+b" binary|out|in|app, binary|in|app 写入到结尾 创建新文件
openmode是位掩码类型 (BitmaskType),开发者可以定义能用来上述这些值的任何组合(通过'|'操作符)的类型。此类型常以整数类型、 std::bitset 、或带附加运算符重载的枚举(有作用域和无作用域)实现。
void file_openmode_test(void)
{
std::string filename = "test1.bin";
//支持输入输出格式,二进制格式打开,打开时舍弃文件内容,相当于重新写入内容
std::fstream s(filename, std::fstream::binary | std::fstream::trunc | std::fstream::in | std::fstream::out);
if (!s.is_open()) {
std::cout << "failed to open " << filename << '\n';
} else {
// 写入
double d = 3.14;
s.write(reinterpret_cast<char*>(&d), sizeof d); // 二进制输出
s << 123 << "abc\n"; // 文本输出
// 对于 fstream ,这会移动文件位置指针(放置与获取)
s.seekp(0);
// 读取,因为二进制写入,如果直接文本工具打开查看是乱码
s.read(reinterpret_cast<char*>(&d), sizeof d); // 二进制输入
int n;
std::string str;
if (s >> n >> str) // 文本输入
std::cout << "read back from file: " << d << ' ' << n << ' ' << str << '\n';
s.close();
}
filename = "test2.bin";
//支持输入输出格式,二进制格式打开,每次写入前寻位到流结尾,即写入内容时在文件末端追加内容,可以自行设置文件位置指针重新指定写入位置
std::fstream s1(filename, std::fstream::binary | std::fstream::app | std::fstream::in | std::fstream::out);
if (!s1.is_open()) {
std::cout << "failed to open " << filename << '\n';
} else {
// 写入
double d = 3.15;
s1.write(reinterpret_cast<char*>(&d), sizeof d); // 二进制输出
s1 << 234 << "bcd\n";
// 对于 fstream ,这会移动文件位置指针(放置与获取)
s1.seekp(0);
// 读取,因为二进制写入,如果直接文本工具打开查看是乱码
s1.read(reinterpret_cast<char*>(&d), sizeof d); // 二进制输入
int n;
std::string str;
if (s1 >> n >> str) // 文本输入
std::cout << "read back from file: " << d << ' ' << n << ' ' << str << '\n';
s1.close();
}
filename = "test3.bin";
//支持输出格式,每次写入前寻位到流结尾
std::fstream s2(filename, std::fstream::ate | std::fstream::out);
if (!s2.is_open()) {
std::cout << "failed to open " << filename << '\n';
} else {
// 写入
double d = 3.16;
s2.write(reinterpret_cast<char*>(&d), sizeof d); // 虽然打开时不明确二进制格式,但也可以二进制输出
s2 << 345 << "cde\n";
//s2不支持读取
s2.close();
}
filename = "test3.bin";
//支持二进制输入格式
std::fstream s3(filename, std::fstream::binary | std::fstream::in);
if (!s3.is_open()) {
std::cout << "failed to open " << filename << '\n'; //ate 和 in格式凑在一起错误
}else{
// 对于 fstream ,这会移动文件位置指针(放置与获取)
s3.seekp(0);
double d = 0.0;
// 读取,因为二进制写入,如果直接文本工具打开查看是乱码
s3.read(reinterpret_cast<char*>(&d), sizeof d); // 二进制输入
int n;
std::string str;
if (s3 >> n >> str) // 文本输入
std::cout << "read back from file: " << d << ' ' << n << ' ' << str << '\n';
s3.close();
}
}
文本流是能组合成行(零或更多字符加上终止的 '\n' )的有序字符序列;行能分解成零或多个字符加一个终止的 '\n' (“换行”)字符。最后一行是否要求终止的 '\n' 是实现定义的。另外,可能必须在输入与输出时添加、切换或删除字符,以符合 OS 中的表示文本(尤其是 Windows OS 上的 C 流在输出时将 '\n' 转换为 '\r\n' ,输入时将 '\r\n' 转换为 '\n' )。仅若下列条件全为真,从文本流读取的数据才保证与先前写出到该文本流者比较相等:
- 数据由打印字符和/或控制字符 '\t' 及 '\n' 组成(尤其是 Windows OS 上,字符 '\0x1A' 终止输入)。
- 无立即为空格符所前驱的 '\n' 字符(当这种输出随后作为输入读入时,这种空格字符可能消失)。
- 末字符是 '\n' 。
二进制流是能通透地记录内部数据的有序字符序列。从二进制流读取的数据始终与先前写出到该流者比较相等,除了允许实现后附不确定数量的空字符到流结尾。宽二进制流不必终止于初始迁移状态。
2.3 格式化管理
fstream类通过基类ios_base实现格式化管理,提供了三个函数及内置的多个格式化标志:
//管理格式化标志。
fmtflags flags() const; //返回当前格式化设置。
fmtflags flags( fmtflags flags );//以给定者替换当前设置。
//设置格式化标志以指定设置。
/*
*设置 flags 所标识的格式化标志。等效地进行下列操作:
*fl = fl | flags ,其中 fl 定义内部格式化标志的状态。
*/
fmtflags setf( fmtflags flags );
/*
*//清除 mask 下的格式化标志,并设置被清除的标志为 flags 所指定者。
*等效地进行下列操作: fl = (fl & ~mask) | (flags & mask) ,其中 fl 定义格式化标志的内部状态。
*/
fmtflags setf( fmtflags flags, fmtflags mask );
//反设置 flags 所表示的格式化标志。
void unsetf( fmtflags flags );
//flags - 要反设置的格式化标志。它能为下列常量的组合:
//常量 解释
dec 为整数 I/O 使用十进制底:见 std::dec
oct 为整数 I/O 使用八进制底:见 std::oct
hex 为整数 I/O 使用十六进制底:见 std::hex
basefield dec|oct|hex 。适用于掩码运算
left 左校正(添加填充字符到右):见 std::left
right 右校正(添加填充字符到左):见 std::right
internal 内部校正(添加填充字符到内部选定点):见 std::internal
adjustfield left|right|internal 。适用于掩码运算
scientific 用科学记数法生成浮点类型,或若与 fixed 组合则用十六进制记法:见 std::scientific
fixed 用定点记法生成浮点类型,或若与 scientific 组合则用十六进制记法:见 std::fixed
floatfield scientific|fixed 。适用于掩码运算
boolalpha 以字母数字格式插入并释出 bool 类型:见 std::boolalpha
showbase 生成为整数输出指示数字基底的前缀,货币 I/O 中要求现金指示器:见 std::showbase
showpoint 无条件为浮点数输出生成小数点字符:见 std::showpoint
showpos 为非负数值输出生成 + 字符,见 std::showpos
skipws 在具体输入操作前跳过前导空白符:见 std::skipws
unitbuf 在每次输出操作后冲入输出:见 std::unitbuf
uppercase 在具体输出的输出操作中以大写等价替换小写字符:见 std::uppercase
开发者可以指定可用的格式化标志。它同样是位掩码类型 (BitmaskType),调用setf函数时,flags、mask 参数是新格式化设定。 mask 定义哪些标志可以改变, flags 定义要改变的标志中该设置那些(其他将被清除)。两个参数都能为上述常量的组合。
#include <iomanip>
const double PI = 3.1415926535;
void file_flags_test(void)
{
const int WIDTH = 15;
const char *filename = "test1.txt";
//创建一个 fstream 类对象
std::fstream fs;
//将 test.txt 文件和 fs 文件流关联
fs.open(filename, std::ios::out);
if(fs.is_open()){
//向test1.txt文件中写入字符串
fs.setf(std::ios::right); // 等价: cout << right;
fs << std::setw(WIDTH/2) << "radius"
<< std::setw(WIDTH) << "circumference" << '\n';
fs.setf(std::ios::fixed);
for (double radius = 1; radius <= 6; radius += 0.5) {
fs << std::setprecision(1) << std::setw(WIDTH/2)
<< radius
<< std::setprecision(2) << std::setw(WIDTH)
<< (2 * PI * radius) << '\n';
}
fs.close();
}else{
std::cout << " open " << std::string(filename) << " fail!" << std::endl;
}
}
//test1.txt
radius circumference
1.0 6.28
1.5 9.42
2.0 12.57
2.5 15.71
3.0 18.85
3.5 21.99
4.0 25.13
4.5 28.27
5.0 31.42
5.5 34.56
6.0 37.70
2.4 文件流状态
fstream、ifstream、ofstream文件流直接继承设备层流IO(iostream/istream/ostream),间接继承基类ios_base,因此其的流状态管理和父类型是一致的。基类ios_base定义及管理一系列条件状态成员(std::ios_base::iostate),用来标记给定的 IO 对象是否处于可用状态,或者碰到了哪种特定的错误。std::ios_base::iostate指定流状态标志。它是位掩码类型 (BitmaskType) ,定义下列常量:
goodbit 无错误
badbit 不可恢复的流错误
failbit 输入/输出操作失败(格式化或提取错误)
eofbit 关联的输出序列已抵达文件尾
//static constexpr iostate goodbit = 0;
关于iostate变量及配套成员函数运用,请参看前一篇博文的(2.3 流IO状态),这里就不展开了。
2.5 std::basic_filebuf类-文件流缓冲
如同iostream/istream/ostream类通过std::streambuf(std::basic_streambuf<char>)建立流缓冲区及实现信息与屏幕交互一样, fstream、ifstream、ofstream文件流是通过std::filebuf(std::basic_filebuf<char>)类建立文件缓存区及控制字符序列的输入与输出的。
//定义于头文件 <fstream>
template< class CharT, class Traits = std::char_traits<CharT> >
class basic_filebuf : public std::basic_streambuf<CharT, Traits> ;
using filebuf = basic_filebuf<char>;
using wfilebuf = basic_filebuf<wchar_t>;
std::basic_filebuf类又继承std::basic_streambuf类,它的关联字符序列为文件的 std::basic_streambuf 。输入序列和输出序列都关联到同一文件,并为两种操作维护连接文件位置。函数 underflow() 和 overflow()/sync() 进行文件和缓冲区的获取放置区之间的实际 I/O 。 CharT 不是 char 时,多数实现在文件存储多字节字符,并用 std::codecvt 刻面进行宽/多字节字符转换。
std::basic_filebuf类除了继承std::basic_streambuf类的提供的寻位、获取区及放置区管理等功能外,自身还定义了与关联文件的打开、关闭、缓冲区与关联文件的交互等操作。即fstream通过std::filebuf实现文件打开、关闭等操作。
//主要成员函数
is_open 检查关联文件是否打开(公开成员函数)
open 打开文件并配置它为关联字符序列(公开成员函数)
close 冲入放置区缓冲区并关闭关联的文件(公开成员函数)
现在着重深入了解一下std::filebuf::open函数(std::basic_filebuf<CharT,Traits>::open),fstream本质上是通过该函数实现文件打开操作的:
//打开拥有给定名称( s 、 p.c_str() (C++17 起) 或 str.c_str() ,取决于重载)的文件。
std::basic_filebuf<CharT, Traits>* open( const char* s,
std::ios_base::openmode mode )
std::basic_filebuf<CharT, Traits>* open( const std::string& str,
std::ios_base::openmode mode )// (C++11 起)
std::basic_filebuf<CharT, Traits>* open( const std::filesystem::path& p,
std::ios_base::openmode mode )// (C++17 起)
//仅若 std::filesystem::path::value_type 非 char 才提供重载。 (C++17 起)
std::basic_filebuf<CharT, Traits>* open( const std::filesystem::path::value_type* s,
std::ios_base::openmode mode )// (C++17 起)
/*
参数
s, str, p - 要打开的文件名; s 必须指向空终止字符串
openmode - 文件打开模式, std::ios_base 模式的二进制或
返回值
成功时为 this ,失败时为空指针。
*/
打开文件的模式采用std::ios_base::openmode的方式,但是filebuf类由ios_base定义的标志组合成打开模式(见前文的2.2章节):
- 若 openmode 不是列出的模式之一,则 open() 失败。
- 若打开操作成功且 openmode & std::ios_base::ate != 0 (设置了 ate 位),则重寻位文件位置到文件尾,如同用调用 std::fseek(file, 0, SEEK_END) ,其中 file 是调用 fopen 返回的指针。
- 若寻位失败,则调用 close() 并返回空指针以指示失败。
- 若关联文件已打开,则立即返回空指针。
2.6 寻位功能
filebuf类提供了用相对寻址或绝对寻址来重寻位文件位置的功能:
// 用绝对寻址重寻位文件位置
protected:
virtual pos_type seekpos( pos_type sp,
std::ios_base::openmode which = std::ios_base::in | std::ios_base::out );
/*
若可能,则重寻位文件指针到 sp 所指示的位置。
若关联文件未打开( is_open()==false ),则立即失败。
若文件为写入打开,则首先用 overflow() 写入放置区和任何当前感染的 locale 所要求的反迁移序列。
然后如同通过调用 std::fsetpos() 重寻位指针。
若文件为读取打开,则若需要则更新获取区。
若 sp 不是由在同一文件上调用 seekoff() 或 seekpos() 获得,则行为未定义。
参数
sp - 之前在同一文件上调用 seekoff() 或 seekpos() 获得的文件位置
which - 确定要影响输入和/或输出序列的哪个。
返回值
成功时为 sp ,失败时为 pos_type(off_type(-1)) 。
*/
许多实现不从 seekpos() 中更新获取区,而是委托给下次 sgetc() 所调用的 underflow() 。下面例子展示,seekpos() 清空获取区并需要第二个 underflow() 以观测效果。
truct mybuf : std::filebuf
{
pos_type seekpos(pos_type sp, std::ios_base::openmode which) {
std::cout << "Before seekpos(" << sp << "), size of the get area is "
<< egptr()-eback() << " with "
<< egptr()-gptr() << " read positions available\n";
pos_type rc = std::filebuf::seekpos(sp, which);
std::cout << "seekpos() returns " << rc << ".\nAfter the call, "
<< "size of the get area is "
<< egptr()-eback() << " with "
<< egptr()-gptr() << " read positions available\n";
// 若 seekpos() 清空获取区则反注释
// std::filebuf::underflow();
// std::cout << "after forced underflow(), size of the get area is "
// << egptr()-eback() << " with "
// << egptr()-gptr() << " read positions available\n";
return rc;
}
};
void file_seek_test(void)
{
mybuf buf;
buf.open("test.txt", std::ios_base::in);
std::istream stream(&buf);
stream.get(); // 读一个字符以强制 underflow()
stream.seekg(2);
}
seekoff() 相对某其他位置,设置输入和/或输出序列的位置指示器。
/*为std::basic_streambuf::pubseekoff 所调用,它又为std::basic_istream::seekg 、 std::basic_ostream::seekp 、 std::basic_istream::tellg 和 std::basic_ostream::tellp 所调用。*/
protected:
virtual pos_type seekoff( off_type off, std::ios_base::seekdir dir,
std::ios_base::openmode which = std::ios_base::in | std::ios_base::out );
/*
若可能,则重寻位文件指针到距文件起始、结尾或当前位置准确off个字符的位置(取决于dir的值)。
若关联文件未打开(is_open()==false),则立即失败。
若多字节字符编码依赖状态(codecvt::encoding()返回-1)或为变长(codecvt::encoding()返回0),而偏移off非0,则立即失败:此函数无法确定对应off个字符的字节数。
若dir不是std::basic_ios::cur或偏移ff 非0,且此filebuf对象上最近做的操作是输出(即放置缓冲区非空,或最近调用的函数是overflow()),则调用std::codecvt::unshift确定需要的反迁移序列,并通过调用 overflow()写入该序列到文件。
基类函数签名所要求的 openmode 参数通常被忽略,因为 std::basic_filebuf 只维护一个文件位置。
参数
off - 要设置位置指示器到的相对位置。
dir - 定义要应用相对偏移到的基位置。它能为下列常量之一:
beg 流的开始
end 流的结尾
cur 流位置指示器的当前位置
which - 定义要影响输入和/或输出序列的哪一个。
in 影响输入序列
out 影响输出序列
返回值 - 新构造的 pos_type 类型对象,存储结果文件位置,或在失败时为 pos_type(off_type(-1)) 。
*/
若字符编码为定宽( codecvt::encoding() 返回某个正值 width ),则如同用 std::fseek(file, width*off, whence) 移动文件指针。否则,如同用 std::fseek(file, 0, whence) 移动文件指针。需要确保文件存储及读取基于相同的字符宽度要求,窄字符和宽字符在相同字符编码下还是不一样的:
#include <locale>
void seekoff_test()
{
std::setlocale(LC_ALL, "en_US.utf8");
// 准备 10 字节文件,保有 4 个 UTF8 中的字符
std::ofstream("text3.txt") << u8"z\u00df\u6c34\U0001d10b"; // 或 u8"zß水𝄋"
// 或 "\x7a\xc3\x9f\xe6\xb0\xb4\xf0\x9d\x84\x8b";
// 用非转换编码打开
std::ifstream f1("text3.txt");
std::cout << "f1's locale's encoding() returns "
<< std::use_facet<std::codecvt<char, char, std::mbstate_t>>(f1.getloc()).encoding() << '\n'
<< "pubseekoff(3, beg) returns " << f1.rdbuf()->pubseekoff(3, std::ios_base::beg) << '\n'
<< "pubseekoff(0, end) returns " << f1.rdbuf()->pubseekoff(0, std::ios_base::end) << '\n';;
// 用 UTF-8 打开
std::wifstream f2("text3.txt");
std::cout << "f2's locale's encoding() returns "
<< std::use_facet<std::codecvt<wchar_t, char, std::mbstate_t>>(f2.getloc()).encoding() << '\n'
<< "pubseekoff(3, beg) returns " << f2.rdbuf()->pubseekoff(3, std::ios_base::beg) << '\n'
<< "pubseekoff(0, end) returns " << f2.rdbuf()->pubseekoff(0, std::ios_base::end) << '\n';
}
2.7 文件缓存区大小设置
filebuf类提供了缓冲区设置函数setbuf,通过该函数可以容许则以用户定义数组替换缓冲区。此函数为受保护虚,它仅可通过 pubsetbuf() 或从导出自 std::basic_filebuf 的用户定义类调用。
//std::basic_filebuf<CharT,Traits>::setbuf
protected:
virtual std::basic_streambuf<CharT, Traits>* setbuf( char_type* s, std::streamsize n )
/*
若 s 为空指针且 n 为零,则 filebuf 变为对输出无缓冲,这表示 pbase() 和 pptr() 为空,而任何输出都被立即发送到文件。
否则,如同调用 setbuf() ,以用户提供的首元素为s 所指向的字符数组替换内部缓冲区(受控制字符序列),并允许此 std::basic_filebuf 将该数组中的至多 n 个字节用于缓冲。
参数
s - 指向用户提供缓冲区的首个 CharT 的指针或空指针
n - 用户提供缓冲区中的 CharT 元素数或零
返回值 this。
*/
各个编译器或支持库在文件缓冲区上定义是不一致的,如果不确定是否满足项目需要,可以自行定义一个缓冲区数组,然后指定分配给文件流。
void file_userbuf_test(void)
{
int cnt = 0;
std::ifstream file;
char buf[1024];
file.rdbuf()->pubsetbuf(buf, sizeof buf);
const char * filename = "test1.txt";
file.open(filename);
if(file.is_open()){
for (std::string line; getline(file, line); ) //逐行读取
++cnt;
std::cout << cnt << '\n'; //输出行数
file.close();
}else{
std::cout << " open " << std::string(filename) << " fail!" << std::endl;
}
}
2.8 文件流缓冲区刷新管理
filebuf类提供了缓冲区的其他管理函数如overflow、uflow、underflow、sync等函数,这些函数都是受保护的虚函数,开发者可以直接继承并重载这些函数实现关联文件与缓冲区直接的交互操作:
受保护成员函数
showmanyc [虚] 可选地提供可用于从文件输入的字符数(虚受保护成员函数)
underflow [虚] 从关联文件读取(虚受保护成员函数)
uflow [虚] 从关联文件读取,并令获取区的下一位置指针前进(虚受保护成员函数)
pbackfail [虚] 回退输出序列以放回字符,不影响关联文件(虚受保护成员函数)
overflow [虚] 从放置区写字符到关联的文件(虚受保护成员函数)
setbuf [虚] 提供用户供应的缓冲区,或将此 filebuf 转变为无缓冲(虚受保护成员函数)
//seekoff [虚] 用相对寻址重寻位文件位置(虚受保护成员函数)
//seekpos [虚] 用绝对寻址重寻位文件位置(虚受保护成员函数)
sync [虚] 从放置区写字符到关联文件(虚受保护成员函数)
//imbue [虚] 更改关联的本地环境(虚受保护成员函数)
/*
*若实现,则返回从文件留待读取的字符数。
*可从文件读取的字符数,或若抵达文件尾则为 -1 。
*/
protected: virtual std::streamsize showmanyc()
/*
*读取更多数据到输入区中。
*返回-成功情况下为 Traits::to_int_type(*gptr()) (待处理序列的首字符),
*失败情况下为 Traits::eof() 。
*/
protected: virtual int_type underflow()
/*
*表现类似 underflow() ,除了若 underflow() 成功(不返回 Traits::eof() ),
*则令获取区的下一位置指针前进。换言之,消耗一个 underflow() 所获得的字符。
*返回值 - 成功情况下为读取并消耗的字符值,失败情况下为 Traits::eof() 。
*/
protected: virtual int_type uflow()
/*
*[1],调用方请求将获取区后备一个字符(以无参数调用 pbackfail() ),
*[2],调用方试图回放异于之前取得的字符(以需要放回的字符调用 pbackfail()),
*返回值
*成功时为 c ,除非 c 为 Traits::eof() ,该情况下返回 Traits::not_eof(c) 。
*失败时为 Traits::eof() 。
*/
protected: virtual int_type pbackfail( int_type c = Traits::eof() )
/*
*若放置区存在(例如文件为写入打开),则调用 overflow() 写入所有未处理输出到文件,然后如同以调用 std::fflush 冲入文件。
*若获取区存在(例如文件为读取打开),则效果是实现定义的。典型实现可能清空获取区,并将当前文件位置后移对应的字节数。
*返回值 - 成功情况下为 0 ,失败情况下为 -1 。
*/
protected: virtual int sync()
2.9 文件流从文件中读取及写入数据
filebuf类提供了读取及写入数据的操作,前者是由输入基类std::istring提供,后者是由输出基类std::ostream提供。
//std::istream(std::basic_istream<char>)
带格式的输入
operator>> 提取带格式数据(公开成员函数)
无格式输入
get 从流中读并取走(移除类似指针向下一个元素移动)一个字符(公开成员函数)
peek 仅读出但不取走(不移除类似指针并未移动)一个字符(公开成员函数)
unget 撤销流中刚取走(移除,类似指针向后退回一个位置)的字符(公开成员函数)
putback 往输入流中退回一个字符(公开成员函数)
getline 一直读并取走字符,直至找到给定字符(公开成员函数)
ignore 读且取走并舍弃字符,直至发现给定字符(公开成员函数)
read 读并取走一块字符(公开成员函数)
readsome 读并取走已经可用的字符块(公开成员函数)
gcount 返回上次无格式输出操作所取走的字符数量(公开成员函数)
//std::ostream(std::basic_ostream<char>)
有格式输出
operator<< 插入带格式数据(公开成员函数)
无格式输出
put 插入字符(公开成员函数)
write 插入字符块(公开成员函数)
这些输入输出操作分为带格式和不带格式两种方式,所谓带格式就是输入或输出前知道先明确其格式,然后再进行读写操作,而不带格式就是先读写,然后再区分格式。
#include <cstring>
void file_rd_test(void)
{
const char * filename = "test4.txt";
std::fstream file;
file.open(filename,std::ios_base::in | std::ios_base::out | std::ios_base::trunc);
if(file.is_open()){
int a = 10;
float b = 11.5;
double c = 12.6;
file << a << std::endl << b << std::endl << c << std::endl; //有格式输出
char ai[] = "10";
char bf[] = "11.5";
char cd[] = "12.6";
file.write(ai,strlen(ai))<<std::endl; //无格式输出
file.write(bf,strlen(bf))<<std::endl;
file.write(cd,strlen(cd))<<std::endl;
// 对于 fstream ,这会移动文件位置指针(放置与获取)
file.seekg(0);
int a_new = 0;
float b_new = 0.0;
double c_new = 0.0;
if (file >> a_new >> b_new >> c_new){ // 有格式输入
std::cout << "read a_new: " << a_new << '\n';
std::cout << "read b_new: " << b_new << '\n';
std::cout << "read c_new: " << c_new << '\n';
}
//
file.seekg(file.cur-1); //重新纠正读取位置
std::string str(10, ' '); // 构造 string 为流大小
int i = 0;
while (i<3)
{
if(file.getline(&str[0], 9))
{
std::cout << "read data: " << str << '\n';
}
++i;
}
file.close();
}else{
std::cout << " open " << std::string(filename) << " fail!" << std::endl;
}
}
//out log
read a_new: 10
read b_new: 11.5
read c_new: 12.6
read data: 10
read data: 11.5
read data: 12.6
关于文件流IO的主要内容介绍到此结束!
感谢读友能耐心看到最后,本人文章都很长,知识点很细,可能会有所遗漏和失误,如果有不妥之处,烦请指出。如果内容对您有所触动,请点赞关注一下防止不迷路。
三、演示源码补充
编译指令:g++ main.cpp test*.cpp -o test.exe -std=c++11
main.cpp
#include "test1.h"
int main(int argc, char* argv[])
{
file_stream_first_test();
file_openmode_test();
file_flags_test();
file_seek_test();
file_userbuf_test();
file_rd_test();
return 0;
}
test1.h
#ifndef _TEST_1_H_
#define _TEST_1_H_
void file_stream_first_test();
void file_openmode_test(void);
void file_flags_test(void);
void file_seek_test(void);
void file_userbuf_test(void);
void file_rd_test(void);
#endif //_TEST_1_H_
test1.cpp
#include "test1.h"
#include <iostream>
#include <fstream>
void file_stream_first_test()
{
const char *url ="https://pyfree.blog.csdn.net/";
const char *filename = "test.txt";
//创建一个 fstream 类对象
std::fstream fs;
//将 test.txt 文件和 fs 文件流关联
fs.open(filename, std::ios::out);
if(fs.is_open()){
//向test.txt文件中写入 url 字符串
fs.write(url, 30);
fs.close();
}else{
std::cout << " open " << std::string(filename) << " fail!" << std::endl;
}
}
void file_openmode_test(void)
{
std::string filename = "test1.bin";
//支持输入输出格式,二进制格式打开,打开时舍弃文件内容,相当于重新写入内容
std::fstream s(filename, std::fstream::binary | std::fstream::trunc | std::fstream::in | std::fstream::out);
if (!s.is_open()) {
std::cout << "failed to open " << filename << '\n';
} else {
// 写入
double d = 3.14;
s.write(reinterpret_cast<char*>(&d), sizeof d); // 二进制输出
s << 123 << "abc\n"; // 文本输出
// 对于 fstream ,这会移动文件位置指针(放置与获取)
s.seekp(0);
// 读取,因为二进制写入,如果直接文本工具打开查看是乱码
s.read(reinterpret_cast<char*>(&d), sizeof d); // 二进制输入
int n;
std::string str;
if (s >> n >> str) // 文本输入
std::cout << "read back from file: " << d << ' ' << n << ' ' << str << '\n';
s.close();
}
filename = "test2.bin";
//支持输入输出格式,二进制格式打开,每次写入前寻位到流结尾,即写入内容时在文件末端追加内容,可以自行设置文件位置指针重新指定写入位置
std::fstream s1(filename, std::fstream::binary | std::fstream::app | std::fstream::in | std::fstream::out);
if (!s1.is_open()) {
std::cout << "failed to open " << filename << '\n';
} else {
// 写入
double d = 3.15;
s1.write(reinterpret_cast<char*>(&d), sizeof d); // 二进制输出
s1 << 234 << "bcd\n";
// 对于 fstream ,这会移动文件位置指针(放置与获取)
s1.seekp(0);
// 读取,因为二进制写入,如果直接文本工具打开查看是乱码
s1.read(reinterpret_cast<char*>(&d), sizeof d); // 二进制输入
int n;
std::string str;
if (s1 >> n >> str) // 文本输入
std::cout << "read back from file: " << d << ' ' << n << ' ' << str << '\n';
s1.close();
}
filename = "test3.bin";
//支持输出格式,每次写入前寻位到流结尾
std::fstream s2(filename, std::fstream::ate | std::fstream::out);
if (!s2.is_open()) {
std::cout << "failed to open " << filename << '\n';
} else {
// 写入
double d = 3.16;
s2.write(reinterpret_cast<char*>(&d), sizeof d); // 虽然打开时不明确二进制格式,但也可以二进制输出
s2 << 345 << "cde\n";
//s2不支持读取
s2.close();
}
filename = "test3.bin";
//支持二进制输入格式
std::fstream s3(filename, std::fstream::binary | std::fstream::in);
if (!s3.is_open()) {
std::cout << "failed to open " << filename << '\n'; //ate 和 in格式凑在一起错误
}else{
// 对于 fstream ,这会移动文件位置指针(放置与获取)
s3.seekp(0);
double d = 0.0;
// 读取,因为二进制写入,如果直接文本工具打开查看是乱码
s3.read(reinterpret_cast<char*>(&d), sizeof d); // 二进制输入
int n;
std::string str;
if (s3 >> n >> str) // 文本输入
std::cout << "read back from file: " << d << ' ' << n << ' ' << str << '\n';
s3.close();
}
}
#include <iomanip>
const double PI = 3.1415926535;
void file_flags_test(void)
{
const int WIDTH = 15;
const char *filename = "test1.txt";
//创建一个 fstream 类对象
std::fstream fs;
//将 test.txt 文件和 fs 文件流关联
fs.open(filename, std::ios::out);
if(fs.is_open()){
//向test1.txt文件中写入字符串
fs.setf(std::ios::right); // 等价: cout << right;
fs << std::setw(WIDTH/2) << "radius"
<< std::setw(WIDTH) << "circumference" << '\n';
fs.setf(std::ios::fixed);
for (double radius = 1; radius <= 6; radius += 0.5) {
fs << std::setprecision(1) << std::setw(WIDTH/2)
<< radius
<< std::setprecision(2) << std::setw(WIDTH)
<< (2 * PI * radius) << '\n';
}
fs.close();
}else{
std::cout << " open " << std::string(filename) << " fail!" << std::endl;
}
}
struct mybuf : std::filebuf
{
pos_type seekpos(pos_type sp, std::ios_base::openmode which) {
std::cout << "Before seekpos(" << sp << "), size of the get area is "
<< egptr()-eback() << " with "
<< egptr()-gptr() << " read positions available\n";
pos_type rc = std::filebuf::seekpos(sp, which);
std::cout << "seekpos() returns " << rc << ".\nAfter the call, "
<< "size of the get area is "
<< egptr()-eback() << " with "
<< egptr()-gptr() << " read positions available\n";
// 若 seekpos() 清空获取区则反注释
// std::filebuf::underflow();
// std::cout << "after forced underflow(), size of the get area is "
// << egptr()-eback() << " with "
// << egptr()-gptr() << " read positions available\n";
return rc;
}
};
#include <locale>
void seekoff_test()
{
std::setlocale(LC_ALL, "en_US.utf8");
// 准备 10 字节文件,保有 4 个 UTF8 中的字符
std::ofstream("text3.txt") << u8"z\u00df\u6c34\U0001d10b"; // 或 u8"zß水𝄋"
// 或 "\x7a\xc3\x9f\xe6\xb0\xb4\xf0\x9d\x84\x8b";
// 用非转换编码打开
std::ifstream f1("text3.txt");
std::cout << "f1's locale's encoding() returns "
<< std::use_facet<std::codecvt<char, char, std::mbstate_t>>(f1.getloc()).encoding() << '\n'
<< "pubseekoff(3, beg) returns " << f1.rdbuf()->pubseekoff(3, std::ios_base::beg) << '\n'
<< "pubseekoff(0, end) returns " << f1.rdbuf()->pubseekoff(0, std::ios_base::end) << '\n';;
// 用 UTF-8 打开
std::wifstream f2("text3.txt");
std::cout << "f2's locale's encoding() returns "
<< std::use_facet<std::codecvt<wchar_t, char, std::mbstate_t>>(f2.getloc()).encoding() << '\n'
<< "pubseekoff(3, beg) returns " << f2.rdbuf()->pubseekoff(3, std::ios_base::beg) << '\n'
<< "pubseekoff(0, end) returns " << f2.rdbuf()->pubseekoff(0, std::ios_base::end) << '\n';
}
void file_seek_test(void)
{
mybuf buf;
buf.open("test.txt", std::ios_base::in);
std::istream stream(&buf);
stream.get(); // 读一个字符以强制 underflow()
stream.seekg(2);
seekoff_test();
}
void file_userbuf_test(void)
{
int cnt = 0;
std::ifstream file;
char buf[1024];
file.rdbuf()->pubsetbuf(buf, sizeof buf);
const char * filename = "test1.txt";
file.open(filename);
if(file.is_open()){
for (std::string line; getline(file, line); ) //逐行读取
++cnt;
std::cout << cnt << '\n'; //输出行数
file.close();
}else{
std::cout << " open " << std::string(filename) << " fail!" << std::endl;
}
}
#include <cstring>
void file_rd_test(void)
{
const char * filename = "test4.txt";
std::fstream file;
file.open(filename,std::ios_base::in | std::ios_base::out | std::ios_base::trunc);
if(file.is_open()){
int a = 10;
float b = 11.5;
double c = 12.6;
file << a << std::endl << b << std::endl << c << std::endl; //有格式输出
char ai[] = "10";
char bf[] = "11.5";
char cd[] = "12.6";
file.write(ai,strlen(ai))<<std::endl; //无格式输出
file.write(bf,strlen(bf))<<std::endl;
file.write(cd,strlen(cd))<<std::endl;
// 对于 fstream ,这会移动文件位置指针(放置与获取)
file.seekg(0);
int a_new = 0;
float b_new = 0.0;
double c_new = 0.0;
if (file >> a_new >> b_new >> c_new){ // 有格式输入
std::cout << "read a_new: " << a_new << '\n';
std::cout << "read b_new: " << b_new << '\n';
std::cout << "read c_new: " << c_new << '\n';
}
//
file.seekg(file.cur-1); //重新纠正读取位置
std::string str(10, ' '); // 构造 string 为流大小
int i = 0;
while (i<3)
{
if(file.getline(&str[0], 9))
{
std::cout << "read data: " << str << '\n';
}
++i;
}
file.close();
}else{
std::cout << " open " << std::string(filename) << " fail!" << std::endl;
}
}