IO库 【C/C++】

时间:2021-02-12 01:21:18

编译器:VS2022

说明

C/C++语言不直接处理输入输出,而是通过标准库中的类型来处理IO。

类型 ifstreamistringstream 都继承自 istream
类型 ofstreamostringstream 都继承自 ostream
可以将一个派生类(子类)对象当作基类(父类)对象来使用。

iostream

iostream 定义了用于读写流的基本类型。
头文件:#include<isotream>

  • istream :输入流类型,提供输入操作。
  • ostream :输出流类型,提供输出操作。

iostream:

  • cin:istream 类对象,从标准输入读取数据,默认情况下关联到用户的控制台窗口。
  • cout:ostream 类对象,向标准输出写入数据,默认情况下关联到用户的控制台窗口。
  • cerr:ostream 类对象,通常用于输出程序错误消息,写入到标准错误,默认情况下关联到用户的控制台窗口。
  • clog:ostream 类对象,通常用于输出程序运行时一般信息(运行日志),默认情况下关联到用户的控制台窗口。

宽字符版本的类型和函数的名字以一个w开始:
wcinwcoutwcerr 是分别对应 cincoutcerr 的宽字符版对象。

>> 运算符:从一个 istream 对象读取输入数据。
<< 运算符:向一个 ostream 对象写入输出数据。
getline :从给定的 istream 读取一行数据,存入给定的 string 对象中。

代码演示:IO对象无赋值,无拷贝。

#include <iostream>
#include <fstream>

using namespace std;

int main()	
{
	ofstream out1, out2;
	out1 = out2;	//不允许对流对象赋值

	ofstream print(ofstream);	//C++PrimerPlus中源码注释,不能初始化ofstream参数,但是vs2022编译器未报错。todo
	out2 = print(out1);	//不能拷贝流对象
	
	return 0;
}

不能将形参或返回类型设置为流类型。
进行IO操作的函数通常以引用方式传递和返回流。
读写一个IO对象会改变其状态,因此传递和返回的引用不能是const的。

IO库操作过程中的条件状态:
IO库 【C/C++】
流发生错误,其上后续的IO操作都会失败。
只有当流为无错状态时,才可以通过流读写数据。

代码演示:确定流状态的方式:作为判断条件。

#include <iostream>
#include <fstream>

using namespace std;


int main()	
{
	int num = 1;
	if (cin >> num)
	{
		cout << "stream success" << endl;
		cout << num << endl;
	}

	return 0;
}

运行结果:
IO库 【C/C++】
IO库定义了一个与机器无关的iostate类型,它提供了表达流状态的完整功能。

badbit 表示系统级错误,如不可恢复的读写错误。

在发生可恢复错误后,failbit 被置位,如期望读取数值却读出一个字符等错误。这种问题通常是可以修正的,流还可以继续使用。

如果到达文件结束位置,eofbitfailbit 都会被置位。

goodbit 的值为 0,表示流未发生错误。

如果badbitfailbiteofbit 任一个被置位,则检测流状态的条件会失败。

标准库定义查询标志位状态的函数:

  • good:所有错误位均未置位的情况下返回 true
  • badfaileof 则对应错误位被置位时返回 true
  • badbit 被置位时,failed返回 true
  • goodfail 是确定流总体状态的正确方法,将流当作条件使用的代码等价于 !fail

流对象的 rdstate 成员返回一个 iostate 值,对应流的当前状态。
setstate 操作将给定条件位置位,表示发生了对应错误。
clear 不接受参数的版本清除(复位)所有错误标志位。执行 clear() 后,调用 good 会返回 true
带参数的 clear 版本接受一个 iostate 值,表示流的新状态。

代码演示:操作流状态。

#include <iostream>
#include <fstream>

using namespace std;


int main()	
{
	int data;
	auto old_state = cin.rdstate();	//记住当前状态
	cin.clear();					//使cin有效
	cin >> data;					//使用cin
	cin.setstate(old_state);		//将cin置为原有 old_state 状态

	//用rdstate读出当前条件状态,将failbit和badbit复位,其他条件状态保持不变。
	cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);
	return 0;
}

刷新缓冲区:

  • 程序正常结束,作为 main 函数返回的一部分,缓冲刷新被执行。
  • 缓冲区满。
  • 使用操纵符如endl。
  • 在每个输出操作之后,我们可以用操纵符unitbuf设置流的内部状态,来清空缓冲区。
  • 默认情况下,对cerr是设置unitbuf的,因此写到cerr的内容都是立即刷新的。
  • 一个输出流可能被关联到另一个流。在这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新。
  • 默认情况下,cin和cerr都关联到cout。因此,读cin或写cerr都会导致cout的缓冲区被刷新。

代码演示:刷新输出缓冲区

#include <iostream>
#include <fstream>

using namespace std;


int main()	
{
	cout << "hello, " << endl;		//换行并刷新缓冲区
	cout << "world!!!" << flush;	//刷新缓冲区
	cout << "C++" << ends <<"***";	//向缓冲区加空字符刷新缓冲区,vs2022代码实测无空白字符 todo

	cout << unitbuf;	//所有输出操作后立即刷新缓冲区
	//所有输出操作后立即刷新缓冲区
	//所有输出操作后立即刷新缓冲区
	//所有输出操作后立即刷新缓冲区
	cout << nounitbuf;	//回到正常系统管理的缓冲区刷新机制
	
	return 0;
}

运行结果:
IO库 【C/C++】

警告:程序崩溃,输出缓冲区不会被刷新。

代码演示:cin 对象 tie 成员函数使用及说明。

#include <iostream>
#include <fstream>

using namespace std;


