c++学习之IO流

发布时间:2024年01月22日

目录

前言:

一,流的概念

二,c++的io流

输入输出流

缓冲区的同步

文件流

文件的打开

文件读写自定义类型数据

字符流

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

2. 字符串拼接

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


前言:

在了解c++的输入输出流之前,我们先来看看c语言的io流,c语言中是利用printf与scanf进行输入输出,中间利用缓冲区实现对数据的高效处理,

对于缓冲区这个我们在linux中已经了解到,所谓的printf与scanf都是底层函数open和write的封装,目的就是提供有缓冲区,使得更加高效的进行读写。

?对于输入输出缓冲区:
? 1.可以 屏蔽掉低级 I/O 的实现 ,低级 I/O的实现依赖操作系统本身内核的实现,所以如果能够屏
蔽这部分的差对于异,可以很容易写出可移植的程序。
2.可以 使用这部分的内容实现 读取的行为 ,对于计算机而言是没有 ”这个概念,有了这
部分,就可以定义“ 的概念,然后解析缓冲区的内容,返回一个 ”。??

一,流的概念

所谓流就是流动的意思,物质从一方向流动到另一方向,是对一种有序连续具有方向性的数

据( 其单位可以是bit,byte,packet )的抽象描述。
所以输入输出流就是输入设备(如键盘)向内存输入数据,内存向输出设备(如显示器)输出数据,这个输入输出的过程就像流一样,主要也是因为有缓冲区作为流的介质。

二,c++的io流

对于c++,c++在兼容c语言的输出输出的情况下,自己也实现了一套完整的输入输入流库,在这个库中ios_base为基类,派生出了许多子类也就是输入,输出等。

其功能与c语言的输入输出功能基本一致,不过c++利用自己面向对象的特点搞了一套,因此我们学习c++的io流可以对标c语言来看:

输入输出流

ostream类是继承了父类basic_ios,其中也包含了需要成员方法,不过我们一般就用一个重载的<<,进行输出。

ostream对象的定义:

?ostream对象不能直接实例,上面是其构造的参数。

在输出流中,定义了三个输出对象:

以输出流为例:?

int main()
{
	//输出流类 ostream
	ostream out(nullptr); 
	//我们用的cout cerr clog 都是ostream的对象
	cout << "hello c++"<<endl;
	clog << "hello c++"<<endl;
	cerr << "hello c++"<<endl;

	//其中关与ostream的成员方法也是有很多的
	cout.put('x');
	cout.put(48);
	cout.write("hello", 5);
	cout.precision(5);//设置浮点小数的精度为5
	return 0;
}

对于ostream与istream我们最多也都是用的他的流插入(<<)与流提取(>>), 当然他们也有很多成员方法,在涉及输入输出的详细操作时,大家可以搜索来看。

int main()
{
	string str;
	while (cin >> str)
	{
		cout << str << endl;
	}
}

其次还有一点就是我们在算法题时,可能会遇到多组输入数据的情况,在这里涉及到输入流的返回值的问题,实际上,cin>>str的返回值还是一个cin输入流对象每次是他要作为bool判断就会调用operator bool来实现类型的转换。

在c++中,ostream是输入,istream是输出,通过继承输出输出,还得到了既可以输入也可以输出的输入输出流类iostream。?

缓冲区的同步

由于c++兼容c语言,二c++与c语言都有自己各自一套的输入输出流,两者也有各自的缓冲区,在使用时,有时候我们不管你是谁的缓冲区的,我们希望他们自己能相互兼容,比如输入整数,用cin与scanf都去输入,我们希望cin输入一个数,scanf输入一个数,他们是不一样的,之后在利用不同的输出,输出数据,不论谁的缓冲区,相互都可以输出:

int main()
{
	int i, j;
	cin >> i;
	scanf("%d", &j);
	printf("%d ",i);
	cout << j << endl;
	return 0;
}

因此c++为了兼容c的输入输出,兼容了c的缓冲区。当然我们也可以关闭掉这个缓冲区的兼容,

?通过调用sync_with_stdio(false)关闭缓冲区的兼容,在vs的平台下没什么问题,但不同的平台处理是不一样的。

文件流

?在c语言中主要用三组函数,fputc/fgetc像文件读写字符,fread/fwrite,读完文件与写文件,fscanf/fprintf格式化输入输出。

