📝前言:
这篇文章我们来讲讲IO库 && IO流:
🎬个人简介:努力学习ing
📋个人专栏:C++学习笔记
🎀CSDN主页 愚润求学
🌄其他专栏:C语言入门基础,python入门基础,python刷题专栏,Linux
一,IO库
C语言是面向过程的语言,而C++是面向对象。在C++中,我们的IO也是面向对象的,这个对象也叫做流。对于要输出的目标文件的不同,IO的对象的类型就不同(比如,输出到屏幕和输出到文件的IO类型是不同的)。
C++IO类型设计的是⼀个继承家族,通过继承家族类解决控制台/⽂件/string的IO操作。
二,IO流
1. 什么是IO流
流是一种抽象概念,表示数据从一个源到目标的流动。
- 输入流:数据从外设流向程序
- 输出流:数据从程序流向外设
在C++中,我们可以把流看做是一个具有以下特性的对象。
- 缓冲区:用来存储数据,避免频繁的与外设交互,提高IO效率
- 状态:流对象会维护一个状态标识,用于检测IO操作是否成功
- 不同流动方向对应不同的对象:输入流和输出流对应的对象不同
- 运算符重载:可以使用
<<
和>>
进行输入和输出(方便) - 格式化控制:允许
<<
和>>
中对数据进行格式化控制
2. IO流的状态
IO流中的状态用标记位来表示,通过|
操作可以链接多个标记位。
四种状态(定义在ios_base
中,子类流都是会继承下来的):
goodbit
表示:流没有错误,对应0001
failbit
表示:IO操作失败,对应0010
eofbit
表示:流到达文件结束,对应0100
badbit
表示:流崩溃了出现了系统级错误,对应1000
其他细节:
- 如果到达文件结束位置
eofbit
和failbit
都会被置位 - ⼀个流一旦发生错误,后续的IO操作都会失败,这时需要调用
clear()
函数来重置流的标记位为1000
- 出现
badbit
代表底层系统级错误,clear()
也没用,这个流就完全不可用了 - 在基类
ios_base
中也提供了很多访问器,让用户可以查看流的状态good()
:查看goodbit
位rdstate()
:返回当前流的所有错误组合的标识(通常是:bad, fail, eof
的组合),通过&
对应的状态位可以得到具体的状态(不如fail()
等方便)setstate(iostate flags)
:设置流的标记位(即:置为1
),setstate(failbit | eofbit)
把这两位置为1
示例:
int main()
{
cout << cin.good() << endl; // 查看 cin 的 goodbit
int i;
cin >> i;
// 故意输入 字符 a 创造错误
cout << (cin.rdstate() & ios_base::failbit) << endl; // 看当前流的错误信息
cin.clear(); // 重置状态为: 1000
// 但是注意:此时字符 a 还在缓冲区没有被读走
cin.get(); // 读走 cin 缓冲区的一个字符
cin >> i; // 重新输入 10
cout << i << endl;
cout << cin.good() << cin.bad() << cin.fail() << cin.eof() << endl;
return 0;
}
运行结果:
如果缓冲区中还残留有多个字符,我们可以用peek()
查看缓冲区的第一个字符,然后get
出来,直到第一个字符为int
时再重新cin
3. IO流的缓冲区
用户的输入并不是直接刷新到外设上,而是先保存在IO流的缓冲区里,当我们刷新缓冲区的时候才会刷新到外设上(实际上是先刷新到内核缓冲区上,但是这设计操作系统)
缓冲区刷新模式分为:满刷新、行筛选、立即刷新(在这里我就不细讲了。)
endl
或flush
会立即强制刷新缓冲区endl
其实是一个函数,<<
对应函数指针会调用对应的函数,而endl
里面包括换行和flush
操作
unitbuf
:设置成立即刷新模式
什么时候会触发缓冲区刷新?
- 缓冲区满了
- 满足刷新模式的条件了
- 强制刷新,如
flush
- 被绑定了,在操作输入流时,绑定的输出流会自动刷新缓冲区
如:默认情况下cin
和cout
绑定,所以cin
读的时候,会导致cout
缓冲区会被刷新
操作:
cin.tie(&cout); // 绑定
cin.tie(nullptr); // 解绑
同步:每次 C++ 流操作前,会先刷新对应的 C 流缓冲区(反之亦然),确保输出顺序一致
关闭同步:
// 关闭标准 C++ 流是否与标准 C 流在每次输⼊/输出操作后同步。
ios_base::sync_with_stdio(false); // 默认是true
关闭以后cout
和printf
混用的时候,输出顺序就不能保障
4. 标准IO流
不过多讲了,cin
和cout
一直在用
ostream
和istream
是不支持拷贝的,只支持移动(外部也不能使用,因为是保护成员)- 对于内置类型,可以直接使用
<<
和>>
- 对于自定义类型,自定义类型要重载
<<
和>>
istream
的cin
对象⽀持转换为bool
值- 即:可以
if(cin)
,如果cin
被设置了badbit
或failbit
,就返回false
,如果是goodbit
就返回true
- 即:可以
- 其他接口也不讲了,需要的时候再查阅(毕竟也记不完,也没必要硬记)
5. 文件IO流
ofstream
是ostream
的派生类,用于输出(写),ifstream
是istream
的派生类,用于输入(读),fstream
是ifstream
和ofstream
的派生类,既可以读也可以写- 文件流对象可以在构造时打开文件,也可以调用
open
函数打开文件,打开文件的mode
(选项,本质也是标记位,可以用|
来组合)有5钟out
:写方式打开,默认清空写trunc
:清空写app
:每次写操作都到末尾写in
:只读方式打开ate
:打开时把文件指针定位到文件末尾(有点鸡肋,用不到)binary
:二进制方式 读 / 写(看和哪个搭配)
- 注意:因为这些标识符是基类的成员,而
iostream
又是继承的ios_base
在,子类中要使用继承的成员需要指明基类的类域,即:ios_base::out
(以上的操作和Linux中的系统调用都差不多,因为本来封装的就是系统调用,就不展示寄出用法了)
示例1
下面展示一个,对于一个已有内容的文件,不希望希望清空,然后用seekp()
设置文件指针写入指针的位置,来实现任意位置读写的操作。(seekp
是写入指针,seekg
是读指针)
原log.txt
代码:
int main()
{
// 这样打开文件就不会清空,文件指针在起始位置
ofstream outfile("log.txt", ios_base::out | ios_base::in);
outfile.seekp(9, ios::cur); // 把指针从当前位置往后移动 9 个位置(以字节为单位)
outfile << "hello" << endl;
return 0;
}
运行结果:
可见,文件写指针p
本来原来指向1
,往后移动9
个位置到,
然后覆盖写入hello
共五个字符,即覆盖写了:\n
、6,7,
共五个字符。
示例2
下面展示一个直接以二进制方式读写来实现图片复制。
int main()
{
ifstream ifs("test.png", ios_base::in | ios_base::binary);
ofstream ofs("test_copy.png", ios_base::out | ios_base::binary);
int n = 0;
while (ifs && ofs) // 确保流的状态都是 good 的
{
char ch = ifs.get();
ofs << ch;
++n;
}
cout << n << endl;
return 0;
}
运行结果:
对于大部分东西的复制,我们都可以用二进制,直接当一个整体输入和输出。
一定要确保两步都是用binary
来的。因为图片的内容不是标准的字符,文本输入和输出中,遇到的一些字符(就如我们cin
字符时,输入ctrl + z
)可能会标记fail
和eof
,导致流失效,图片不能复制完全。
6. string IO流
- string IO流是一种在内存中处理字符串的流类,它允许你像操作文件流一样操作字符串,即可以使用
<<
和>>
(和操作文件的方法一样) ostringstream
是string
的写入流,istringstream
是string
的读出流stringstream
是ostringstream
和istringstream
的派生类,既可以读也可以写【这个更好用,因为我们对字符串的读写控制没文件要求怎么高】stringstream
系列底层维护了⼀个string
类型的对象来保存结果string
流使用str
函数获取底层的string
对象
示例1
int main() {
// 输出字符串流 (ostringstream)
ostringstream oss;
oss << "Hello " << 32 << " world!"; // 会把 32 当字符
string str = oss.str(); // 获取字符串
cout << str << endl;
// 输入字符串流 (istringstream)
istringstream iss("100 200"); // 用"100 200"构造 iss底层的string
int a, b;
iss >> a >> b; // >> 会以空格为分隔符
cout << a + b << endl;
// 输入输出字符串流 (stringstream)
stringstream ss;
ss << "3.14";
double pi;
ss >> pi; // 从 ss 流中读入 3,14 然后给了pi
cout << pi * 2 << endl;
return 0;
}
2. 示例2
对于自定义类型,需要自定义类型重载<<
class Date
{
public:
friend ostream& operator<<(ostream& out, const Date& d);
Date(int y, int m, int d)
:_year(y),
_month(m),
_day(d)
{}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "/" << d._month << "/" << d._day;
return out;
}
int main()
{
stringstream ss;
Date date1(2025, 6, 3);
ss << date1 << " hello world!" << endl;
cout << ss.str() << endl;
return 0;
}
运行结果:
🌈我的分享也就到此结束啦🌈
要是我的分享也能对你的学习起到帮助,那简直是太酷啦!
若有不足,还请大家多多指正,我们一起学习交流!
📢公主,王子:点赞👍→收藏⭐→关注🔍
感谢大家的观看和支持!祝大家都能得偿所愿,天天开心!!!