【C++进阶】C++中的IO流

目录

前言

1. C语言中的IO

2. 如何理解流

3. C++中的IO流

 3.1 C++中的标准IO

3.2 C++中的文件IO

 4. stringstream

总结


前言

        C语言中的I/O接口十分强大,但使用起来有些繁琐。好在C++中的I/O方式为我们解决了这些问题,让数据的读写操作变得更加简洁和便捷; 在本文中,我们将探讨C++中的I/O流;

在这里插入图片描述

1. C语言中的IO

         C语言中最常用的输入输出的方式就是 scanf 和printf ; scanf(): 从标准输入设备(键盘)读取数据,并将值存放在变量中。printf(): 将指定的文字/字符串输出到标准输出设备(屏幕)。注意宽度输出和精度输出控制;
        C语言借助了相应的缓冲区(内部封装)来进行输入与输出;

 如何理解:

  1. 屏蔽掉低级I/O的实现, 低级IO需要依赖底层操作系统的实现, 屏蔽掉底层细节便于程序的可移植性;
  2.  可以实现行读取的操作, 在计算机内部是没有行这个概念的, 有了缓冲区就可以根据行进行数据解析, 更为方便

2. 如何理解流

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

         C++流是指信息从外部输入设备(键盘)向计算机内部(内存)输入和从内存向外部输出设备(显示器)输出的过程。这种输入输出的过程被形象的比喻为“流”。

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

3. C++中的IO流

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

         从图中结构可以看出它使用了菱形继承;  istream 主要是进行输入, ostream主要是进行输出, 然而如果使用输入输出需要包含不同的类那就有点不便了, 所以它有采用了一个iostream的类去继承 istream 和 ostream ; 在这样在使用时只需用iostream就可以既可以输入又可以输出;

 3.1 C++中的标准IO

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

  • cout进行标准输出,即数据从内存流向控制台(显示器)。
  • cin进行标准输入即数据通过键盘输入到程序中,
  • cerr用来进行标准错误的输出
  • clog进行日志的输出

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

 注意:

  •  cin为缓冲流。键盘输入的数据保存在缓冲区中,当要提取时,是从缓冲区中拿。如果一次输入过多,会留在那儿慢慢用,如果输入错了,必须在回车之前修改,如果回车键按下就无法挽回了。只有把输入缓冲区中的数据取完后,才要求输入新的数据。
  • 输入的数据类型必须与要提取的数据类型一致,否则出错。出错只是在流的状态字state中对应位置位(置1),程序继续。
  • 空格和回车都可以作为数据之间的分格符,所以多个数据可以在一行输入,也可以分行输入。但如果是字符型和字符串,空格和回车也依然无法无法读入;
  • cin和cout可以直接输入和输出内置类型数据,原因:标准库已经将所有内置类型的输入和输出全部重载了;
  •  OJ中的输入和输出:

 常见的几种:

// 单个元素循环输入
while(cin>>a)
{
    // ...
}

// 多个元素循环输入
while(c>>a>>b>>c)
{
    // ...
}

// 整行接收
while(cin>>str)
{
    // ...
}

        使用while(cin>>i)去流中提取对象数据时,调用的是operator>>,返回值是应该是 istream 类型的对象,这里为什么可以做逻辑条件值?

        因为 istream的对象又调用了operator bool,operator bool调用时如果接收流失败,或者有结束标志,则返回false;

  • 对于自定义类型,如果要支持cin和cout的标准输入输出,需要对<<和>>进行重载
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;
}

int main()
{
    Date d(2022, 4, 10);
    cout << d;
}

3.2 C++中的文件IO

 文件的格式主要有两种: 文本文件二进制文件;

 在使用时也非常简单, 文件流对象有三种:

  • ifstream ifile(只输入用)
  • ofstream ofile(只输出用)
  • fstream iofile(既输入又输出用)

 文本形式读写文件:

// 写
ofstream ofs(_filename); // 默认以文本形式打开一个文件不需要添加任何选项(默认为覆盖写)
ofs << value1 << " " << value2 << " " << value3;

// 读
ifstream ifs(_filename);
ofs >> value1 >> value2 >> value3; // 当有多个值时,流提取时默认将换行和空格视为分割符

 二进制形式进行读和写:

// 写
ofstream ofs(_filename, ios_base::out | ios_base::binary);// 覆盖写 + 二进制; 
ofs.write((const char*)&info, sizeof(info)); // info为结构化字段, 取结构体的地址,取的字节个数

// 读
ifstream ifs(_filename, ios_base::in | ios_base::binary);
ifs.read((char*)&info, sizeof(info)); // //读到这个结构体中

 在使用时可以根据自己的需求封装一下, 这样进行文件读写时也会更加方便简洁;

 写入方式:

  • 追加写 选项: ios_base::app
  • 覆盖写 选项: ios_base::out (默认)

 另外特别需要注意:

 二进制读写一个特别坑的地方,  文本读写并不会存在问题;

使用string类型存储数据, 然后把string类型的数据以二进制形式写入到文件中时;

我的测试环境是VS2022, 感兴趣的小伙伴可以去测试一下:

struct ServerInfo
{
	/*char _address[132];*/
	string _address;
	double _x;

	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((char*)&winfo, sizeof(winfo));
	}
	// 读
	void Read(ServerInfo& rinfo)
	{
		ifstream ifs(_filename, ofstream::in | ofstream::binary);
		ifs.read((char*)&rinfo, sizeof(rinfo));//读到这个结构体中
	}

private:
	string _filename;
};

int main()
{
	
	ServerInfo winfo = { "https://legacy.cplusplus.com", 12.13, { 2024, 1, 1 } };

	BinIO bin;
	bin.Write(winfo);

	ServerInfo info;
	bin.Read(info);

	cout << info._address << endl;
	cout << info._x << endl;
	cout << info._date << endl;

	return 0;
}

当ServerInfo 使用 string 类型时, 读数据出来的时候程序就会异常崩溃;

主要分为两种情况:

读写在一个进程中时,读是可以读取到,但最后程序还是会崩,这是由于浅拷贝的问题

  • string内部存在一个指针、大小、容量
  • 写进去时会将指针地址、大小容量什么都写入到了文件当中
  • 当读取时,将所有数据读给一个对象
  • 这就导致两个对象的指针指向同一块空间,进而导致程序崩溃

读和写不在一个进程中:

  • 不同的进程读取数据的对象中的string内部变成了野指针
  •  因为读进来的string中它的指针指向的空间已经被销毁
  • 这里导致string内容读取不到
  • 所有使用二进制读写时要特别注意容器
     

 最好的避免方法就是不使用容器;

 4. stringstream

 这里作为拓展, 只需要知道 stringstream怎么使用即可;

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

  1. 使用itoa()函数
  2. 使用sprintf()函数
     

这两个接口以及非常的好用了, 尤其是sprintf, 在一些限制只能使用C的情况下, 格外好用;

但是使用时就比较繁琐;

        两个函数在转化时,都得需要先给出保存结果的空间,那空间要给多大呢,就不太好界定,
而且转化格式不匹配时,可能还会得到错误的结果甚至程序崩溃;

 在C++中,可以使用stringstream类对象来避开此问题。
        在程序中如果想要使用stringstream,必须要包含头文件。在该头文件下,标准库三个类:
istringstream、ostringstream 和 stringstream,分别用来进行流的输入、输出和输入输出操
作,本文主要介绍stringstream;

当需要拼接多个字符串时C++中string的方式就有点显得僵硬;

	string sql2;
	sql2 += "select * from t_scroe where name = '";
	sql2 += name;
	sql2 += "'";
	cout << sql2 << endl;

 而stringstream就很好的解决了这些问题;

stringstream的功能非常强大:

  • 将数值类型数据格式化为字符串
#include<sstream>
int main()
{
	int a = 12345678;
	string sa;
	stringstream s;
	s << a;
	s >> sa;
	cout << sa << endl; // 12345678
	
	s.str(""); // 将底层管理的string对象置为""
	s.clear(); // 清空s, 不清空会转化失败
	double d = 12.34;
	s << d;
	s >> sa;
	string sValue;
	sValue = s.str(); // str()方法:返回stringsteam中管理的string类型
	cout << sValue << endl; // 12.34
	return 0;
}
  • 字符串拼接
int main()
{
	stringstream sstream;
	// 将多个字符串放入 sstream 中
	sstream << "first" << " " << "string,";
	sstream << " second string";
	cout << sstream.str() << endl; //  first string, second string

	// 清空 sstream
	sstream.str("");
	sstream << "third string";
	cout << sstream.str() << endl; // third string
	return 0;
}
  • 序列化和反序列化结构数据
struct ChatInfo
{
	string _name; // 名字
	int _id;      // id
	Date _date;   // 时间
	string _msg;  // 聊天信息
};

int main()
{
	ChatInfo winfo = { "张三", 135246, { 2024, 2, 24 }, "xxxxxx" };
	stringstream oss;//stringstream继承了ostringstream和istringstream
	
	oss << winfo._name << endl;
	oss << winfo._id << endl;
	oss << winfo._date << endl;
	oss << winfo._msg << endl;
	cout << oss.str() << endl;

	// 网络输出
	ChatInfo rinfo;
	string str = oss.str();
	stringstream iss(str);
	iss >> rinfo._name;
	iss >> rinfo._id;

	// 注意这里流提取默认空格和换行作为分割符,date自己实现的ostream分隔符应该使用空格或换行
	// 否则会提取出错

	iss >> rinfo._date;
	iss >> rinfo._msg;

	cout << "-------------------------------------------------------" << endl;
	cout << "姓名:" << rinfo._name << "(" << rinfo._id << ") ";
	cout << rinfo._date << endl;
	cout << rinfo._name << ":>" << rinfo._msg << endl;
	cout << "-------------------------------------------------------" << endl;

	return 0;
}

 在学习时, 进行序列化和反序列化操作时一般使用的是Json;

 使用注意:

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

总结

        C++中的IO方式解决了C语言IO接口使用繁琐的问题, 让数据的读写操作变得更加简洁; 同时C++的IO接口在使用时也有许多的注意点 ,  学习C++中的IO , 在后续无论是工作, 还是学习都非常的重要;  好了以上便是本文的全部内容, 希望对你有所帮助, 感谢阅读!

  • 5
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值