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

news2024/11/18 3:33:09

C语言当中的IO流

 C语言中我们用到的最频繁的输入输出方式就是 scanf () printf()。 scanf():  从标准输入设备(键盘)读取数据,并将值存放在变量中。printf(): 将指定的文字/字符串输出到标准输出设备(屏幕)。

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

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

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

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

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

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

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

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

还有,如使用 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:

 如果你想退出的话,可以输出 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()函数,可以发生强转。


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

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

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

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

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

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

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


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

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

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

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

 

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

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

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


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

 这也是 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:


文件 IO流使用介绍

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

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

#include<fstream>

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

open 函数:

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

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

构造函数的方式使用:

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

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

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

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

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

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

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

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

 这样的话,每一个操作代表一个 二进制位为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的。

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

#include<fstream>

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()函数快速读取数据

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

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

 sstream (stringstream)流介绍和使用

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

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

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

ostringstream 

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

我们先来简单使用一下 ostringstream

#include<sstream>

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,都是把 要输出输入的数据转化成字符串来输出输入的:

 只不过在上述 函数当中的 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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1110080.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数字图像处理实验记录二(直方图和直方图均衡化)

文章目录 一、基础知识1&#xff0c;什么是直方图2&#xff0c;直方图有什么用3&#xff0c;直方图均衡化4、原理代码实现 二、实验要求任务1&#xff1a;任务2&#xff1a; 三、实验记录任务1&#xff1a;任务2&#xff1a; 四、结果展示任务1&#xff1a;任务2&#xff1a; 五…

利用Cpolar永久免费内网穿透软件实现IStoreOS安装与远程访问

文章目录 前言1. ssh局域网登陆iStoreOS系统2. 安装Cpolar内 网穿透软件3. 测试公网远程链接4. 公网使用固定http地址远程访问iStoreOS webui界面 前言 iStoreOS系统是基于OpenWrt定制的软路由系统&#xff0c;提供了如轻nas&#xff0c;云盘&#xff0c;文件共享等众多网络服务…

【EI会议征稿】2024年智能电网与人工智能国际学术会议(SGAI 2024)

2024年智能电网与人工智能国际学术会议&#xff08;SGAI 2024) 2024 International Conference on Smart Grid and Artificial Intelligence 随着时代的发展&#xff0c;基础的电网技术已经比较成熟&#xff0c;但与日俱增的用电需求以及不断转变的用电模式促使我们需要不断地…

Go 存储系列:LSM存储引擎 LevelDB

概念介绍 LSM-Tree 被是一种面向写多读少应用场景的数据结构 &#xff0c;被 Hbase、RocksDB 等强力 NoSQL 数据库采用作为底层文件组织方式。 简单的LSM-Tree 包含 2 层树状数据结构&#xff1a; Memtable 并完全驻留在内存中&#xff08;假设 T0&#xff09; SStables 存储…

可视化(Visual) SQL初探

一、背景 在当今数字化时代&#xff0c;数据信息作为企业和组织的宝贵资源之一&#xff0c;如何挖掘其中的价值并帮助企业和组织个体决策&#xff0c;已然成为炙手可热的话题。数据分析作为其具体载体&#xff0c;是从数据中提取信息、洞察机遇、制定战略、做出决策的关键过程…

在云计算环境中,如何利用 AI 改进云计算系统和数据库系统性能

文章目录 前言一、关于唐明洁教授二、AI for System2.1 面向分布式作业的人工智能2.1.1 现阶段企业云计算系统环境所遇到的普遍痛点2.1.2 云计算系统环境所遇到的普遍痛点的解决方案&#xff08;一&#xff09;Google Autopilot Eurosys 2021方案&#xff08;Pod级别&#xff0…

熟练使用 Redis 的五大数据结构:Java 实战教程

入门 入门阶段主要记住 Redis 的命令&#xff0c;熟练使用 Redis 的 5 大数据结构就可以了。 如果没有 Redis 环境&#xff0c;可以直接通过这个网址https://try.redis.io/&#xff0c;很赞&#xff0c;它会给你模拟一个在线的环境可供你尽情使用&#xff01; 熟练使用Redis的…

C++11lambda表达式--你了解C++的lambda表达式吗?他的底层是怎样的呢?

文章目录 1.lambda表达式的出现1.1C98对内置类型排序1.2C98对自定义类型排序1.3C11中lambda表达式对数据进行排序 2.lambda表达式的语法2.1lambda表达式语法的介绍2.2lambda表达式语法的讲解1.交换函数的lambda表达式写法2.捕捉列表捕捉3.捕捉列表混合捕捉4.不能相互赋值 可以拷…

excel怎么固定前几行前几列不滚动?

