C++ IO流

C语言的输入与输出

在C语言中,我们使用最频繁的输入输出方式为: scanf 和 printf.

  1. scanf : 从输入设备(键盘)读取数据,并将值存放在变量中.
  2. printf: 将指定的文字/字符串输出到标准输出设备(屏幕).
    在这里插入图片描述

流是什么?

  • "流"即是流动的意思,是物质从一处向另一处流动的过程,是对一种有序连续且具有方向性的数据的抽象概述.
  • C++流是指信息从输入设备(如键盘)向内存输入和信息从内存向输出设备输出的过程,这种输入输出的过程被形象的比喻为"流".

流的特征为: 有序连续,具有方向性.

C++IO流

C++系统实现了一个庞大的类库,其中ios为基类,其他类都是直接或者间接继承自ios类.
在这里插入图片描述

C++标准IO流

C++标准库提供了4个全局流对象(cin、cout、cerr、clog).

  • 使用cout进行标准输出,即数据从内存流向控制台(显示器).
  • 使用cin进行标准输入,即数据通过键盘输入到程序中.
  • 使用cerr进行标准错误的输出.
  • 使用clog进行日志的输出.

注意:

  1. cin为缓冲流,键盘输入的数据保存在缓冲区中,当要提取时,是从缓冲区中提取,如果一次性输入过多,则多余的数据会保存在缓冲区中以供之后提取,如果输错了,必须在回车之前进行修改,如果回车键按下就无法修改了,只有将输入缓冲区的数据读取完之后的数据才要求输入新的数据.
int main()
{
	int a , b;
	cin >> a; //向放缓冲区中输入 1 , 2
	cout << a << endl;
	cin >> b; //直接从输入缓冲区提取2,不用认为输入
	cout << b << endl;
	return 0;
}

2.输入的数据类型必须与要提取的数据类型一致.

3.回车和空格都可以作为数据之间的分隔符,所以多个数据可以在一行输入,也可以分行输入.但是如果是字符型和字符串,则空格无法用cin输入,字符串中也不能有空格.回车键也无法读入.


int main()
{
	string s;
	cin >> s;          //输入:"hello yzh"
	cout << s << endl; //输出:"hello"
	return 0;
}

对于含有空格的字符串,我们可以尝试采用getline函数进行读取,因为getline函数只有遇到’\n’才会停止读取.

int main()
{
	string s;
	
	getline(cin, s); //输入: "hello yzh";

	cout << s << endl; //输出:"hello yzh

	return 0;
}

4:对于内置类型,cin和cout可以直接输入和输出相关数据,原因:标准库已经将所有内置类型的输入和输出全部重载了,

流插入: >>
在这里插入图片描述
流提取: <<
在这里插入图片描述
5:对于自定义类型,如果要支持cin和cout的标准输入输出,需要对<<和>>进行重载.

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;
}
int main()
{
	Date de;

	cin >> de; //2023 7 6

	cout << de; //2023 7 6

	return 0;
}

6: 在线OJ中的输入和输出.

  • 对于IO类型的算法,一般需要循环输入.
  • 输出:严格按照题目的要求进行,不能多一个空格或者少一个空格.
  • 连续输入时,VS系列编译器在输入ctrl+Z时结束.

例如: C语言中的cin和C++中的scanf默认都是用空格和换行分割的.

输入格式:2023 07 06

int main()
{
	int year, month, day;

	cin >> year >> month >> day; // 2023 07 06

	scanf("%d%d%d", &year, &month, &day); // 2023 07 06

	return 0;
}

输入格式:20230706
要达到输入格式C语言可以利用sancf中规定年为4个位置,月为2个位置,日为2个位置.

int main()
{
	int year, month, day;

	scanf("%4d%2d%2d", &year, &month, &day); //输入格式:20230706

	return 0;
}

C++只能通过stoi函数将数字字符串按照年,月,日的格式进行分割,然后将数字字符串格式转换为整数.

