【C++高阶】深入理解C++ I/O流:标准库中的隐藏宝石

Eternity._ 2024-09-30 10:35:01 阅读 74

📝个人主页🌹:Eternity._

⏩收录专栏⏪:C++ “ 登神长阶 ”

🤡往期回顾🤡:C++ 特殊类

🌹🌹期待您的关注 🌹🌹

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

❀ C++ IO流

📒1. C语言的输入与输出📚2. 流是什么📜3. C++ IO流🌞C++标准IO流⭐C++文件IO流

📝4. stringstream🍁将数值类型数据格式化为字符串🍂字符串拼接🌸序列化和反序列化

📖5. 总结


前言:在编程的世界中,输入与输出(I/O)是连接程序与现实世界的桥梁。无论是从键盘接收用户指令,还是将处理结果输出到屏幕或文件,I/O操作都是程序设计中不可或缺的一部分。对于C++这一强大而灵活的编程语言而言,其丰富的I/O流库更是为开发者提供了高效、灵活且易于使用的数据交换机制

C++的I/O流库不仅涵盖了基本的输入输出操作,如标准输入输出流(cin和cout)、文件流(ifstream和ofstream)以及字符串流(istringstream、ostringstream和stringstream),还提供了丰富的格式化选项和错误处理机制,使得开发者能够轻松应对各种复杂的I/O需求

然而,尽管C++ I/O流库功能强大,但其使用方式却相对复杂,尤其是对于初学者而言,往往难以快速掌握。因此,本文旨在通过深入浅出的方式,引导读者逐步了解C++ I/O流库的基本原理、使用方法以及高级特性。我们将从最基本的输入输出操作讲起,逐步深入到文件处理、字符串流操作、格式化输出等高级话题,帮助读者建立起对C++ I/O流库的全面认识

让我们一起走进C++ I/O流的世界,探索其背后的奥秘,共同提升编程技能,创造出更加高效、优雅的C++程序!


📒1. C语言的输入与输出

C语言中我们用到的最频繁的输入输出方式就是<code>scanf ()与printf()

scanf(): 从标准输入设备(键盘)读取数据,并将值存放在变量中

printf(): 将指定的文字/字符串输出到标准输出设备(屏幕)

注意宽度输出和精度输出控制

C语言借助了相应的缓冲区来进行输入与输出,如图:

在这里插入图片描述

输入输出缓冲区:

可以屏蔽掉低级I/O的实现,低级I/O的实现依赖操作系统本身内核的实现,所以如果能够屏

蔽这部分的差异,可以很容易写出可移植的程序可以使用这部分的内容实现“行”读取的行为,对于计算机而言是没有“行”这个概念,有了这

部分,就可以定义“行”的概念,然后解析缓冲区的内容,返回一个“行”

注意事项:

在使用<code>scanf()时,务必检查其返回值以确保成功读取了预期数量的输入项格式化字符串中的格式说明符应与输入数据的类型严格匹配


📚2. 流是什么

“流”即是流动的意思,是物质从一处向另一处流动的过程,是对一种有序连续且具有方向性的数据( 其单位可以是bit,byte,packet )的抽象描述

C++流是指信息从外部输入设备(如键盘)向计算机内部(如内存)输入和从内存向外部输出设

备(显示器)输出的过程。这种输入输出的过程被形象的比喻为“流”

C++中的流(Streams)是一种抽象的概念,用于表示数据序列的源或目标。它们提供了一种统一的方法来执行输入/输出操作,无论是从文件、内存缓冲区、控制台或其他输入输出设备读取或写入数据。流的概念使得C++的输入输出操作变得既灵活又强大

流的特征:有序连续、具有方向性

为了实现这种流动,C++定义了I/O标准类库,这些每个类都称为流/流类,用以完成某方面的功能


📜3. C++ IO流

C++系统实现了一个庞大的类库,其中ios为基类,其他类都是直接或间接派生自ios类

