C++当中的IO流介绍 - operator 类型()的特殊用法

C语言当中的IO流

C++当中的IO流介绍 - operator 类型()的特殊用法_第1张图片 C语言中我们用到的最频繁的输入输出方式就是 scanf () printf()。 scanf():  从标准输入设备(键盘)读取数据,并将值存放在变量中。printf(): 将指定的文字/字符串输出到标准输出设备(屏幕)。

注意:宽度输出和精度输出控制。C语言借助了相应的缓冲区来进行输入与输出。

C++当中的IO流介绍 - operator 类型()的特殊用法_第2张图片

 关于 C 当中输入输出函数,对应 C++ 当中的输入输出流如下所示:

C++当中的IO流介绍 - operator 类型()的特殊用法_第3张图片

 对输入输出缓冲区的理解:

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

C++为什么要设计自己的IO 

C当中的输入输出就已经很够用了,C++为什么要自己设计 IO流呢?

原因就是,为了自定义类型的输入和输出。

C++ 是 面向对象的语言,就会诞生出很多自定义类型的输入和输出,而对于 自定义类型的输入和输出,普通的 printf 和 scanf 已经不够用了,比如 日期类的输入和输出,我们手动去用 printf 和 scanf 的话非常的麻烦,所以,C++ 当中就可以支持对  operator<<()  和 operator>>() 这两个流提取 和 流插入 操作符进行 重载。使用者可以根据自己的写的自定义类型,自定义这个类型的输入方式和输出方式。

C++当中的IO流介绍 - operator 类型()的特殊用法_第4张图片

还有,如使用 printf 和 scanf 函数来输入输出自定义类型当中的成员的话,如果成员是 public 修饰的就还好,如果是 private私有的,在外部如何访问?

从而,C++ 为了能更好的 和控制台进行交互,就自己实现了一套 IO流。

 而在我们看来,cout 和 cin 对内置类型和已经重载的自定义类型,进行自动识别 从而进行 输出 或者输入,在本质上就是每一个 类型 都重载了 operator<<()  和 operator>>() 所以,从我们使用上来看,就好像是自动识别一样。

 而且,operator<<()  和 operator>>()   更形象化,比如 cout << n ; 就是把 n 这个变量流入到 屏幕上。

C++ while(cin >> n)返回值问题(operator()的一种特殊用法)

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

相信你在写 OJ 题当中经常遇见这种情况,这种情况,当你输入任何一个字符串,回车都会输出这个字符串,然后紧接着你还可以输入字符串给 str:

C++当中的IO流介绍 - operator 类型()的特殊用法_第5张图片

 如果你想退出的话,可以输出 ctrl + z 然后 回车,就可以退出了。注意不能使用 ctrl + c 的方式退出,因为 这种方式退出的是程序的直接退出,不是正常的退出。

究竟是怎么实现的,就可以看 cin >> str 这个表达式的返回值是什么了。

cin >> str 本质是是调用 operator>>(cin , str) 这个函数,所以,我们要看这个函数的返回的值:
 

返回值是一个 istream 对象,istream 在这个其实就是这个 cin,因为 cin 是通过 传引用传入函数的,所以,返回值还是这个 cin;


cin 在本质上是不能给 while 循环 判断的。 但是有一个操作是可以进行判断:

在 C++98 当中是使用 opeartor!()这个函数来实现的,但是在 C++11 当中是使用          operator bool()这个函数来实现的。

 你可能有疑问,bool 是可以重载的吗?答案是肯定的。

先看下述例子:
 

class A
{
public:
	A(int a)
	{
		_a = a;
	}
private:
	int _a = 1;
};

int main()
{
	A aa1(1);
	A aa2(2);
	
	// 内置类型转换为 自定义类型 (隐式类型转换,可以使用构造+拷贝构造实现对 aa1的赋值)
	aa1 = 2;

	// 自定义类型转换为 内置类型(因为类型不相似,不能发生隐式类型转换)
	int n = aa2; // 报错

	return 0;
}

上述的 int n = aa2 这个操作是不行的,此处要发生类型的转换,要是用C++ 的四种类型转换之一的 reinterpret_cast 来转换。

