c++的IO流

目录

1、c语言的输入与输出

2、流是什么

3.C++IO流

3.1标准IO流

3.2c++文件IO流

正常使用

tie的问题

典型的二进制读写和文本读写

4.stringstream的简单介绍

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

4.2.字符串拼接

4.3.序列化和反序列化结构数据


1、c语言的输入与输出

c语言中,我们用到的最频繁的输入输出方式是scanf与printf。

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

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

其中要注意宽度输出和精度输出控制。

设备A将数据输入缓存区,编译器根据代码从缓存区接受数据,之后处理数据,输出结果输出到缓冲区,结果从缓存区输出到设备A上。

缓存区:

1、可以屏蔽掉低级I/O的实现,低级I/O的实现依赖操作系统本身本身内核的实现,所以如果能够屏蔽掉这部分差异,就可以写出可移植的程序。

2、可以使用这部分的的内容实现"行"读取的行为。因为计算机没有行的概念,所以把缓冲区的内容通过标识符等分割实现行,这样读取的时候可以读入一行、输出一行。

2、流是什么

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

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

特点:有序连续、具有方向性。

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

3.C++IO流

c++实现了很庞大的类库,ios是基类。其他类都是直接或间接派生自ios类。

图片来自Input/Output - C++ Reference (cplusplus.com)

3.1标准IO流

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

cout:标准输出,即数据从内存流向控制台(显示器)。

cin:标准输入即数据通过键盘输入到程序中。

cerr:标准错误的输出。

clog:进行日志的输出。

cout、cerr、clog基本没区别,只是应用场景不同。

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

include<iostream>

using namespace std;

**1、cin为缓冲流。**键盘输入的数据存在缓冲区,当要提取时,是从缓冲区拿。

如果一次输入多了,会留在那等待下一次提取,如果输入错了,必须在回车前修改,如果回车按下就无法挽回,只有输入缓冲区中的数据取完后,才要求输入新的数据。

**2、输入的数据类型必须与要提取的数据类型一致,否则出错。**出错只是在流的状态字state中队员位置位(置1),程序继续。

3、空格和回车都可以作为数据之间的分格符 ,所以多个数据可以在一行输入,也可以分行输入。但如果是字符型和字符串,则空格(ascll为32),无法用cin输入 ,字符串中也不能有空格。回车符也无法读入。

**4、cin和cout可以直接输入和输出内置类型数据,**原因:标准库已经将所有内置类型的输入和输出全部重载,一些库自带的容器也已经做了重载,比如string

cin:

cout:

5.对于自定义类型(非官方库的容器),如果要支持cin和cout,需要重载<<和>>,具体可看我《类与对象》的文章。

6、在线oj的输入

cpp 复制代码
while(cin>>n>>m)
{
    ....
}

数据是多行的输入,那么我们自己测试的时候,怎么结束这个循坏,ctrl+c(是很暴力,直接把进程干掉了),一般是ctrl+z+换行。

可以看到,string是有重载>>的,返回的是cin的引用,那么怎么让cin的引用转换成bool呢,就如我《类型转换》的文章中,c++是有自定义类型转内置类型的方法,而库里也是有实现的。

cin在读取的时候并返回的时候,因为while的条件类型是bool,所以cin会进行隐式类型转换,当读取到特定错误字符(接受流失败)或者读取到文件末尾(结束符)的时候,就会转换为false,其他情况都是true。

通过这种方式,在线oj会直接让程序读取文件,读到文件末尾,自然就结束了。

而我们自己测试的时候,ctrl+z本身也是特定字符,会返回false。

下面这份是基于我类与对象的那个日期类的基础上再加了个operator bool

cpp 复制代码
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()
	{
		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 = 1;
	double j = 2.2;
	cout << i << endl;
	cout << j << endl;
	// 自定义类型则需要我们自己重载<< 和 >>
	Date d(2022, 4, 10);
	cout << d;
		while (d)
		{
			cin >> d;
			cout << d;
		}
	return 0;
}

还有一种情况,cin和cout的速度不够快

cpp 复制代码
		ios::sync_with_stdio(0);
		cin.tie(0);cout.tie(0);
c++因为要兼容c和c++,所以cin和cout都要考虑c和c++的缓冲区(保证混着用的时候输入输出是正常顺序),但c只需要考虑c的缓冲区。
所以cin和cout的速度就比scanf和printf慢很多。
ios::sync_with_stdio(0);就是直接关闭了cin和cout兼容c的机制,让他们只考虑c++自己的缓冲区。
这样的话速度可以跟scanf和printf差不多。
		cin.tie(0);就是让cin和cout不再绑定

但注意,这样写了之后就不要混用c++和c的输入输出

3.2c++文件IO流

c++根据文件内容的数据格式分为二进制文件和文本文件。采用文件流操作文件的一般步骤:

1、定义一个文件流对象(ifstream输入、ofstream输出、fstream输入输出,这3个都继承自iostream、istream、ostream)

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

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

