前言:

IO流,启动!因笔者对于IO流的理解不是很深刻,所以这里进行简单的介绍即可。


1 IO流

IO流是我们从学习C++开始就一直会使用的东西,我们先了解一下C++IO流的一套继承体系:

整个IO体系的基类是ios_base,看IO的源码的时候也有所体现,这里也用到了菱形虚拟继承,istream和ostream继承了ios,iostream继承了ostream istream,后面的分别是文件流 字符串流。

我们平常使用的iostream里面,cout cerr clog都是输出,使用起来是没有差别的,不过是一个输出日志 一个输出错误码,它们是底层机制有区别,这里了解一下即可:

int main()
{
	int a = 2;
	cout << a << endl;
	cerr << a << endl;
	clog << a << endl;
	return 0;
}

对于cout而言,一般是输出的窄字符->narrow char ,我们平时使用的字符基本上都是窄字符,用utf8表示的,当然也有宽字符:

同理,也有wcerr wlog,我们目前都还用不上,涉及到utf16的时候,说不定就会有所接触了。

根据文档介绍,对象都是通过标准的输出流面向窄字符流输出的,不妨简单描述一点就是,输入输出是通过字符流完成的。比如以文件的形式读取的时候,都是先转换为字符串,然后再读进去。

现在思考一个问题,cin>>的返回值是什么?为什么可以使用while来判断

文档也没有过多的介绍,这里cin>>能作为返回值实际上是调用了istream类的函数:

operator bool,检查到输出了类似于eof的这种标志,就返回了false,while循环就结束了。

正常输入的时候,是怎么判断流输入正常的呢?

调用的是istream的这四个成员变量,good eof fail bad:

对于good 来说,检查流是不是正常的,这个我们一般不用管,对于eof来说,是文件的标志结尾,我们也不用管,对于fail来说,是有没有基本的逻辑错误,比如读取的是整型但是输入的是char,bad就更不用管了,一般严重错误的时候才会设置,所以我们实际要关心的是fail,我们可以打印看看:

int main()
{
	int a = 0;
	cout << cin.good() << endl;
	cout << cin.eof() << endl;
	cout << cin.fail() << endl;
	cout << cin.bad() << endl;

	cin >> a;

	cout << cin.good() << endl;
	cout << cin.eof() << endl;
	cout << cin.fail() << endl;
	cout << cin.bad() << endl;
	return 0;
}

正常的打印结果就是1 0 0 0 ,那么我如果输入字符呢?

可以看到good 和 fail被设置为了相反的,说明输入不正常。

面临这种情况,我们就要想办法把字符去掉,这些标志也要重新设置,如果不重新设置:

a也打印不出来我们想要的值,重新设置的函数为clear,去掉字符就get一下就可以了:

int main()
{
	int a = 0;
	cout << cin.good() << endl;
	cout << cin.eof() << endl;
	cout << cin.fail() << endl;
	cout << cin.bad() << endl;

	cin >> a;

	cout << cin.good() << endl;
	cout << cin.eof() << endl;
	cout << cin.fail() << endl;
	cout << cin.bad() << endl;
	
	cin.clear();
	cin.get();

	cin >> a;
	cout << a << endl;
	return 0;
}

同理,如果有多个字符,咱么多get一下就可以了。

这是在IO流的cin里面要注意的事。

这里简单提一下,在竞赛中,如果io的输入输出过多了,就会影响效率的,因为C++兼容C语言,所以C语言有自己的缓冲区,C++也有自己的缓冲区,输入输出的时候,不同的缓冲区之间有绑定关系,即自己的事干完了还要看别的缓冲区有没有完事儿,那么呢,缓冲区刷新才能让里面的东西出去是吧?如果我不设置刷新的标志,比如换行,是不是C++的打印就在C语言之前了呢?

int main()
{
	printf("a");
	cout << " a " << endl;
	printf("\n");
	return 0;
}

这里是不会的,因为编译器作了相关处理。

默认的是cin 绑定 cout cerr ,IO的输入输出一多,就会导致我干完了我还要等你,所以解除绑定关系可以高效率,代码如下:

ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);