对于文件部分的读写也是与输入输出类似的:

文件输入类?ifstream? ,文件输出类 ofstream, 文件输出输出类 fstream。与输入输出流类似,文件流也有对应的文件缓冲区filebuf.

在c++中,由于面向对象的特性,这里都是类,主要的两个类文件输入ifstream与文件输出ofstream这两个类,其它向文件读写字符,字符串,格式化读写都是成员方法,c++提供了许多接供我们使用。

文件的打开

不过最基本的我们还是从创建文件来看,文件的打开有两种方式:

1.通过文件输入流或者文件输出流的构造函数来选择性地打开文件,

2.通过调用open方法代开文件,

文件打开的方式如下图:

?in以读的方式打开文件,out以写的方式打开文件,binary以二进制方式读写文件,ate在文件末尾出输出,app在文件末尾处追加,trunk,截断,在打开前清空文件内容。

通过将两项|(或)的方式,就可以实现和c语言的r,w,a的方式打开文件。

以ifstream为例:

int main()
{
	ifstream fp("test.txt", ifstream::out |ifstream::app);
    ofstream fd("test.txt",   ofstream::out|ofstream::app);
	fd.write("hello c++", 10);
	char* str=new char(10);
	fp.read(str,10);
	cout << str << endl;
	return 0;

}

文件读写自定义类型数据

在c语言中可以使用fscanf与fprintf进行数据的读写,通过%d,%s等识别数据类型来向文件读写,在c++中没有对标的方法,而是全都放到fstream的流插入(<<)与流提取(>>)当中,通过不同的构造参数,调用不同的函数进行文件读写。

当然这也是为了自定义类型向文件读写时,通过重载<<与>>实现文件读写,也统一了文件读写数据可以都用 fstream的 流插入与流提取。

对于c++还是c语言向文件里读写数据其实都只有两种方式:

1.二进制读写。(字节流读写)

2.文本读写。

先以二进制读写为例:

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;
};

//我们向文件读写的自定义数据
struct severInfo
{
	severInfo()
	{}
	severInfo(char str[], double x, Date tmp) :address(str), x(x), date(tmp)
	{}
	char* address;
	double x;
	Date date;
};
//进行读写
class IO
{
public:
	IO(const string file)
	{
		filename = file;
	}

	void write(const severInfo& tmp)
	{
		//向文件以二进制方式写入 
		ofstream fp(filename, ofstream::binary | ofstream::out);
		fp.write((char*)&tmp, sizeof(tmp));//取地址转为char* 二进制写入
	}
	void read(const severInfo& tmp)
	{
		//以读的二进制方式打开文件
		ifstream fp(filename, ifstream::binary | ifstream::in);
		fp.read((char*)&tmp, sizeof(severInfo));

	}

private:
	string filename;
};


int main()
{
	IO test("test.txt");
	severInfo tmp((char*)"192.168.1.1", 10.5, Date(2023, 1, 1));
	//此时写入二进制数据到文件当中
	test.write(tmp);
	severInfo info;
	//将读来的二进制数据存放会info中
	test.read(info);
	return 0;
}

通过存入数据的二进制(地址),在以二进制方式读取出来,实现自定义类型的读写。

但文件中是二进制数据。

其次二进制读写数据要注意不要用,数据不要用容器,因为如果时容器的话,一般封装起来不仅仅是我们要的数据,如string 里面还有capacity, size等参数,一般二进制读取数据就是内置类型的数据,或者封装起来的内置类型的数据。

文本读写

文本读写的本质就是转化成字节流,即字符串。因此我们需要很多的类型转化,好处就是文件可以看得见,坏处就是更加麻烦。

在c语言我们可以使用sprintf,sscanf将其他类型转化为字符串,c++中使用to_string来转化。

class Date
{
	friend ostream& operator << (ostream& out, const Date& d);
	friend istream& operator >> (istream& in, Date& d);

	//friend ofstream& operator << (ofstream& out, const Date& d);
	//friend ifstream& operator >> (ifstream& 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;
}

//ofs << winfo._date;
ostream& operator << (ostream& out, const Date& d)
{
	out << d._year << " " << d._month << " " << d._day;
	return out;
}