4、关闭文件。

头文件<fstream>

正常使用

关于成员函数,这里不多说,可以直接去文档里查。

cpp 复制代码
	ifstream isf;
	isf.open("te2.cpp");
	char x;
	while (isf.get(x))
	{
		cout << x;
	}

get也是返回流对象,也有重载operator bool,也可以做到隐式转换成bool类型。

下面是比较简洁的写法。

cpp 复制代码
	ifstream isf("te2.cpp");
	//流对象的析构会自动close,如果要提前关闭,再isf.close();
	char x;
	while (isf>>x)//流对象也有重载>>,内置类型可以直接输入,自定义类型再自己重载。
	{
		cout << x;
	}

但这个写法,>>是不接受空格和回车的。

所以如果格式要求严格的话,还是推荐get。

getline读一行。read读n个字符。

cpp 复制代码
ifstream isf("te2.cpp",ios::in | ios::binary);//以二进制的方式读入
//默认是文本类型
//这个写法,本质就是提前设置了值,不同的值对应不同的读写方式,通过或的方式,形成不同的值传入

tie的问题

第一种形式(1)返回一个指向被绑定输出流的指针。

第二种形式(2)将该对象绑定到tiestr,并返回在调用之前被绑定的流的指针(如果有的话)。

被绑定的流是在此流对象中的每次输入/输出操作之前都会被刷新的输出流对象

cpp 复制代码
#include <iostream>     // std::ostream, std::cout, std::cin
#include <fstream>      // std::ofstream

int main () {
  std::ostream *prevstr;
  std::ofstream ofs;
  ofs.open ("test.txt");

  std::cout << "tie example:\n";//这段话是输出到cout的缓冲区的

  *std::cin.tie() << "This is inserted into cout";
//这段话也是cout的缓冲区。
// 因为cin默认是绑定到cout的,tie()返回被绑定的流对象的指针,所以这里返回是cout的指针
//所以这里输出在cout的缓冲区。
  prevstr = std::cin.tie (&ofs);
//因为ofstream是ostream的派生类,所以可以直接派生类对象的地址给父类对象的指针
//因为tie(有参数)返回是调用前被绑定的流对象的指针,所以这里返回是cout的指针
//此时cin绑定到了ofs
  *std::cin.tie() << "This is inserted into the file";
//所以此时的tie()返回的是ofs的指针,解引用后,因为ofstream有重载<<,所以直接输出到text.txt文件中了。
  std::cin.tie (prevstr);
//此时又重新绑定回了cout。
  ofs.close();
  std::cin.tie(0);//这个就是绑定了空指针。
  return 0;
}

典型的二进制读写和文本读写

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> pll;
typedef pair<pll, ll> ppl;
const ll N = 2001, M = 2 * 1e5 + 1;
ll n, m;
class ps {
	friend ostream& operator <<(ostream& out, const ps& s);
	friend istream& operator >>(istream& in, ps& s);
public:
	ps(int _p1=1,int _p2=2,int _p3=3)
		:p1(_p1)
		,p2(_p2)
		,p3(_p3)
	{}
private:
	int p1, p2, p3;
};
ostream& operator <<(ostream& out, const ps& s)
{
	out << s.p1 << " " << s.p2 << " " << s.p3;
	return out;
}
istream& operator >>(istream& in,ps& s)
{
	in >> s.p1 >> s.p2 >> s.p3;
	return in;
}//如果是自定义类型,要记得为其重载>>和<<
struct pt {
	//string s;不能用string,因为string内部针对字符串,短的存数组,长的会在堆上开辟空间,
	// 这样的话二进制读写的时候,如果字符串长的话,读写的内容只是string内部存字符串的指针,而进程结束后,旧指针所指向的空间是被释放的。
	//这样就算二次读写进来,解引用该指针,访问的是野指针,就会报错。
	//因此涉及到深拷贝的,不能进行二进制读取
	char a1[32];
	int s;
	ps da;
};
class ALLRW
{
public:
	ALLRW(const char* filename)
		:_filename(filename)
	{}
	//二进制写出到文件,本质就是强行将自定义类型的指针强转成const char*,然后以一个字节一个字节写出。
	void writebinary(const pt& ob)
	{
		ofstream ofs(_filename, ios::out | ios::binary);
		ofs.write((const char*)&ob, sizeof(ob));
	}
	//二进制文件写入到程序,本质就是强行将自定义类型的指针强转成char*,然后以一个字节一个字节写入。
	void readbinary( pt& ob)
	{
		ifstream ifs(_filename, ios::in | ios::binary);
		ifs.read((char*)&ob, sizeof(ob));
	}
	//从程序写出到文本文件
	void writetext(const pt& ob)
	{
		ofstream ofs(_filename, ios::out);
		ofs << ob.a1 << endl;//记得加换行,因为>>不读空格和换行,否则的话txt文件里内容是挤在一起的,第一个>>会全部读完,第二个>>只能读个随机值
		ofs << ob.s << endl;
		ofs << ob.da << endl;
	}
	//从文本文件写入到程序
	void readtext( pt& ob)
	{
		ifstream ifs(_filename, ios::in);
		ifs >> ob.a1 >> ob.s >> ob.da;
	}
	
private:
	string _filename;
};