sync的意思是同步,tie的默认参数:

给tie空指针就是说,谁也不绑定了,我自己单干,这样效率会高一点。


2 文件流

文件流和C语言的文件流使用起来是方面很多的,这时IO流的一个优势,具体请看下文。

首先先来了解一下fstream的构造:

文件流类的构造的默认参数有了,我们就不用写,其中ios_base::in,是基类的,但是因为继承下来了,我们也可以使用fstream的in,但是没必要,然后:

根据文件的不同的读写模式可以实现不同模式的读取,默认是文本的形式读取的,这里面的write就是C语言里面的fwrite,默认的写是覆盖写,想要追加写就使用app写,文件流所在的头文件是fstream:

int main() 
{
	std::ofstream ofs("test.txt");
	ofs << "xxxxxxxxxxxxxx";
	return 0;
}

写进去,没有就创建,改写进去的内容默认覆盖:

int main() 
{
	std::ofstream ofs("test.txt", ofstream::out | ofstream::app);
	ofs << "111";

	return 0;
}

这样是追加写。

那么如何读取一个图片呢?使用istream进行读取即可;

int main()
{
	ifstream ifs("D:\\C++\\数据结构.jpg", ifstream::in | ifstream::binary);
	char c = ifs.get();
	int n = 0;
	while (ifs.good()) {
		++n;
		c = ifs.get();
	}
	cout <<n<<endl;
	return 0;
}

使用ifstream流读取即可,其中ifstream是读取 ostream是写入 fstream是既可以读也可以写。

那么理论上来说,复制图片也是可以的,这个小实验就交给同学们完成了。

那么今天的重点就要来了,我们如何将一个类的数据写到文件里面?在C语言的章节,我们都是写入的一些整型,今天写入自定义类型:

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 ServerInfo
{
	char _ip[32];
	//string _ip;
	int _port;
	Date _date;
};

struct ConfigManager
{
public:
	ConfigManager(const char* filename = "test.txt")
		:_filename(filename)
	{}

private:
	string _filename; // 配置文件
};

用到的三个类如上,我们分为二进制的读写和文本文本读写来看,首先是二进制读写:

二进制的读写是最简单的:

	void WriteBin(const ServerInfo& info)
	{
		ofstream ofs(_filename, ios_base::out | ios_base::binary);
		ofs.write((const char*)&info, sizeof(ServerInfo));
	}

	void ReadBin(ServerInfo& info)
	{
		ifstream ifs(_filename, ios_base::in | ios_base::binary);
		ifs.read((char*)&info, sizeof(info));
	}

整体的思路是将结构体的地址转换为char*的地址,然后一个字符一个字符的读取就行了。

但是二进制面临有地址的读写是会出问题的,因为结构体里面的数据有成员变量,string这种,如果是string,指向的空间我们是没有拷贝的,拷贝的是那个地址,所以如果使用二进制的读写string vector这种就会报错,如果是文本操作就不会。

在这里IO流的优点就体现出来了,如果是C语言还要将string转为字符串,但是IO流这里可以直接写入:

void WriteText(const ServerInfo& info)
{
	ofstream ofs(_filename);
	ofs << info._ip << " " << info._port << " " << info._date;
}

void ReadText(ServerInfo& info)
{
	ifstream ifs(_filename);
	ifs >> info._ip >> info._port >> info._date;
}

因为重载了 << >>,就不用单独转换了。



3 字符串流

字符串流用在序列化和反序列化,就是转成字符串说什么的,

同样,stringstream是两者的集合,所在的头文件是sstream,使用的时候可以将字符串转化为结构体信息也可以将结构体信息转换为字符串,这里看个代码就行:


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;
	// 字符串解析成结构信息
	ChatInfo rInfo;
	istringstream iss;
	iss.str(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;
}

给空格是因为方面后面好读取,因为没有空格读取的话后面读出来看不懂的,也算是一个分隔符了。

这部分校招方面考的很少,所以咱们了解一下就行了。


感谢阅读!

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