int main()
{
	int year, month, day;

	string str;

	cin >> str;
	
	year = stoi(str.substr(0, 4));

	month = stoi(str. substr(4, 2));

	day = stoi(str.substr(6, 2));

	cout << year << "年" << month << "月" << day<<"日";
}

并且,C++中还可以通过while循环支持多行测试用例,当然,我们可以使用ctrl+z+换行给一个流读取结束的标志.

int main()
{
	int year, month, day;

	string str;
	
	while (cin >> str)
	{
		year = stoi(str.substr(0, 4));

		month = stoi(str.substr(4, 2));

		day = stoi(str.substr(6, 2));

		cout << year << "年" << month << "月" << day<<"日";
	}
	return 0;
}

C++文件流

二进制读写; 在内存中如何存储,就如何写到磁盘文件上.

  • 优点: 读写快.
  • 缺点: 写入打文件,但是文件内容看不见.

文本读写: 序列化成字符串写出来,读回来也是字符串,

  • 可以看见具体内容.
  • 存在一个转换过程,读写速度慢.

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

  1. 定义一个文件流对象
  • istream ifile(只输入用)
  • ofstream ofile(只输出用)
  • fstream iofile(既输入又输出用)

2.使用文件流对象的成员函数打开一个磁盘文件,使的文件流对象和磁盘文件之间建立联系.( 输入或者输出).

3.利用提取和插入运算符对文件进行读写操作,或者使用成员函数进行读写.

以下为C++IO流文本读,写以及二进制读写.

struct ServerInfo
{
	//char _address[32];  //用于二进制读写,必须采用字符数组,不能采用string类.

	string _address;  //用于文本读写,可以采用string类.

	int _port;
	
};
class ConfigManager
{
public:
	ConfigManager(const char* filename = "sever.config")
		:_filename(filename)
	{}

	//二进制写:将数据写道文件里面.
	void WriteBin(ServerInfo& info)
	{
		ofstream ofs(_filename, ios_base::out | ios_base::binary); //以二进制覆盖写的方式打开文件.
		ofs.write((const char*)&info, sizeof(info));   //将目标大小的数据写入.
	}

	//二进制读,将文件里面的数据读取到内存中.
	void ReadBin(const ServerInfo& info)
	{
		ifstream ifs(_filename, ios_base::in | ios_base::binary);//以二进制读的方式打开文件.
		ifs.read((char*)&info, sizeof(info));  //将目标大小的数据读取.
	}


	//文本读(输入到内存中)
	void ReadText1(ServerInfo& info)
	{
		ifstream ifs(_filename, ios_base::in);

		ifs >> info._address >> info._port;
	}

	//文本写(输出到文件中)
	void WriteText1(ServerInfo& info)
	{
		ofstream ofs(_filename, ios_base::out);

		ofs << info._address << endl;

		ofs << info._port << endl;

	}

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

};

测试案例如下:

int main() {
	//写入数据(二进制)
	ServerInfo winfo = { "127.0.0.1",888 };

	ConfigManager cm;

	cm.WriteBin(winfo);

	//读取数据(二进制)
	/*ServerInfo rinfo;

	ConfigManager cm;

	cm.ReadBin(rinfo);

	cout << rinfo._address << endl;   //打印

	cout << rinfo._port << endl;*/

	//文本写.
	//ServerInfo winfo = { "127.0.0.1",888};

	//ConfigManager cm;

	//cm.WriteText1(winfo);


	//文本读.
	//ServerInfo rinfo;

	//ConfigManager cm;

	//cm.ReadText1(rinfo);

	//cout << rinfo._address << endl;

	//cout << rinfo._port << endl;


	return 0;
}

注意:

  • 使用二进制读写时,如果目标数据由string类,vector类存储,那么写入到文件的时候并不是将数组的内容写入,而是将string字符串数组的地址,capacity等写入.该地址也对应着"写入文件"时这一进程.可是,当我们将从文件读取(输入)到内存中时,会将上一进程中写入的地址读取,可是,在新的进程中,该地址是无效的,对应着野指针.
  • 所以二进制读写不适合深浅拷贝的类,一般采用字符数组存储数据.