如果我们想使用 (int)aa2 的方式强制转换的话,需要重载 operator()这个运算符函数。C语言当中能强转的运算符是 "()" ,所以我们期望对这个 operator()重载。但是,此处的  ()不能当中是 用于强转的运算符,此时这个运算符(operator())已经被 仿函数当中的用法给占用了:

class Less
{
public:
	int operator() (int x, int y)
	{
		return x < y;
	}
};

int main()
{
	Less less;
	int x = 10, y = 20;

	cout << less(x, y) << endl;

	return 0;
}

此时,operator()重载的是上述 "()" 小括号的使用方式,但是我们现在想使用的方式是在 (int)x ,在变量前使用的方式,这样的话两者就冲突了,所以,C++在这里进行了特殊处理,也就是一种特殊用法
 

	operator int()
	{
		return _a;
	}

上述看似是重载了 类型 int 其实是重载了 在变量左边使用的 "()" 。

完整代码:
 

class A
{
public:
	A(int a)
	{
		_a = a;
	}

	operator int()
	{
		return _a;
	}

private:
	int _a = 1;
};

int main()
{
	A aa1(1);
	A aa2(2);
	
	// 内置类型转换为 自定义类型 (隐式类型转换,可以使用构造+拷贝构造实现对 aa1的赋值)
	aa1 = 2;

	// 自定义类型转换为 内置类型 (因为重载了 左括号 “()”所以可以执行)
	int n = (int)aa2; 
 
	return 0;
}

此时,我们就重载了 在变量左边使用的 “()”运算符。

 注意上述的 operator int () 并不是没有指定返回值,int 就是这个函数的返回值,当然,你还可以写成 operator double () 各种类型的模式,代表的就是 不同类型的强转的返回值。 


此时你应该就理解了,operator bool () 这个函数的实现意义了,在while(表达式),这个式子当中,表达式的返回值必须是 bool 类型,或者是 0 表示 假,非零表示真;但是while的表达式 接收还是一个 bool 类型。

所以 while(cin >>str ) 就发生了像上述的强转,因为 在 istream 的 父类 ios 类 当中重载 了 operator bool()函数,可以发生强转。


C++当中的IO流介绍 - operator 类型()的特殊用法_第6张图片

 但是如上所示,在库当中实现的 operator bool () 函数,使用 explicit 关键词修饰的,我们知道,explicit 关键字修饰的函数,是不能发生 类型转换的。但是,operator bool () 是可以转换的(但是必须是显示的进行转换)
 

C++当中的IO流介绍 - operator 类型()的特殊用法_第7张图片

 如上所示,程序正常运行。

其实在这个地方,加上 explicit 就是限制了 operator int ()  这个函数的隐式类型转换,如下所示,进行隐式类型转化的时候会报错,也 operator int ()加上了 explicit 修饰:

C++当中的IO流介绍 - operator 类型()的特殊用法_第8张图片

但是,如果是进行显示类型转换(强制转换)的话,还是可以调用到 opeartor int () 进行强转的:
 

C++当中的IO流介绍 - operator 类型()的特殊用法_第9张图片

 这个你就需要注意了,我相信你现在可能有疑问,这个 (int)aa1 不是C 当中的强制类型转换吗 ?和 operator int( )函数有什么关系呢?

如果我们没有 operator int ()这个函数的话,直接 (int) aa1 是会报错的:

C++当中的IO流介绍 - operator 类型()的特殊用法_第10张图片

具体原因请看上述对于为什么要重载 operator int ()介绍。


 还需要注意 operator bool () 和 其他类型的 operator 类型 () 可能会冲突,比如 operator bool ()和 operator int ():

C++当中的IO流介绍 - operator 类型()的特殊用法_第11张图片

这是为什么呢?我们现在先只用 operator bool ()函数来做强转试试:

C++当中的IO流介绍 - operator 类型()的特殊用法_第12张图片

 发现,aa1 当中的 _a 成员变量此时的值应该是 10, 但是 n 却输出了1,注意,n不是 bool 类型的,而是  double 类型的。

我们调试查看,n 和 aa1 当中的 _a 成员变量的值是多少:

 

C++当中的IO流介绍 - operator 类型()的特殊用法_第13张图片

发现只是 n 的值接收为 1,aa1 当中 _a 的值就是 10 。因为 是按照bool 输出的,0 即为假,非0 即为真。