在这里插入图片描述


🌞C++标准IO流

C++标准IO流(Standard Input/Output Streams)是C++标准库中的一部分,它们提供了一套丰富的类和函数,用于处理标准输入输出操作,如从控制台读取数据或向控制台输出数据。这些流是面向对象的,并且基于继承体系,使得它们能够灵活地处理各种输入输出任务

<code>std::fstream:同时继承自std::istream和std::ostream,因此支持同时读写文件

C++标准库提供了4个全局流对象cin、cout、cerr、clog

使用cout进行标准输出,即数据从内存流向控制台(显示器)使用cin进行标准输入即数据通过键盘输入到程序中同时C++标准库还提供了cerr用来进行标准错误的输出,以及clog进行日志的输出

在使用时候必须要包含文件并引入std标准命名空间

注意事项:

cin为缓冲流。键盘输入的数据保存在缓冲区中,当要提取时,是从缓冲区中拿。如果一次输入过多,会留在那儿慢慢用,如果输入错了,必须在回车之前修改,如果回车键按下就无法挽回了。只有把输入缓冲区中的数据取完后,才要求输入新的数据输入的数据类型必须与要提取的数据类型一致,否则出错。出错只是在流的状态字state中对

应位置位(置1),程序继续空格和回车都可以作为数据之间的分格符,所以多个数据可以在一行输入,也可以分行输

入。但如果是字符型和字符串,则空格(ASCII码为32)无法用cin输入,字符串中也不能有

空格。回车符也无法读入cin和cout可以直接输入和输出内置类型数据,原因:标准库已经将所有内置类型的输入和输出全部重载了 cin文档 cout文档对于自定义类型,如果要支持cin和cout的标准输入输出,需要对<<和>>进行重载

调用的是operator>>,返回值是istream类型的对象,那么这里可以做逻辑条件值,源自于istream的对象又调用了operator booloperator bool调用时如果接收流失败,或者有结束标志,则返回false

在这里插入图片描述

代码示例 (C++):

<code>// 日期类

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)

{ }

operator bool()

{

// 假设输入_year为0,则结束

if (_year == 0)

return false;

else

return true;

}

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;

return out;

}

// C++ IO流,使用面向对象+运算符重载的方式

// 能更好的兼容自定义类型,流插入和流提取

int main()

{

// 自动识别类型的本质--函数重载

// 内置类型可以直接使用--因为库里面ostream类型已经实现了

int i = 520;

double j = 13.14;

cout << i << endl;

cout << j << endl;

// 自定义类型则需要我们自己重载<< 和 >>

Date d(2024, 9, 11);

cout << d;

while (d)

{

cin >> d;

cout << d;

}

return 0;

}


⭐C++文件IO流

C++文件IO流(File Input/Output Streams)是C++标准库中的一部分,用于处理文件的读写操作。这些流封装了文件作为数据的来源或目标,使得文件的读写变得既方便又灵活。C++通过< fstream >头文件提供了文件IO流的相关类和函数

C++根据文件内容的数据格式分为二进制文件和文本文件

主要类

std::ifstream:继承自std::istream,用于从文件读取数据std::ofstream:继承自std::ostream,用于向文件写入数据std::fstream:同时继承自std::istream和std::ostream,因此支持同时读写文件

采用文件流对象操作文件的一般步骤:

定义一个文件流对象

使用文件流对象的成员函数打开一个磁盘文件,使得文件流对象和磁盘文件之间建立联系

使用提取和插入运算符对文件进行读写操作,或使用成员函数进行读写

关闭文件

二进制读写代码示例 (C++):

// 文件流对象

struct ServerInfo

{

// 二进制读写时,尽量避免使用容器

string _address;

//char _address[32];

int _port;

Date _date;

};

// 二进制读写

class BinIO

