C++学习之旅【IO库相关内容介绍】


引言:前篇文章,小编已经介绍了关于C++智能指针的使⽤及其原理!相信大家应该有所收获!接下来我将带领大家继续深入学习C++的相关内容!本篇文章着重介绍关于C++IO库相关内容介绍!本文将围绕IO库的核心概念、分类体系、核心特性及实际应用展开详细介绍,拆解IO操作的底层逻辑,梳理不同类型IO库的使用场景与优势,帮助开发者全面理解IO库的价值,掌握其核心使用方法,从而在实际开发中灵活运用IO库,优化数据交互流程,提升程序开发效率与质量.废话不多说,带着这些疑问,下面跟着小编的节奏🎵一起去疯狂的学习吧!
1.IO继承家族类
①C++语⾔不直接处理输⼊输出,⽽是通过⼀族定义在标准库中的类型在处理IO.这些类型⽀持从设备中读取数据和向设备中写⼊数据的IO操作,设备可以是⽂件、控制台窗⼝等.
②到⽬前为⽌,我们已经使⽤过的IO类型和对象都是操纵char数据的,默认情况下这些对象都是关联到⽤⼾的控制台窗⼝.但是实际中IO类不仅仅是从控制台窗⼝控制输⼊输出,还⽀持⽂件和string类的IO操作.其次IO类型使⽤模板实现的,还⽀持对wchar_t数据的输⼊输出.
③通过下图可以看到C++IO类型设计的是⼀个继承家族,通过继承家族类解决控制台/⽂件/string的IO操作.
④IO流库的官方文档
这是C++标准I/O流库的类继承与头文件关系图,清晰展示了C++流体系的核心层次结构.
1️⃣整体结构分层
C++ 流库从下到上分为基础层 →核心输入输出层→ 扩展功能层(文件/字符串流),所有类都围绕流(stream)和缓冲区(streambuf)两个核心概念构建.
2️⃣基础层:流的根基
(1)<ios>头文件ios_base:所有流类的最顶层基类,负责管理流的状态(如错误标志)和格式控制(如进制、对齐).ios:继承自ios_base,是输入/输出流的公共父类,核心作用是关联一个流缓冲区(streambuf),为上层读写操作提供底层数据通道.
(2)<streambuf>头文件streambuf:流缓冲区基类,是所有流的底层数据搬运工,负责和物理设备(控制台、文件、内存)直接交互,完成数据的读写缓冲.
3️⃣核心层:输入与输出的基础
(1)<istream>头文件istream:输入流基类,继承自ios,提供>>、get()、read()等输入操作接口.cin:istream的全局实例,对应标准输入(键盘),是我们最常用的输入对象.
(2)<ostream>头文件ostream:输出流基类,继承自ios,提供<<、put()、write()等输出操作接口.cout/cerr/clog:ostream的全局实例:cout:标准输出(控制台,带缓冲)cerr:标准错误(控制台,无缓冲,用于紧急错误)clog:标准错误(控制台,带缓冲,用于日志输出)
(3)<iostream>头文件iostream:同时继承自istream和ostream,是双向流基类,支持同时读写操作.
这个头文件也包含了cin、cout等全局对象的声明,所以我们写#include <iostream>就能直接使用标准输入输出.
4️⃣扩展层:针对不同场景的流
(1)<fstream>头文件(文件操作)
基于核心流类,封装了对文件的读写能力:ifstream:继承自istream→ 文件输入流(读文件)ofstream:继承自ostream→ 文件输出流(写文件)fstream:继承自iostream→ 文件双向流(同时读写文件)filebuf:继承自streambuf→ 文件流的专属缓冲区,负责和文件系统交互.
(2)<sstream>头文件(字符串操作)
基于核心流类,封装了对内存字符串的读写能力:istringstream:继承自istream→ 字符串输入流(从字符串中读数据)ostringstream:继承自ostream→ 字符串输出流(向字符串中写数据)stringstream:继承自iostream→ 字符串双向流(同时读写字符串)stringbuf:继承自streambuf→ 字符串流的专属缓冲区,负责在内存中管理字符串数据.
2.IO流状态
①IO操作的过程中,可能会发⽣各种错误,IO流对象中给了四种状态标识错误,可以参考下图进⾏理解.goodbit表示流没有错误/eofbit表⽰流到达⽂件结束/failbit表示IO操作失败了/badbit表⽰流崩溃了出现了系统级错误.
②⼀个常⻅的IO流错误是cin>>i,i是⼀个int类型的对象,如果我们在控制台输⼊⼀个字符,cin对象的failbit状态位就会被设置,cin就进⼊错误状态,⼀个流⼀旦发⽣错误,后续的IO操作都会失败,我们可以调⽤cin.clear()函数来恢复cin的状态为goodbit.
③badbit表示系统级错误,如不可恢复的读写错误,通常情况下,badbit⼀旦被设置了,流就⽆法再使⽤了.
④failbit表示⼀个逻辑错误,如期望读取⼀个整形,但是却读取到⼀个字符,failbit被设置了,流是可以恢复的,恢复以后可以继续使⽤.
⑤如果到达⽂件结束位置eofbit和failbit都会被置位.如果想再次读取当前⽂件,可以恢复⼀下流的状态,同时重置⼀个⽂件指针位置.
⑥goodbit表示流未发⽣错误.
#include<iostream>
using namespace std;
int main()
{
cout << cin.good() << endl;
cout << cin.eof() << endl;
cout << cin.bad() << endl;
cout << cin.fail() << endl << endl;
int i = 0;
//输⼊⼀个字符或多个字符,cin读取失败,流状态被标记为failbit
cin >> i;
cout << i << endl;
cout << cin.good() << endl;
cout << cin.eof() << endl;
cout << cin.bad() << endl;
cout << cin.fail() << endl << endl;
if (cin.fail())
{
//clear可以恢复流状态位goodbit
cin.clear();
//我们还要把缓冲区中的多个字符都读出来,读到数字停下来,否则再去cin>>i还是会失败
char ch = cin.peek();
while (!(ch >= '0' && ch <= '9'))
{
ch = cin.get();
cout << ch;
ch = cin.peek();
}
cout << endl;
}
cout << cin.good() << endl;
cout << cin.eof() << endl;
cout << cin.bad() << endl;
cout << cin.fail() << endl << endl;
cin >> i;
cout << i << endl;
return 0;
}
3.管理输出缓冲区
①任何输出流都管理着⼀个缓冲区,⽤来保存程序写的数据.如果我们执⾏os<<“hello world”;字符串可能⽴即输出,也可能被操作系统保存在缓冲区中,随后再输出.有了缓冲区机制,操作系统就可能将多个输出操作组合成为⼀个单⼀的系统级写操作.因为设备的写操作通常是很耗时的,允许操作系统将多个输出操作组合为单⼀的设备写操作肯可能带来很⼤的性能提升.
②会触发缓冲区刷新,将数据真正的写到输出设备或⽂件的原因有很多,如:<1>程序正常结束;<2>缓冲区满了;<3>输出了操纵符endl 或 flush会⽴即刷新缓冲区;<4>我们使⽤了操纵符unitbuf设置流的内部状态,来清空缓冲区,cerr就设置了unitbuf,所以cerr输出都是⽴即刷新的.<5>⼀个输出流关联到另⼀个流时,当这个流读写时,输出流会⽴即刷新缓冲区.例如默认情况下cerr和cin都被关联到cout,所以读cin或写cerr时,都会导致cout缓冲区会被⽴即刷新.
③tie可以⽀持跟其他流绑定和解绑,可以参考下图.
这是C++标准库中std::ios::tie函数的官方文档,它解释了流的绑定(tied)机制.
1️⃣函数基本信息
所属类:std::ios(所有流的基类)
头文件:<ios>/<iostream>
作用:获取或设置当前流的绑定输出流(tied stream)
2️⃣两个重载版本
(1)获取绑定流(无参版本)
ostream* tie() const;
功能:返回一个指针,指向当前流所绑定的输出流.
示例:cin.tie()默认会返回&cout,因为cin默认绑定到cout.
(2)设置绑定流(带参版本)
ostream* tie(ostream* tiestr);
功能:将当前流绑定到tiestr这个输出流,并返回调用前原本绑定的流指针(如果之前没绑定,返回nullptr).
示例:cin.tie(nullptr)会解除cin与cout的绑定.
3️⃣核心概念:什么是绑定流(tied stream)?
绑定流是一个输出流,它的核心行为是:
当当前流执行任何 I/O 操作之前,这个绑定的输出流会被自动 flush(刷新缓冲区).
举个最常见的例子:
默认情况下cin绑定到cout,所以当你执行:
cout << “请输入数字:”;
int x;
cin >> x;
在cin等待输入前,cout的缓冲区会被强制刷新,保证请输入数字:这句话先显示在屏幕上,再等待你输入,避免出现先等待输入,再显示提示文字的混乱情况.
4️⃣默认绑定规则
标准输入流:cin默认绑定到cout,wcin(宽字符输入流)默认绑定到wcout.
库实现可能在初始化时,对其他标准流做额外绑定(比如cerr/clog相关).
5️⃣参数与返回值
参数tiestr:要绑定到的目标输出流指针(可以是nullptr表示解除绑定).
返回值:调用前,这个流原本绑定的输出流指针;如果之前没有绑定,返回nullptr.
#include<iostream>
#include<fstream>
using namespace std;
void func(ostream& os)
{
os << "hello world";
os << "hello ";
//"hello world"和"hello"是否输出不确定
system("pause");
//遇到endl,"hello world"和"hello"⼀定刷新缓冲区输出了
//os << endl;
//os << flush;
//int i;
//cin >> i;
os << "hello cat";
//"hello cat"是否输出不确定
system("pause");
}
int main()
{
ofstream ofs("test.txt");
//func(cout);
//unitbuf设置后,ofs每次写都直接刷新
//ofs << unitbuf;
//cin绑定到ofs,cin进⾏读时,会刷新ofs的缓冲区
//cin.tie(&ofs);
func(ofs);
return 0;
}
#include<iostream>
using namespace std;
int main()
{
//在io需求⽐较⾼的地⽅,如部分⼤量输⼊的竞赛题中,加上以下⼏⾏代码可以提⾼C++IO效率
//并且建议⽤'\n'替代endl,因为endl会刷新缓冲区
//关闭标准 C++ 流是否与标准 C 流在每次输⼊/输出操作后同步。
ios_base::sync_with_stdio(false);
//关闭同步后,以下程序可能顺序为b a c
//std::cout << "a\n";
//std::printf("b\n");
//std::cout << "c\n";
//解绑cin和cout关联绑定的其他流
cin.tie(nullptr);
cout.tie(nullptr);
return 0;
}
4.标准IO流
①C++标准IO流前⾯已经使⽤得⽐较多了,C++标准IO流默认是关联到控制台窗⼝的.cin是istream类型全局对象,cout/cerr/clog是ostream类型的全局对象,内置类型这两个类都直接进⾏了重载实现,所以可以直接使⽤,⾃定义类型就需要我们⾃⼰重载<<和>>运算符.
②ostream和istream是不⽀持拷⻉的,只⽀持移动(外部不能使⽤,因为是保护成员).
③istream的cin对象⽀持转换为bool值,进⾏条件逻辑判断,⼀旦被设置了badbit或failbit标志位,就返回false,如果是goodbit就返回true.
④ostream和istream还有不少其他接⼝,实践中相对⽤得⽐较少,需要时⼤家查查⽂档.
#include<iostream>
#include<fstream>
#include<string>
using namespace std;
int main()
{
int i = 0, j =1;
//持续的输⼊,要结束需要输⼊Ctrl+Z换⾏,Ctrl+Z⽤于告诉程序输⼊已经完成,类似于在⽂件末尾添加⼀个标记。
//istream& operator>>(int i),>>运算符重载的返回值是istream对象,istream对象可以调⽤operator bool转换为bool值
//本质在底层是将cin的eofbit和failbit标志位设置了,cin调⽤operator bool函数语法逻辑上实现转换为bool值
while (cin>>i>>j)
{
cout << i <<":"<<j<< endl;
}
cout << cin.good() << endl;
cout << cin.eof() << endl;
cout << cin.bad() << endl;
cout << cin.fail() << endl << endl;
//流⼀旦发⽣错误就不能再⽤了,清理重置⼀下再能使⽤
cin.clear();
string s;
while (cin >> s)
{
cout << s << endl;
}
}
5.⽂件IO流
①ofstream是输出⽂件流,也就是写⽂件的流,ofstream是ostream的派⽣类;ifstream是输⼊⽂件流,也就是读⽂件的流,ifstream是istream的派⽣类;fstream是ifstream和ofstream的派⽣类,既可以读也可以写.
②⽂件流对象可以在构造时打开⽂件,也可以调⽤open函数打开⽂件,打开⽂件的mode有图中的⼏种.in为读打开;out为写打开;binary以⼆进制模式打开;ate打开后⽴即寻位到流结尾;app每次写⼊前寻位到流结尾;trunc在打开时舍弃流的内容;这些值是ios_base中定义的成员变量继承下来的,并且它们也是组合的独⽴⼆进制位值,需要组合时,可以或到⼀起.它们之间的区别,具体参考下⾯的代码演示.
③⽂件流打开后如果需要可以主动调⽤close函数关闭,也可以不关闭,因为流对象析构函数中会关闭.
④⽂件流打开⽂件失败或读写失败,也会使⽤IO流状态标记,我们调⽤operator bool或operator!判断即可.
⑤ifstream⽂件流的读数据主要可以使⽤get/read/>>重载,ofstream⽂件流写数据主要可以使⽤put/write/<<重载,具体主要参考下⾯代码的演示.相⽐c语⾔⽂件读写的接⼝,C++fstream流功能更强⼤⽅便,使⽤<<和>>进⾏⽂件读写很⽅便,尤其是针对⾃定义类型对象的读写.
#include<iostream>
#include<fstream>
#include<string>
using namespace std;
int main()
{
ofstream ofs("test.txt");
//字符和字符串的写
ofs.put('x');
ofs.write("hello\nworld", 11);
//使⽤<<进⾏写
ofs << "22222222" << endl;
int x = 111;
double y = 1.11;
ofs << x << endl;
ofs << y << endl;
ofs.close();
// app和ate都是尾部追加,不同的是app不能移动⽂件指针,永远是在⽂件尾写
// ate可以移动⽂件指针,写到其他位置
ofs.open("test.txt", ios_base::out | ios_base::app);
ofs << "1111111" << endl;
ofs.seekp(0, ios_base::beg);
ofs << x << " " << y << endl;
ofs.close();
ofs.open("test.txt", ios_base::out | ios_base::ate);
ofs << "1111111" << endl;
ofs.seekp(0, ios_base::beg);
ofs << x << " " << y << endl;
ofs.close();
//out和 out|trunc都会先把数据清掉,再写数据(官⽅⽂档也明确是这样写的)
//https://en.cppreference.com/w/cpp/io/basic_filebuf/open
//那么trunc存在的意义是什么呢?out|trunc更明确的表达了⽂件中有内容时要清除掉内容
//对于代码维护者和阅读者来说能清晰地理解这个⾏为,在⼀些复杂的⽂件系统环境或不同的
//C++⽂件流实现库中,out⾏为不完全等同于截断内容的情况(虽然当前主流实现基本⼀致),
//out|trunc更明确的表要清除内容的⾏为
ofs.open("test.txt", ios_base::out);
//ofs.open("test.txt", ios_base::out | ios_base::trunc);
ofs << "xxxx";
ofs.close();
return 0;
}
int main()
{
//实现⼀个图⽚⽂件的复制,需要⽤⼆进制⽅式打开读写,第⼀个参数可以给⽂件的绝对路径
ifstream ifs("你自己选取的图片路径",ios_base::in | ios_base::binary);
ofstream ofs("你自己选取的图片路径",ios_base::out | ios_base::binary);
int n = 0;
while (ifs && ofs)
{
char ch = ifs.get();
ofs << ch;
++n;
}
cout << n << endl;
return 0;
}
#include<iostream>
#include<fstream>
#include<string>
using namespace std;
class Date
{
friend ostream& operator << (ostream& out, const Date& d);
friend istream& operator >> (istream& in, Date& d);
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
istream& operator >> (istream& in, Date& d)
{
in >> d._year >> d._month >> d._day;
return in;
}
ostream& operator << (ostream& out, const Date& d)
{
out << d._year << " " << d._month << " " << d._day << endl;
return out;
}
struct ServerInfo
{
//⼆进制读写时,这⾥不能⽤string,否则写到⽂件中的是string中指向字符数组的指针
//若string对象析构后再去⽂件中读取string对象,string中读到是⼀个野指针。
char _address[32];
//string _address;
int _port;
Date _date;
};
struct ConfigManager
{
public:
ConfigManager(const char* filename)
:_filename(filename)
{}
//⼆进制写
//内存中怎么存,囫囵吞枣,就怎么直接写出去
void WriteBin(const ServerInfo& info)
{
ofstream ofs(_filename, ios_base::out | ios_base::binary);
ofs.write((const char*)&info, sizeof(info));
}
//⼆进制读
//将⽂件中的内容直接囫囵吞枣,直接读到内存中
void ReadBin(ServerInfo& info)
{
ifstream ifs(_filename, ios_base::in | ios_base::binary);
ifs.read((char*)&info, sizeof(info));
}
void WriteText(const ServerInfo& info)
{
ofstream ofs(_filename);
ofs << info._address << " " << info._port << " " << info._date;
}
void ReadText(ServerInfo& info)
{
ifstream ifs(_filename);
ifs >> info._address >> info._port >> info._date;
}
private:
string _filename; //配置⽂件
};
void WriteBin()
{
ServerInfo winfo = { "192.0.0.1111111111111111111111", 80, { 2026, 3, 21 }
};
//⼆进制读写
ConfigManager cf_bin("test.bin");
cf_bin.WriteBin(winfo);
}
void ReadBin()
{
//⼆进制读写
ConfigManager cf_bin("test.bin");
ServerInfo rbinfo;
cf_bin.ReadBin(rbinfo);
cout << rbinfo._address << " " << rbinfo._port << " " << rbinfo._date <<
endl;
}
void WriteText()
{
ServerInfo winfo = { "192.0.0.1", 80, { 2026, 3, 21 } };
//⽂本读写
ConfigManager cf_text("test.txt");
cf_text.WriteText(winfo);
}
void ReadText()
{
ConfigManager cf_text("test.txt");
ServerInfo rtinfo;
cf_text.ReadText(rtinfo);
cout << rtinfo._address << " " << rtinfo._port << " " << rtinfo._date <<
endl;
}
int main()
{
WriteBin();
ReadBin();
WriteText();
ReadText();
return 0;
}
6.stringIO流
①ostringstream是string的的写⼊流,ostringstream是ostream的派⽣类;istringstream是string的的读出流,istringstream是istream的派⽣类;stringstream是ostringstream和istringstream的派⽣类,既可以读也可以写.这⾥使⽤stringstream会很⽅便.
②stringstream系列底层维护了⼀个string类型的对象⽤来保存结果,使⽤⽅法跟上⾯的⽂件流类似,只是数据读写交互的都是底层的string对象.
③stringstream最常⽤的⽅式还是使⽤<<和>>重载,进⾏数据和string之间的IO转换.
④string流使⽤str函数获取底层的string对象,或者写⼊底层的string对象,具体细节参考下⾯代码理解.
#include<iostream>
#include<sstream>
#include<string>
using namespace std;
class Date
{
friend ostream& operator << (ostream& out, const Date& d);
friend istream& operator >> (istream& in, Date& d);
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
istream& operator >> (istream& in, Date& d)
{
in >> d._year >> d._month >> d._day;
return in;
}
ostream& operator << (ostream& out, const Date& d)
{
out << d._year << " " << d._month << " " << d._day << endl;
return out;
}
struct ChatInfo
{
string _name; //名字
int _id; //id
Date _date; //时间
string _msg; //聊天信息
};
int main()
{
//结构信息序列化为字符串
ChatInfo winfo = { "李四", 123456, { 2026, 3, 21 }, "晚上⼀起看电影吧" };
ostringstream oss;
oss << winfo._name << " " << winfo._id << " " << winfo._date << " " <<
winfo._msg;
string str = oss.str();
cout << str << endl << endl;
//我们通过⽹络这个字符串发送给对象,实际开发中,信息相对更复杂,
//⼀般会选⽤Json、xml等⽅式进⾏更好的⽀持
//字符串解析成结构信息
ChatInfo rInfo;
istringstream iss(str);
iss >> rInfo._name >> rInfo._id >> rInfo._date >> rInfo._msg;
cout << "-------------------------------------------------------" << endl;
cout << "姓名:" << rInfo._name << "(" << rInfo._id << ") ";
cout << rInfo._date << endl;
cout << rInfo._name << ":>" << rInfo._msg << endl;
cout << "-------------------------------------------------------" << endl;
return 0;
}

结语:本篇文章写完,小编的C++学习之旅就要正式的告一段落了!在这段C++学习之旅中,我从零基础的语法入门,逐步深入到面向对象、STL容器、算法实战,再到内存管理、高级特性的探索,每一步都充满了挑战与收获.C++作为一门兼顾高效性与灵活性的编程语言,既保留了C语言的底层操控能力,又引入了面向对象的编程思想,同时通过STL标准库简化了开发流程,其庞大的体系和严谨的语法的,让我在学习中不断突破认知,也深刻体会到"多练、多思、多复盘"是掌握这门语言的核心.从入门启蒙:夯实语法基础➡️进阶提升:面向对象核心特性➡️工具掌握:STL标准库的应用➡️深入探索:高级特性与实战优化.这段C++学习之旅,让我深刻体会到"编程没有捷径,唯有脚踏实地".C++虽然难度较大,但只要循序渐进、多练多思、多复盘,就能逐步掌握.每一次解决一个bug、每一次写出一个正确的算法、每一次完成一个小型项目,都是一种成长.同时,我也意识到,学习编程不仅是学习语法和工具,更是培养一种逻辑思维和解决问题的能力.这种能力,不仅适用于编程,也适用于生活和工作中的各种场景.未来,我将继续保持学习的热情,不断深入探索C++的奥秘,努力成为一名更优秀的程序员.接下来小编将继续前往不同的领域进行学习,大家跟随小编的脚步一起学习吧!
每日心灵鸡汤:往前走就是最好的结局!
高度自律,疯狂前进,无所畏惧,不断学习,不计成本投资自己,不在乎他人眼光,拉黑一切垃圾人,我要疯狂掠夺一切美好的东西.学历、身材、思维、胆识、人脉、能力,这些我都要,我野心明确,咱们顶峰相见.有时候我们确实因一件小事,变得丧气烦躁,从而毁掉一切,但别忘了,我们也可以因为狠心作出的一个个好的决定,让整个人生都变得美好.!

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐







所有评论(0)