对于 operator int ()的例子,这里不在做演示,直接告诉结果,n 输出就是 10,和 aa1 当中的 _a 成员变量输出结果是一样的。

 这里举例说明是要让读者清楚,operator bool ()和 operator int () 是会使用冲突的,如果你两个函数都实现了,当前情况下,指向使用 其中某一个函数的话,可以考虑使用 explicit 修饰其中一个你不想用的函数。


 还需要注意的是,在 IO 当中,有四个类是 菱形继承的关系:

C++当中的IO流介绍 - operator 类型()的特殊用法_第14张图片

 这也是 C++ 的搞出 菱形继承之后,大胆的在自己的官方库当中使用 这个大坑。

C++ 的文件 IO流 

 C 当中,根据文件不同的数据格式分为二进制文件 和 文本文件,同样具有 二进制的输入方式和 文件的输入文件的方式。

下面是一个日期类以二进制访问输出到文件当中:
 

	Date d(2023, 10, 14);
	FILE* fin = fopen("file.txt", "w");
	fwrite(&d, sizeof(Date), 1, fin);
	fclose(fin);

但是以二进制的方式输入到文件当中是不好的方式,因为 输入到文件当中,我们是看不懂的,所以我们还是要以 文本的方式输入的到文件当中:
 

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)
	{}

	// 简单实现一个 把日期类当中的数据写到 一个 string当中
	operator string()
	{
		string str;
		str += to_string(_year);
		str += ' ';
		str += to_string(_month);
		str += ' ';
		str += to_string(_day);
		return str;
	}

	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(2023, 10, 14);
	FILE* fin = fopen("file.txt", "w");
	string str = d;
	fputs(str.c_str(), fin);
	return 0;
}

像上述,我们简单的实现了一个 operator string ( )函数,把 日期当中的成员 强转为 string 的时候,就把 成员的内容尾插到 string 当中存储。

但是,上述的 用C 解决方式还是太麻烦了。


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

  •  定义一个文件流对象(ifstream ifile(只输入用)
                                         ofstream ofile(只输出用)
                                         fstream iofile(既输入又输出用))
  •  使用文件流对象的成员函数打开一个磁盘文件,使得文件流对象和磁盘文件之间建立联系
  •  使用提取和插入运算符对文件进行读写操作,或使用成员函数进行读写
  •  关闭文件

我们更多的是使用 fstream 这个流,因为这个既可以输入又可以输出。 

 C++ 当中关键的两个文件输入输出流,ifsream 和 ofstream 都是都是各自继承了 istream 和 ostream:

C++当中的IO流介绍 - operator 类型()的特殊用法_第15张图片


文件 IO流使用介绍

我们这里就直接使用 fstream 来演示了。

要使用 fsream 就要引入头文件:

#include

 C++ 当中使用 fstream 有两种使用方式:

open 函数:

C++当中的IO流介绍 - operator 类型()的特殊用法_第16张图片

 这种方式就和 C语言当中的使用方式是类似的,open()函数的第一个参数是 文件名,支持用 C 当中的字符串的形式传入,也支持使用 string 对象的方式传入;第二个参数文件使用的方式,这个就和  C 当中的使用方式是一样的了:

除了可以使用 C当中 类似 'r'  'w' 这样的方式来确定读取文件的方式,还可以使用 C++ 当中独有的方式,具体请看下面一种方式 当中的详细介绍。

构造函数的方式使用:

其实上述的open 函数的使用方式和 C 当中差别不大,看不出 C++ IO流的提升,所以,我们更多使用的方式是使用构造函数的方式来使用
 

	fstream ofs("text.txt", ios_base::out | ios_base::in);

C++当中的IO流介绍 - operator 类型()的特殊用法_第17张图片

 同样支持 字符创 和 string 两种方式的文件名传入,第二个参数也是 文件的读取操作,文件读取方式,默认缺省值是  out 也就是 覆盖写的方式读取文件

在C++当中,对于文件的读取方式有做了进一步的 修改,有一下几种文件的读取方式

C++当中的IO流介绍 - operator 类型()的特殊用法_第18张图片