在Excel中&#xff0c;如果你想固定前几行或前几列不滚动&#xff0c;可以通过以下几种方法来实现。详细的介绍如下&#xff1a; **固定前几行不滚动&#xff1a;** 1. 选择需要固定的行数。例如&#xff0c;如果你想要固定前3行&#xff0c;应该选中第4行的单元格。 2. 在E…

2023年全网最新 Windows10 搭建 Node.js 环境教程

目录 一、Node.js安装包下载1.1 官网下载node.js安装包1.2 百度网盘获取node.js安装包 二、Windows上安装Node.js三、配置npm在安装全局模块时的路径和缓存cache的路径二、Pycharm 调用 NodeJS 运行 js 代码的环境配置 本文将向大家介绍在 Windows10 上安装 Node.js 的方法。本…

短视频剪辑矩阵系统开发源码----源头搭建

短视频剪辑矩阵系统开发源码----源头搭建 目录 一、源码技术构建源码部署搭建交付之---- 二、短视频矩阵系统功能模型搭建 三、短视频矩阵系统开发的路径呢规则 一、源码技术构建源码部署搭建交付之---- 1.需要协助系统完成部署、接口全部正常接入、系统正常运行多久&#…

canvas操作像素的处理,图片操作例子,黑白蒙版等

经常拍照的同学会使用图片处理软件&#xff0c;给自己的照片加上各种效果。图片处理软件也是软件&#xff0c;同样也是由代码写的&#xff0c;那么如何实现图片处理呢&#xff0c;这章我们就探讨一下这个问题。 canvas中像素处理涉及到3个方法&#xff0c;我们先来看一下API吧…

Jmeter项目实战

一&#xff0c;性能测试流程 性能需求分析 性能方案设计 业务建模 脚本优化 执行测试 收集性能数据 结果分析 性能测试报告 二&#xff0c;性能需求分析 项目管理系统业务&#xff1a;登录 注册 搜索&#xff08;一般最核心的就是登陆&#xff0c;大多只对登录做压测&a…

Spring framework day 01:spring 整合数据源(连接池)

前言 在现代的企业应用开发中&#xff0c;数据库是不可或缺的一部分。而对于大部分的应用程序来说&#xff0c;与数据库的交互涉及到频繁的连接、查询和事务操作。为了提高应用程序的性能和可扩展性&#xff0c;使用连接池来管理数据库连接是一个不错的选择。而 Spring 框架提…

基于多线程+队列实现生产者和消费者

基于多线程队列实现生产者和消费者 需求分析设计思路代码展示 需求分析 需要设计一个系统&#xff0c;能够实时接收视频传来的车牌数据并注入到对应的车辆实体类中。这可能涉及到多线程编程&#xff0c;以处理并监督车牌数据的流入和处理。下面是一种可能的设计思想&#xff1…

如何一起修改多张图片大小?一键修改多张图片尺寸的技巧

如果我们需要在社交平台上分享图片信息&#xff0c;就要把调整图片大小到平台要求的尺寸&#xff0c;单张图片尺寸修改比较简单&#xff0c;要是多张的话&#xff0c;为了提高处理效率&#xff0c;我们就要用到专业工具了&#xff1b;今天分享一个在线图片编辑器&#xff0c;利…

ES6 Class和Class继承

1.class的基本语法 class可以理解为是一个语法糖&#xff0c;将js只能通过构造函数创建实例的方法进行了补充 构造函数&#xff1a; function Person ({ name, age18 }) {this.name namethis.age age } new Person({name: 张三}) Class类&#xff1a; class Person {con…

TCP 传输、重传及工作原理

IP和MAC层的内存受限&#xff0c;用于发送数据包。因此&#xff0c;它们都会限制消息的长度。 这一限制要求TCP在提供给IP层之前&#xff0c;将可变长度的字节打包成多个段。每个段的长度应该是合适的。 下面是一个简单的图示&#xff0c;展示了段是如何通过互联网发送的。 1*I…

淘宝店铺所有商品数据接口及店铺商品数据分析

获取淘宝店铺所有商品数据的接口是淘宝开放平台提供的接口&#xff0c;通过该接口可以获取店铺所有商品数据。 通过淘宝开放平台接口获取店铺所有商品数据的方法如下&#xff1a; 在开放平台注册成为开发者并创建一个应用&#xff0c;获取到所需的 App Key 和 App Secret 等信…

华为数通方向HCIP-DataCom H12-831题库(单选题:301-310)

第301题 关于配置防火墙安全区域的安全级别的描述,错误的是 A、同一系统中,两个安全区域不允许配置相同的安全级别 B、只能为自定义的安全区域设定安全级别 C、安全级别一旦设定不允许更改 D、新建的安全区域,系统默认其安全级别为1 答案:D 解析: 新创建的安全区域缺省未…