{

public:

BinIO(const char* filename = "info.bin")

:_filename(filename)

{ }

void Write(const ServerInfo& winfo)

{

ofstream ofs(_filename, ofstream::out | ofstream::binary);

ofs.write((const char*)&winfo, sizeof(winfo));

}

void Read(ServerInfo& rinfo)

{

ifstream ifs(_filename, ifstream::in | ifstream::binary);

ifs.read((char*)&rinfo, sizeof(rinfo));

}

private:

string _filename = "info.bin"; // 配置文件

};

int main()

{

ServerInfo winfo = { "10.0.2.15", 22, { 2024, 9, 12 } };

BinIO bin;

bin.Write(winfo);

ServerInfo info;

bin.Read(info);

cout << info._address << endl;

cout << info._port << endl;

cout << info._date << endl;

return 0;

}

注意:二进制读写时,尽量避免使用容器,容器中存放的指针可能会在读取文件时,释放变成野指针


文件读写代码示例 (C++):

// 文件流对象

struct ServerInfo

{

string _address;

//char _address[32];

int _port;

Date _date;

};

// 文件读写

class TestIO

{

public:

TestIO(const char* filename = "info.txt")

:_filename(filename)

{ }

void Write(const ServerInfo& winfo)

{

ofstream ofs(_filename);

ofs << winfo._address << endl;

ofs << winfo._port << endl;;

ofs << winfo._date << endl;;

}

void Read(ServerInfo& rinfo)

{

ifstream ifs(_filename);

ifs >> rinfo._address;

ifs >> rinfo._port;

ifs >> rinfo._date;

}

private:

string _filename = "info.txt"; // 配置文件

};

int main()

{

ServerInfo winfo = { "10.0.2.15", 22, { 2024, 9, 12 } };

TestIO test;

test.Write(winfo);

ServerInfo info;

test.Read(info);

cout << info._address << endl;

cout << info._port << endl;

cout << info._date << endl;

return 0;

}


📝4. stringstream

stringstream 是 C++ 标准库中的一个非常有用的类,它属于 < sstream > 头文件。stringstream 是一种输入/输出流(I/O stream),用于在内存中执行字符串的输入输出操作,类似于 cin 和 cout,但是它是针对字符串的。stringstream 可以被用来进行字符串的格式化、解析和转换,而不需要通过文件或控制台

在程序中如果想要使用stringstream,必须要包含头文件。在该头文件下,标准库三个类:

istringstream、ostringstream 和 stringstream,分别用来进行流的输入、输出和输入输出操作


🍁将数值类型数据格式化为字符串

C语言中,如果想要将一个整形变量的数据转化为字符串格式:

使用itoa()函数 (C++中为 _itoa())使用sprintf()函数

但是两个函数在转化时,都得需要先给出保存结果的空间,那空间要给多大呢,就不太好界定,

而且转化格式不匹配时,可能还会得到错误的结果甚至程序崩溃

代码示例 (C++):

int main()

{

int n = 123456789;

char s1[32];

_itoa(n, s1, 10);

char s2[32];

sprintf(s2, "%d", n);

char s3[32];

sprintf(s3, "%f", n);

return 0;

}

在C++中,可以使用stringstream类对象来避开此问题

代码示例 (C++):

#include<sstream>

int main()

{

int a = 12345678;

string sa;

// 将一个整形变量转化为字符串,存储到string类对象中

stringstream s;

s << a;

s >> sa;

cout << sa << endl;

s.str("");

s.clear(); // 清空s, 不清空会转化失败

double d = 13.14;

s << d;

s >> sa;

string sValue;

sValue = s.str(); // str()方法:返回stringsteam中管理的string类型

cout << sValue << endl;

return 0;

}

注意:

多次转换时,必须使用clear将上次转换状态清空掉