成员常量 代表 access
in input 文件打开读取:内部流缓冲区支持输入操作。(读文件)
out output 文件打开写入:内部流缓冲区支持输出操作。(覆盖写文件)
binary binary 操作以二进制模式而不是文本模式执行。(以二进制的方式操作文件)
ate at end 输出位置从文件末尾开始。(追加写文件)
app append 所有输出操作都发生在文件的末尾,并附加到其现有内容。
trunc truncate 文件打开之前存在的任何内容都将被丢弃。

 上述的操作同样的是可以一起使用的,如果你想把多个 操作一起使用的话,可以在不同操作之间加 "|" 来连接。

那么,向上述的 out ,ate , app 这些操作,是如何实现用 "|" 来区分的呢

实际上是使用 1,2,4,8 这样的方式来区分的:

C++当中的IO流介绍 - operator 类型()的特殊用法_第19张图片

 这样的话,每一个操作代表一个 二进制位为1 的值,那么两者 的 "|" 的结果,不同的操作 "|" 出来时不一样的结果。

 IO流对象的释放

fstream 当中是支持了 fclose ()这个文件关闭的函数的,但是,实际使用方式我们是不用显示的写 fclose 这个函数的。

因为 fstream 是一个类,这个类当中是有析构函数的,在这个析构函数当中就调用了 fclose()函数,当 这 fstream 类对象出了自己的作用之后,就会自动地调用析构函数。

写入文件

 fstream 当中就有 write()这个函数,用于写入文件内存:

	Date d(2023, 1, 1);

	fstream ofs("text.txt", ios_base::out|ios_base::binary);
	ofs.write( (const char*) &d, sizeof(d));

如下就是以 二进制的方式写。

但是,如果是使用文本大方式来写的话,不用像上述一样使用 write()函数,可以直接使用 "<<":

	Date d(2023, 1, 1);

	fstream ofs("text.txt", ios_base::out|ios_base::binary);

	ofs << d;

这里的 Date 能使用 "<<" 的原因是,Date 当中重写了 operator<<() 这个函数:如果,没有重写这个函数是不能使用 "<<" 的方式写入文件的:

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

这里,是 ostream 类 的 out 传给了 fstream 类的 ofs,其实使用使用多态的原理,我们在上述也介绍过,ifsream 和 ofstream 都是都是各自继承了 istream 和 ostream的。

C++当中的IO流介绍 - operator 类型()的特殊用法_第20张图片

一个二进制 和 文本方式 读写的  简单IO类实现

#include

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)
	{}

	// 简单实现一个 把日期类当中的数据写到 一个 string当中
	operator string()
	{
		string str;
		str += to_string(_year);
		str += ' ';
		str += to_string(_month);
		str += ' ';
		str += to_string(_day);
		return str;
	}

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

struct ServerInfo
{
	// 注意这里 不建议 用 string vector 这样的自定义对象
	char _address[32];
	
	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; // 配置文件
};

int main()
{
	ServerInfo winfo = { "192.0.0.1", 80, { 2022, 4, 10 } };

	// 二进制读写
	ConfigManager cf_bin("test.bin");
	cf_bin.WriteBin(winfo);
	ServerInfo rbinfo;
	cf_bin.ReadBin(rbinfo);
	cout << rbinfo._address << " " << rbinfo._port << " "
		<< rbinfo._date << endl;

	// 文本读写
	ConfigManager cf_text("test.text");
	cf_text.WriteText(winfo);
	ServerInfo rtinfo;
	cf_text.ReadText(rtinfo);

	cout << rtinfo._address << " " << rtinfo._port << " " <<
		rtinfo._date << endl;

	return 0;
}

上述是,往文件当中,或者是从文件当中 以 二进制 和 文本的两种方式 写入 和 读取 ServerInfo 类的类对象。

在ServerInfo类当中,有三个成员,一个是 主体数据 Date 类的数据,还有 point ,和 地址。

实现也非常简单,就是实现四个函数分别是 二进制的 读写 和 文本形式的读写。

其中需要注意的点是,关于地址 adress 这个字符串,我们输入的是 ip 地址,是一个字符串,你可以发现,我们使用 一个 字符数组的形式存储的,为什么不用 string  vector 这些容器存储呢?

因为,

用这个例子就是想体现出 C++ 当中的 IO流 文件输入和输出,对于 C当中的文件输入输出函数 两者之间的对比。