//我们向文件读写的自定义数据
struct severInfo
{
	severInfo()
	{}
	severInfo(char str[], double x, Date tmp) :address(str), x(x), date(tmp)
	{}
	char* address;
	double x;
	Date date;
};

class TEXTIO
{
public:
	TEXTIO(const string file)
	{
		filename = file;
	}

	void write(const severInfo& tmp)
	{
		//向文件文本方式写入 
		ofstream fp(filename, ofstream::out | ofstream::trunc);
		fp << tmp.address<<endl;
		fp << tmp.date<<endl;
		fp << tmp.x << endl;
	}
	
	//这里自定义类型Date需要重载>> 和 <<istream& operator >> (istream& in, Date& d)
	//这里的ofstream与ifstream都是继承istream与ostream
	void read(const severInfo& tmp)
	{
		ifstream fp(filename);
		fp >> tmp.address;
		fp >> tmp.x;
		fp >> tmp.date;
	}

private:
	string filename;
};


int main()
{
	severInfo info((char*)"192.168.1.1", 1.1, Date(2023, 1, 1));
	TEXTIO test("test.txt");
	//文本写
	test.write(info);
	severInfo tmp;
	//文本读
	test.read(tmp);
}

字符流

C 语言中,如果想要将一个整形变量的数据转化为字符串格式,如何去做?
1. 使用 itoa() 函数
2. 使用 sprintf() 函数
但是两个函数在转化时,都得 需要先给出保存结果的空间 ,那空间要给多大呢,就不太好界定,
而且 转化格式不匹配时,可能还会得到错误的结果甚至程序崩溃 。‘

对标于c语言的sprintf与sscanf,字节流的输入输出,c++也提供了字节流的输入与输出。

istringstream为字符输入,ostringstream为字符输出,stringstream是继承了输入输出,stringbuf为字符缓冲区。

使用方法与上面的文件流类似,当然字符流中的输入输出也都提供了一系列的成员方法,

如str,=等。除了自身的成员函数,当然继承的也可以使用。

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

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

int main()
{
	
	stringstream str;
	int a = 10;
	string tmp;
	//将其他类型的数据输出为字符型
	str << a;
	//再将该数据输入到string对象中
	str >> tmp;
	cout << tmp << endl;
	cout<<str.str();//返回当前字符流的副本
	str.clear();//清空字符缓冲区


	return 0;
}

2. 字符串拼接

给当前字符输入其他字符,使得字符串拼接起来:
int main()
{
	stringstream str;
	// 将多个字符串放入 str 中
	//先将两个字符连接起来,即都输出到str当中
	str << 20;
	str << "个苹果";
	str << 10;
	str << "个香蕉";
	cout << "开始" << str.str() << endl;
	str.str("");//修改副本为空字符
	str << 5;
	str << "个橘子";
	cout << "最后" << str.str() << endl;
	return 0;
}

可以看到对于stringstream,它自身对象就是一个缓冲区,临时字符串,可以拼接我们输出的字符

,对于内置类型,使用str方法获取它里面的临时字符串,当然也可以此时输入到一个字符串对象当中,用以存储。

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

所谓的序列化与反序列化其实就是将其他类型包括自定义类型的数据转化为字符串,或者反之,将字符串转化为对应类型的数据。
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;
	return out;
}
struct ChatInfo
{
	string name; 
	int 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;

	//反序列化
	// 字符串解析成ChatInfo对象
	ChatInfo tmp;
	istringstream iss(str);
	iss >> tmp.name >> tmp.id >> tmp.date >> tmp.msg;
	return 0;
}
注意:
1. stringstream 实际是在其底层维护了一个 string 类型的对象用来保存结果
2. 多次数据类型转化时,一定要用 clear() 来清空,才能正确转化 ,但 clear() 不会将
stringstream 底层的 string 对象清空。
3. 可以 使用 s. str("") 方法将底层 string 对象设置为 "" 空字符串
4. 可以 使用 s.str() 将让 stringstream 返回其底层的 string 对象
5. stringstream 使用 string 类对象代替字符数组,可以避免缓冲区溢出的危险,而且其会对参
数类型进行推演,不需要格式化控制,也不会出现格式化失败的风险 ,因此使用更方便,更
安全。

?

文章来源:https://blog.csdn.net/qq_61422622/article/details/135725802
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。