int main()
{
	ALLRW v("test.txt");
	pt x = { "2222222",2,{2,3,4} };
	v.writetext(x);
	pt x2;
	v.readtext(x2);
	cout << x2.a1 << " " << x2.s << " " <<x2.da<< endl;

	return 0;
}

另外,c++io流还提供了个一些标志位,用于表示各种流操作的状态。

如果发生了fail错误,这种错误一般是可以重新来的。比如

cpp 复制代码
cin>>i;

//发生fail错误

cin.clear();//清除标志位,一定要先清标志位,因为流操作前会先检查标志位。

cin.get();//注意,必须先读一个,因为之前fail错误,导致输入缓冲区的内容没有被读走
//如果此时直接cin>>i,会出现不等我们输入,又重新试图读缓冲区的内容
//但是因为缓冲区的内容是不合法的,必然又导致了fail错误。
//所以先读光缓冲区,再输入

cin>>i;

4.stringstream的简单介绍

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

1.使用itoa()函数

2.使用sprintf()函数

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

cpp 复制代码
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类对象来避开此问题。

在程序中如果想要使用stringstream,必须要包含头文件。在该头文件下,标准库三个类:istringstream、ostringstream 和 stringstream,分别用来进行流的输入、输出和输入输出操作。

stringstream的作用如下

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

cpp 复制代码
#include <iostream>
#include <sstream>
using namespace std;

int main()
{
    int a = 12345678;
    string sa;
    
    // 将整型变量转换为字符串
    stringstream s;
    s << a;
    s >> sa;
    
    // 清除状态和内容,为下一次转换做准备
    s.str("");  // 清空底层字符串
    s.clear();  // 清除错误状态
    
    // 将双精度浮点数转换为字符串
    double d = 12.34;
    s << d;
    s >> sa;
    
    // 获取stringstream中管理的字符串
    string svalue = s.str();
    cout << svalue << endl;
    
    return 0;
}

w

4.2.字符串拼接

cpp 复制代码
#include <iostream>
#include <sstream>
using namespace std;

int main()
{
    stringstream sstream;
    
    // 将多个字符串放入 sstream 中
    sstream << "first" << " " << "string,";
    sstream << " second string";
    cout << "strResult is: " << sstream.str() << endl;
    
    // 清空 sstream
    sstream.str("");
    sstream << "third string";
    cout << "After clear, strResult is: " << sstream.str() << endl;
    
    return 0;
}

w

4.3.序列化和反序列化结构数据

cpp 复制代码
struct ChatInfo
{
    string _name;  //名字
    int _id;       // id
    Date _date;    // 时间
    string _msg;   //聊天信息
};

int main()
{
    //结构信息序列化为字符串
    ChatInfo winfo = {"张三", 135246, {2022,4,10}, "晚上一起看电影吧"};
    
    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;
}

w
1.stringstream实际是在其底层维护了一个string类型的对象用来保存结果。

2.多次数据类型转化时,一定要用clear()来清空,才能正确转化,但clear()不会将

stringstream底层的string对象清空。

3.可以使用s.str("")方法将底层string对象设置为"空字符串。

4.可以使用s.str()让stringstream返回其底层的string对象。

5.stringstream使用string类对象代替字符数组,可以避免缓冲区溢出的危险,而且其会对参数类型进行推演,不需要格式化控制,也不会出现格式化失败的风险,因此使用更方便,更安全。

相关推荐
ygklwyf2 小时前
JPRS编程竞赛2026#1(AtCoder初学者竞赛442)
c++·算法·模拟
王老师青少年编程2 小时前
信奥赛C++提高组csp-s之倍增算法思想及应用(3)
c++·noip·csp·信奥赛·csp-s·提高组·倍增算法
学嵌入式的小杨同学2 小时前
【嵌入式 Linux 实战 1】Ubuntu 环境搭建 + 目录结构详解:嵌入式开发入门第一步
linux·c语言·开发语言·数据结构·vscode·vim·unix
⑩-2 小时前
JUC-场景题
java·开发语言
a程序小傲2 小时前
京东Java面试被问:基于Gossip协议的最终一致性实现和收敛时间
java·开发语言·前端·数据库·python·面试·状态模式
tqs_123452 小时前
Spring Boot 的自动装配机制和 Starter 的实现原理
开发语言·python
程序员小白条2 小时前
面试 Java 基础八股文十问十答第二十二期
java·开发语言·数据库·面试·职场和发展·毕设
编程大师哥2 小时前
JavaScript 和 Python 哪个更适合初学者?
开发语言·javascript·python