很明显是 C++ 当中更加方便,我们可以发现,对于文本输入输出,在拿到 文件对象之后(fstream),如果是内置类型,和已经重载了 operator<<() 和 operator>>() 的自定义类型 都可以直接使用 流插入 和 流提取 来 从提取数据 或者 输入 数据到 文件当中,但是对于没有重载  operator<<() 和 operator>>() 的自定义类型 就不能使用了;老老实实的使用 write()和 read()函数吧。

但是一般来说,一个类 对于 operator<<() 和 operator>>() 函数的需求还是挺大的,这也是 C++ 要单独设计 IO流 的初衷。


还需要注意的是,不管是 C++ 当中的 fstream 的读取,还是 C 当中的 fsacnf(),他们读取文件当中的数据都是按照 空格 "\n" "EOF" 这些进行区分数据的,也就是作为本次读取 结束标志。所以,我们在 往文件当中输入 数据的时候,一定要记得对 不同的数据 要用 空格分隔开(按照上述例子当中是文件输入一样),这样,不仅仅我们可以更方便的区分数据,对于读取文件当中的数据也是非常好的。

使用 ifstream 当中的 get()函数快速读取数据

C++当中的IO流介绍 - operator 类型()的特殊用法_第21张图片

 get()函数可以一次读取一个字符,我们可以用一个 char 变量,传入 get()函数,get()当中是用 & 的方式接收的,在其中就会对这个 char 变量进行赋值,赋值成当前取出的字符。

get()函数返回值是一个 istream对象的引用,那么我们就可以利用这个特性,也 istream 继承了 ios ,在 ios 当中重载 operator bool ()这个函数,此时就会发生,如果 在get()返回时候 有是有 不同类型的 接收的话,就会进行 隐式类型转换。在文件当中如果没有读到 EOF结束的话,都是返回 非0 ,也就是bool 当中的 true。当访问到 EOF的话,就会返回0 ,强转为 bool 的话 0 就是 false 了。

int main()
{
	ifstream ifs("text.cpp");
	char ch;
	while (ifs.get(ch))
	{
		cout << ch;
	}

	return 0;
}

此时,在屏幕上打印的就是文件当中的内容啦:

C++当中的IO流介绍 - operator 类型()的特殊用法_第22张图片

 sstream (stringstream)流介绍和使用

 因为 string  和 字符串 这些使用的频率非常高,所以,在C++ 的流当中对 字符串 也单独写了一个IO流出来:

sstream 当中的 流也是直接从 控制台流当中 直接继承过来的:

C++当中的IO流介绍 - operator 类型()的特殊用法_第23张图片

 其中的 istringstream 和 ostringstream 就分别对应了 输入 和 输出。

ostringstream 

ostringstream 就是 string 的流插入,把一个string 对象插入到 string 流当中。 

我们先来简单使用一下 ostringstream

#include

int main()
{
	Date d1(2023, 3, 3);
	ostringstream oss;
	oss << d1;

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

	return 0;
}

ostringstream 当中有一个 str 函数可以帮助我们 取出 oss 这个 ostringstream 对象当中的 string 对象存储的内容,返回值就是一个 string 对象。

像上述就是 因为 在 Date 类当中我们实现了 opeartor<<() 这个函数,所以,可以直接使用 我们实现的 ostream& operator << () 这个函数来实现 流插入,因为 ostringstream 是 ostream 的派生类,所以,oss << d1 发生了多态,oss 传参,但是 函数当中使用 ostream& 这个形式来接收的,发生向下转型,子类进行切片。

Date 当中的 ostream& operator << ()  这个函数就把 Date 当中的成员变量的数据转化为 string 返回给 一个 ,函数当中 ostream& 的这个形参,也就是现在传入的 oss 的引用。

在 Ostringstream 类的 oss 当中有一个 string对象,用于存储 接收到的 在函数当中返回的 string 对象。

所以,我们才在下面使用 str()函数接收到 返回的 string类的对象。


其实无论是 控制台流 istream,文件流 fstream ,还是字符串流 sstream,都是把 要输出输入的数据转化成字符串来输出输入的:

C++当中的IO流介绍 - operator 类型()的特殊用法_第24张图片

 只不过在上述 函数当中的 out 形参接收的是 控制台流 istream 对象,就输出到 控制台当中,是 文件流 fstream 就输出到文件当中 ,是 字符串流 sstream 就输出到这个 ostringstream 类对象当中的string 当中存储。

后面两种之所以能接收,是因为后面两种都是第一种 控制台流 istream 的派生类,所以,当传入的 流 是后两种的时候,就会发生多态,切片。

 

istringstream

istringstream,就是 流提取,把 istringstream 当中存储的 string 对象提取出来,可以放到 一个 string 当中,也可以用其他存储,比如字符串,还可以直接输出:
 

	Date d(2023, 3, 3);
	string str("2023 3 3");
	istringstream iss(str);
	iss >> d;
	cout << d << endl;

输出:

2023 3 3

如上所示,我们用一个 string 构造到一个 istringstram 当中,然后把 istrigstream 当中存储的string 提取出来 放到 Date类对象 d 当中。

流提取也是和 从文件当中 提取内容是一样的,首次提取是 "2023 3 3" 这个string对象,是按照 空格 "\n" 来进行分割的。

序列化和反序列化 

其实上述的 istringstream 和 ostringstream 做的事情叫做 序列化反序列化

  • 序列化就是把,日常当中使用的不同类型的值转换为 字符串(或者说是文本表示的字节流的值)。
  • 反序列化,就是从 字符串当中提取出来,解析成 不同类型的值。

 为什么要实行上述一样的 序列化和反序列化呢?

因为 在高级语言在内存当中 变量的不同类型的值,是非常复杂的,对值的表达是非常复杂的,比如 在C++ 当中 整形 用 补码存储;浮点型分为 整形部分和 浮点数部分;bool 类型 又是 0 就是假,非零就是真;enum 又是整形常量;

而像上述举的各个例子,都是内置类型,还有在内置类型基础之上 写出 的自定义类型,甚至还有自定义类型 和 内置类型 写出 的 自定义类型。

之所以在内存当中有这么多复杂的类型存储,是因为 在内存当中的值不仅仅要描述数据,还有进行很多简单或者复杂的逻辑运算,算法支持。不同数据存储的 类型当中有什么关系,我们要这些关系来实现出什么结果,这些都是我们想要内存当中所实现的功能。而不仅仅是为了方便我们查看,如果只是为了方便我们查看的话,一个字符串类型就够我们进行查看了,因为字符串可以表示出任意的类型。

但是这是在内存当中需要进行复杂逻辑运算需要这么复杂,但是在其他地方我们可能不需要我们复杂的运算,也就不需要这么复杂的数据类型。

比如在网络传输,只是为了传输,那么只要控制好数据的传输协议,目的地址等等的信息,当要传输的数据是不需要网络层面进行逻辑运算的,只需要传输,那么就按照一个比特位基础的 一组报文,甚至还可以分成更小的组来进行传输。

又或者是在文件,数据库当中存储,只是为了方便我们查看,或者是存储数据,那么需要向内存当中这么多 的类型进行存储吗?肯定是不需要的。

但是,在其他地方流入到内存当中进行逻辑操作,又或者是内存当中进行逻辑操作之后的结果要进行存储或者是查看,这两者之间就需要进行 同一类型的转换,或者是 进行类型的解析

这就是我们为什么要进行 序列化 和 反序列化操作的原因。 

比如下面这个例子:

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

stringstream

stringstream 其实就是  ostringstream 和 istringstream 的结合体,他同时具有 两个流多功能。

也就是说 stringstream 既可以进行 输出 又可以 进行输入。

 

向上述在  序列化和 反序列化 当中给出的例子使用 istringstream  和 ostringstream  实现的,那么我们就 用 stringstream 来实现一下上面的例子:
 

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

int main()
{
	ChatInfo winfo = { "张三", 135246, { 2023, 10, 16 }, "晚上一起看电影吧" };
	stringstream oss;
	oss << winfo._name << " " << winfo._id << " " << winfo._date << " " << winfo._msg;
	string str = oss.str();

	//send(str);
	cout << str << endl;

	// json xml  实现更好,但是不是这篇博客的重点,所以不过多阐述
	ChatInfo rInfo;
	stringstream 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++,开发语言)