二进制读,写的自己实现

对文本读来说,我们可以调用ifstream类对象中的成员函数读取文本数据,这一过程也成为输入,步骤如下:

  • 创建ifs对象并二进制以读的形式当打开文件.
  • 创建输入缓冲区
  • 依次调用getline函数对文本数据行读取到缓冲区中.
  • 依次将缓冲区的数据放入对应的变量中.
	void ReadText( ServerInfo& info)
	{
		ifstream ifs(_filename, ios_base::in | ios_base::binary);

		char buff[128];

		ifs.getline(buff, 128); //1

		info._address = buff; //将读取的数据放在对应的变量中.

		ifs.getline(buff, 128); //2

		info._port = stoi(buff);//将读取的数据转回整型变量中.

	}

对于文本写来说,我们也可ofstream类对象中的成员函数将数据写入文件中,这一过程也成为输出.步骤如下:

  • 创建ofs对象并二进制以写的形式当打开文件.
  • 依次将目标数据写入到文件中
	void WriteText(const ServerInfo& info)
	{
		ofstream ofs(_filename, ios_base::out | ios_base::binary);

		ofs.write(info._address.c_str(), info._address.size());   //1

		ofs.put('\n'); //'n'分割数据,读取时方便分辨.

		const string str = to_string(info._port);//2

		ofs.write(str.c_str(), str.size());
	}

利用istream类对象中的get()函数读取Test.cpp文件中的数据(输入),然后再利用cout将打印出来.

int main()
{
	ifstream ifs("Test.cpp");

	char ch = ifs.get();

	while (ifs)
	{
		cout << ch;
		ch = ifs.get();
	}
	return 0;
}

stringstream的简单介绍

在C++中,我们可以使用stringstream来将整型变量的数据达转换为字符串形式.主要有两种使用方式:
1.将数值数据格式转化为字符串.

int main()
{
	//方法一:
	int a = 10;
	string sa;
	stringstream s;
	s << a; //将int类型的a放入输入流
	s >> sa; //从s中抽取前面插入的int类型的值,赋值给string类型
	cout << sa << endl;

	//方法二:
	s.str(""); //将stringstream底层管理的string对象设置为""。
	s.clear(); //将上次转换状态清空掉
	//进行下一次转换
	double b = 3.14;
	s << b;
	sa = s.str(); //获取stringstream中管理的string类型(方式二)
	cout << sa << endl;
	return 0;
}

2.字符串拼接.

int main()
{
	//方法一:
	string rets;
	stringstream s;
	s << "hello" << "yzh"; //将多个字符串放入stringstream中
	s >> rets; 
	cout << rets << endl;

	//方法二:
	s.str(""); //将stringstream底层管理的string对象设置为空字符串
	s.clear(); //将上次转换状态改变.
	s << "welcome" << " " << "to" << " " << "C++"; //将多个字符串放入stringstream中
	rets = s.str(); 
	cout << rets << endl;
	return 0;
}

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 }, "晚上一起看电影吧"
	};
	stringstream oss;
	oss << winfo._name << " " << winfo._id << " " << winfo._date << " "
		<< winfo._msg;
	string str = oss.str();
	cout << str << endl << 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;
}

注意:

  • 序列化与反序列化也可以使用ostringstream和istringstream操作,效果与用法stringstream相同.
  • stringstream实际是在底层维护了一个string类型的对象用来保存结果。
  • stringstream在转换结尾时(即最后一个转换后),会将其内部状态设置为badbit,因此在下一次转换前必须调用clear将状态重置为goodbit才可以转换,但clear不会将stringstream底层的string对象清空。
  • 所以,我们可以使用s.str(“”)的方式将stringstream底层的string对象设置为空字符串,否则多次转换时,会将结果全部累积在底层string对象中。
  • 使用stringstream转换后可以使用 >> 运算符 也可以 使用使用s.str()将让stringstream返回其底层的string对象。
  • 20
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 20
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

暂停更新

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值