stringstream s在转换结尾时(即最后一个转换后),会将其内部状态设置为badbit,因此下一次转换是必须调用clear()将状态重置为goodbit才可以转换,但是clear()不会将stringstreams底层字符串清空掉s.str(""),将stringstream底层管理string对象设置成"",否则多次转换时,会将结果全部累积在底层string对象中


🍂字符串拼接

关于字符串拼接,我们之前也了解过,两种常见方式:

std::string类中的append()函数+=操作符

代码示例 (C++):

int main()

{

string s1;

s1 += "hello";

s1 += " ";

s1 += "world";

cout << s1 << endl;

string s2;

s2.append("hello");

s2.append(" ");

s2.append("world");

cout << s2 << endl;

return 0;

}

现在我们也可以使用stringstreamostringstream来完成这一操作

代码示例 (C++):

#include<sstream>

int main()

{

// stringstream

stringstream sstream;

// 将多个字符串放入 sstream 中

sstream << "hello" << " " << "world";

cout << sstream.str() << endl;

// 清空 sstream

sstream.str("");

// ostringstream

ostringstream oss;

oss << "hello" << " " << "world";

cout << oss.str() << endl;

return 0;

}


🌸序列化和反序列化

序列化和反序列化是计算机科学中常用的两个概念,它们主要涉及到将数据结构或对象状态转换为可以存储或传输的格式(序列化),以及将这些格式恢复回原始的数据结构或对象状态(反序列化)。这两个过程在数据持久化、网络通信、对象深拷贝等场景中非常有用

我们通过网络这个字符串发送给对象,实际开发中,信息相对更复杂,一般会选用Json、xml等方式进行更好的支持

代码示例 (C++):

#include<sstream>

struct ChatInfo

{

string _name; // 名字

int _id; // id

Date _date; // 时间

string _msg; // 聊天信息

};

int main()

{

// 结构信息序列化为字符串

ChatInfo winfo = { "张宝龙", 1314520, { 2024, 9, 12 }, "晚上一起激战CS:GO"

};

ostringstream oss;

oss << winfo._name << endl;

oss << winfo._id << endl;

oss << winfo._date << endl;

oss << winfo._msg << endl;

string str = oss.str();

cout << str << endl << endl;

// 网络输出

// 字符串解析成结构信息

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;

}


注意事项:

stringstream实际是在其底层维护了一个string类型的对象用来保存结果多次数据类型转化时,一定要用clear()来清空,才能正确转化,但clear()不会将stringstream底层的string对象清空可以使用s. str("")方法将底层string对象设置为""空字符串可以使用s.str()将让stringstream返回其底层的string对象stringstream使用string类对象代替字符数组,可以避免缓冲区溢出的危险,而且其会对参数类型进行推演,不需要格式化控制,也不会出现格式化失败的风险,因此使用更方便,更安全


📖5. 总结

通过本次对C++ I/O流的学习之旅,我们一同探索了C++语言中这一强大而灵活的输入输出机制。从最初的标准输入输出流cin和cout,到文件流ifstream和ofstream的深入应用,再到字符串流istringstream、ostringstream和stringstream的灵活操作,我们见证了C++ I/O流库在数据处理和交换中的无限可能

学习过程中,我们不仅掌握了C++ I/O流库的基本用法,还学会了如何利用格式化选项来定制输出格式,使数据呈现更加符合需求的形式。同时,我们也深入了解了I/O操作中可能出现的异常和错误,并学习了相应的处理策略,以确保程序的健壮性和稳定性

最后,我希望这篇文章能够成为你学习C++ I/O流过程中的一盏明灯,为你指引方向,提供帮助。在未来的编程道路上,愿你能够运用所学,创造出更加精彩、高效的C++程序。再次感谢你的阅读,期待与你在更广阔的编程世界中相遇!

在这里插入图片描述

希望本文能够为你提供有益的参考和启示,让我们一起在编程的道路上不断前行!

谢谢大家支持本篇到这里就结束了,祝大家天天开心!

在这里插入图片描述



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。