int main()	
{
	//tie 第一个版本不带参数,返回指向输出流的指针
	ostream* old_tie_1 = cin.tie();	//old_tie_1 指向 cout对象,标准库已经将 cin 和 cout 关联.
	if (old_tie_1)
	{
		*old_tie_1 << "hello world" << endl;
	}
	else
	{
		cout << "nummptr == old_tie_1" << endl;
	}
	//如果对象未关联到流,返回空指针


	//tie的第二个版本接受一个指向ostream的指针,将自己关联到此ostream。
	cin.tie(&cout);	//将 cin 关联到 cout。标准库已经将 cin 和 cout 关联。

	ostream* old_tie_2 = cin.tie(nullptr); //cin 不再与其他流关联。
	ostream* old_tie_3 = cin.tie();	//old_tie_3 指向 cout对象。
	if (old_tie_3)
	{
		*old_tie_3 << "hello world" << endl;
	}
	else
	{
		cout << "nummptr == old_tie_3" << endl;
	}

	cin.tie(&cout); //将 cin 关联到 cout。
	ostream* old_tie_4 = cin.tie();	//old_tie_4 指向 cout对象
	if (old_tie_4)
	{
		*old_tie_4 << "hello world" << endl;
	}
	else
	{
		cout << "nummptr == old_tie_4" << endl;
	}

	return 0;
}

运行结果:
IO库 【C/C++】
每个流同时最多关联到一个流,但多个流可以同时关联到同一个ostream。

fstream

fstream定义了读写命名文件的类型。
头文件:#include<fstream>

fstream:

  • istream:从文件读取数据。
  • ostream:向文件写入数据。
  • fstream:读写文件。

宽字符版本的类型和函数的名字以一个w开始:
wistreamwostreamwfstream 是分别对应 istreamostreamfstream 的宽字符版。

除了继承自 iostream 外,fstream新增成员管理与流关联的文件:
IO库 【C/C++】

当一个 fstream 对象被销毁时, close 会自动被调用。

每个流都有一个关联的文件模式,文件模式指出如何使用文件:
IO库 【C/C++】
指定文件模式限制如下:

  • 只可以对 ofstream 或 fstream 对象设定 out 模式。
  • 只可以对 ifstream 或 fstream 对象设定 in 模式。
  • 只有当 out 设定时,才可以设定 trunc 模式。
  • 只要 trunc 模式没被设定,就可以设定 app 模式。
  • app 模式下,即使没有显式指定 out 模式,文件也总是以输出方式打开。
  • 默认情况下,没有指定 trunc 模式,out 模式打开的文件也会被截断。
  • 为了保留以 out 模式打开的文件的内容,必须同时指定 app 或者 in。
  • ate和binary模式可用于任何类型的文件流对象,且可以与其他任何文件模式组合使用。

在每次打开文件时,都要设置文件模式,可能是显式地设置,也可能是隐式地设置。
当程序未指定模式时,就使用默认文件模式:

  • ifstream :默认 in 模式。
  • ofstream:默认 out 模式(打开文件会都是已有数据)。
  • fstream:默认 int 和 out 模式。

对于一个给定流,每当打开文件时,都可以改变其文件模式。

sstream

sstream定义了读写内存string对象的类型。
头文件:#include<sstream>

fstream:

  • istringstream:从 string 读取数据。
  • ostringstream:向 string 写入数据。
  • stringstream:读写 string

宽字符版本的类型和函数的名字以一个w开始:
wistringstreamwostringstreamwstringstream 是分别对应 istringstreamostringstreamstringstream 的宽字符版。

stringstream 新增管理与流相关联的string:
IO库 【C/C++】

代码演示:

#include <iostream>
#include <sstream>
#include <string>
#include <vector>

using namespace std;

//istringstream:当我们的某些工作是对整行文本进行处理,而其他一些工作是处理行内的单个单词时,通常可以使用istringstream。
//假定有一个文件,列出了一些人和他们的电话号码。
//某些人只有一个号码,而另一些人则有多个——家庭电话、工作电话、移动电话等。
//我们的输入文件看起来可能是这样的:
//morgan 2015552368 8625550123
//drew 9735550130
//lee 6095550132 2015550175 8005550000
//记录姓名和电话信息

//ostringstream:当我们逐步构造输出,希望最后一起打印时,ostringstream是很有用的。
//想逐个验证电话号码并改变其格式。如果所有号码都是有效的,我们希望输出一个新的文件,包含改变格式后的号码。
//对于那些无效的号码,我们不会将它们输出到新文件中,而是打印一条包含人名和无效号码的错误信息。
//由于我们不希望输出有无效电话号码的人,因此对每个人,直到验证完所有电话号码后才可以进行输出操作。

struct PersionInfo
{
	string name;
	vector<string> phone;
};

int main()	
{
	string line, word;
	vector<PersionInfo> people;

	while (getline(cin, line))
	{
		PersionInfo info;
		istringstream record(line);	//将记录绑定到读入的行
		record >> info.name;		//读取名字
		while (record >> word)		//读取电话
		{
			info.phone.push_back(word);
		}
		people.push_back(info);
	}

	for (auto& itr : people)
	{
		cout << "name:" << itr.name << "--";
		for (auto& iphone_itr : itr.phone)
		{
			cout << iphone_itr << ",";
		}
		cout << " ." << endl;
	}

	for (const auto& entry : people)
	{
		ostringstream formatted, badNums;
		for (const auto& nums : entry.phone)
		{
			if (nums.size() > 12)
			{
				badNums << " " << nums;
			}
			else
			{
				//输出拼接
				formatted << entry.name << " phone: "<<nums << "is success" << endl;	
				cout << formatted.str() << endl;
			}
		}
	}

	return 0;
}


运行结果:
IO库 【C/C++】