【C++】IO流

 

目录

一、C语言的标准输入输出

二、C++的IO流

1.标准IO流

2.文件IO流

补、stringstream


一、C语言的标准输入输出

        对于新手而言,C语言所用的最频繁的输入输出方式就是scanf ()和printf()。

  • scanf(): 从输入设备(键盘)获取数据,并将数据存在一些变量中。
  • printf(): 将指定的文字/字符串输出至输出设备(屏幕。printf()需留意宽度输出和精度输出的控制)。

        将数据从输入设备获取并保存、将数据输出至输出设备,这个过程中借助了输入缓冲区和输出缓冲区。

 【Tips】输入/输出缓冲区的功能

  1. 低级I/O的实现依赖操作系统本身内核的实现,所以如果能够屏蔽这部分的差异,可以很容易写出可移植的程序。通过输入/输出缓冲区,就可以屏蔽掉低级I/O的实现;
  2. 计算机是没有“行”这个概念的,有了输入/输出缓冲区,就可以定义“行”的概念,只要解析缓冲区的内容,返回一个“行”,就实现了“行”读取的行为。

二、C++的IO流

        “流”是流动的意思,指物质从一处向另一处流动的过程,是对一种有序连续且具有方向性的数据(其单位可以是bit、byte、packet)的抽象描述。

        C++流是指,信息从输入设备(键盘)向计算机内部(内存)输入,和从计算机内部向输出设备(显示器)输出的过程。这种过程的特点是,连续有序,具有方向性。

        C++定义了I/O标准类库来实现这种流动的过程,它是一个庞大的类库,库中每个类都称为流类(或简称流),用以完成某些特定的功能。流和流之间有继承关系,例如ios为基类,istream、ostream、fstream、stringstream等都是直接或间接派生自ios类。

        其中,iostream就是标准IO流,ifstream、ofstream、fstream就是文件IO流。

1.标准IO流

        iostream就是标准IO流,它继承了istream流和ostream流,同时也继承了istream的cin,ostream的cout、cerr、clog。

        cin、cout、cerr、clog是4个全局流对象,用于C++标准输入输出。

  • cin:进行标准输入(意思是数据通过键盘输入)到程序中。
  • cout:进行标准输出(意思是数据从内存流向控制台/显示器)。
  • cerr:进行标准错误的输出。
  • clog:进行日志的输出。

        cin(流插入)和cout(流提取)可以看作是C++对C语言中scanf()和printf()的改进。scanf()和printf()只能处理内置类型,而cin和cout可以自动识别类型(是以运算符重载的方式实现的),并且面向对象,可以更好支持自定义类型,更形象。

【ps】cin和cout的使用须知

  1. 空格和回车都可以作为数据之间的分格符,所以多个数据可以在一行输入,也可以分行输 入。但cin无法识别字符型数据之间的空格(因为空格的ASCII码为32),它会自动跳过空白(空格,换行符,制表符),直到遇到非空白字符。同样的,cin能读取的字符串中也不能有空格和回车符;
  2. cin可以根据类型选择读取,如果输入数据的类型不匹配存储数据的变量的类型,那cin就不会读入数据,而返回一个0放入变量中;
  3. 使用cin输入时会返回一个istream对象,这个对象最终会隐式类型转换为bool值;
  4. 从键盘输入时,cin会先将从键盘获取的数据保存在缓冲区中,当cout提取时才从缓冲区中取出。如果输入的数据过多,一次提取不完,就会将数据留在缓冲区中慢慢使用,只有当缓冲区中的数据取完,cin才会获取新的数据;如果输入时输错了,就必须在按下回车键之前修改;
  5. cin输入的数据类型与cout要提取的数据类型应保持一致,否则会出错(但这个出错只是在流的状态字state中,程序是可以继续的);
  6. 对于内置类型的数据,cin和cout是可以直接输入和输出的,因为标准库已经为>>和<<实现了相应的重载;
  7. 对于自定义类型的数据,如果要支持cin和cout的标准输入输出,需要手动对>>和<<进行重载;
  8. 在线OJ中的输入和输出: 1)对于IO类型的算法,一般都需要循环输入;2)输出:严格按照题目的要求进行,多一个少一个空格都不行;3)连续输入时,vs系列编译器下在输入ctrl+Z时结束。
// 单个元素循环输入
while(cin>>a)
{
    // ...
}

// 多个元素循环输入
while(c>>a>>b>>c)
{
    // ...
}

// 整行接收
while(cin>>str)
{
    // ...
}
//while (cin >> str)可以实现持续的输入,直到ctrl z+换行 结束

int main()
{
	string str;
	while (cin >> str) // operator>>(cin, str).operator bool()
  //cin >> str返回了一个istream对象,最终隐式类型转换为bool值
  //这个过程通过从父类继承的operator bool()来实现
  //输入结束或输入发生错误返回false,否则返回true
	//ctrl z修改了流中的标准,ctrl c发信号结束进程
	{
		cout << str << endl;
	}

	return 0;
}
int main()
{
	int a, b;
	while (cin>>a>>b)  // cin.operator>>(a).operator>>(b).operator bool()
	{
		cout << a << endl;
		cout << b << endl;
	}
}
//[补]类型转换
class A
{
public:
	A(int a)
		:_a(a)
	{}

	//operator()无法重载,原因是被仿函数占用
	operator int () //这里int就是重载函数的返回值
  //explicit operator int()	//加上explicit,放宽了限制,使非int的内置类型也支持来自自定义类型的转换
	{
		return _a;
	}

	operator bool()//还可以重载为其他类型
	{
		return _a;
	}

	int _a;
};

int main()
{
	// 自定义类型<-内置类型
	A aa1 = 100; //单参数的构造支持隐式类型转换

	// 内置类型<-自定义类型 - 需要重载的支持
	int i = aa1;	//operator int ()
	cout << i << endl;//100

	bool ret = aa1;
	cout << ret << endl;//1
 
	//double d = aa1;
	//int* ptr = aa1;
    //加上explicit后都可以实现
    //没加就需要显示调用
    double d = (int)aa1;

	return 0;
}
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)
	{}
	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;
}
// C++ IO流,使用面向对象+运算符重载的方式
// 能更好的兼容自定义类型,流插入和流提取
int main()
{
	// 自动识别类型的本质--函数重载
	// 内置类型可以直接使用--因为库里面ostream类型已经实现了
	int i = 1;
	double j = 2.2;
	cout << i << endl;
	cout << j << endl;
	// 自定义类型则需要我们自己重载<< 和 >>
	Date d(2023, 1, 2);
	cout << d << endl;
	while (cin >> d)
	{
		cout << d << endl;;
	}
	return 0;
}

 

2.文件IO流

        C++根据文件内容的数据格式,将文件分为二进制文件和文本文件。

【Tips】采用文件流对象操作文件的一般步骤:

  1. 定义一个文件流对象(ifstream ifile - 只输入用;ofstream ofile - 只输出用;fstream iofile - 既输入又输出用);
  2. 使用文件流对象的成员函数打开一个磁盘文件,使文件流对象和磁盘文件之间建立联系;
  3. 使用提取和插入运算符对文件进行读写操作,或使用成员函数进行读写;
  4. 关闭文件;
//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)
	{}

	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");
  //以二进制方式写入文件
	/*fwrite(&d, sizeof(Date), 1, fin);
	fclose(fin);*/
  //但文件当中只有字节流,不转换成字符串是看不懂的
 
  //将日期转换成字符串
	//string str = d;
	//fputs(str.c_str(), fin);

	return 0;
}
//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)
	{}

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

#include<fstream>

int main()
{
	Date d(2023, 10, 14);
	//ofstream ofs("file.txt", ios_base::out | ios_base::binary);
	ofstream ofs("file.txt");
	// 二进制的方式
	//ofs.write((const char*)&d, sizeof(d));
	 
	// 文本的方式
	ofs << d;
}

 

// C++文件流的优势就是可以对内置类型和自定义类型,都适用
// 一样的方式,去流插入和流提取数据
// 当然以下自定义类型Date需要重载>> 和 <<

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)
	{}
	operator bool()
	{   
		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 _address;
	char _address[32];
	// 二进制读写不能用string vector这样的对象存数据
	// 否则写出去就是一个指针,进程结束就是野指针,另一个进程再读进来进坑了

	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 = { "777.6.5.4", 80, { 2023, 5, 4 } };

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

	// 文本读写
	ConfigManager cf_text("test.text");
	cf_text.WriteText(winfo);
	ServerInfo rtinfo;
	cf_text.ReadText(rtinfo);
	cout << rtinfo._address << " " << rtinfo._port << " " << rtinfo._date << endl;
    //777.6.5.4 80 2023 5 4 

	return 0;
}

 

补、stringstream

        在C语言中,想要将一个整形变量的数据转化为字符串格式,一般通过以下两个函数:

  1. _itoa()
  2. sprintf()

        但是两个函数在转化时,都必须先为保存结果预留空间,但预留空间的具体大小并不好界定, 另外,转化格式不匹配时,可能会得到错误的结果,甚至引发程序崩溃。

int main()
{
	int n = 123456789;
	char s1[32];
	itoa(n, s1, 10);
	char s2[32];
	sprintf(s2, "%d", n);
	char s3[32];
	sprintf(s3, "%f", n);
	return 0;
}

        在C++中,可以使用stringstream流来避开此问题,而要使用stringstream,必须要包含头文件 <sstream>(在头文件<sstream>下含有三个流:istringstream - 输入、ostringstream - 输出、stringstream - 输入输出)。

        stringstream主要可以用来:

 1、将数值类型数据格式化为字符串

#include<sstream>
int main()
{
	int a = 7654321;
	string sa;
	// 将一个整形变量转化为字符串,存储到string类对象中
	stringstream s;
	s << a;
	s >> sa;
	cout << sa << endl;
 
	s.str("");
	// s.str("");
	// 将stringstream底层管理string对象设置成"", 
	// 否则多次转换时,会将结果全部累积在底层string对象中
	s.clear(); // 清空s, 不清空会转化失败
	// clear()
	// 注意多次转换时,必须使用clear将上次转换状态清空掉
	// stringstreams在转换结尾时(即最后一个转换后),会将其内部状态设置为badbit
	// 因此下一次转换是必须调用clear()将状态重置为goodbit才可以转换
	// 但是clear()不会将stringstreams底层字符串清空掉
 
	double d = 12.34;
	s << d;
	s >> sa;
	string sValue;
	sValue = s.str(); // str()方法:返回stringsteam中管理的string类型
	cout << sValue << endl;
	return 0;
}

 

2、字符串拼接  

#include<sstream>
int main()
{
	stringstream sstream;
	// 将多个字符串放入 sstream 中
	sstream << "first" << " " << "string,";
	sstream << " second string";
	cout << "strResult is: " << sstream.str() << endl;
	// 清空 sstream
	sstream.str("");
	sstream << "third string";
	cout << "After clear, strResult is: " << sstream.str() << endl;
	return 0;
}

 

3、 序列化和反序列化结构数据 

#include<sstream>
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)
	{}
	operator bool()
	{ 
		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()
{
	// istringstream和ostringstream可以简单序列化和反序列化
  //序列化是指,把日常表示的各种类型的值转换成字符串,或文本能表示的字节流的值
	Date d1(2023, 10, 16);
	ostringstream oss;
	oss << d1;
	string str1 = oss.str();
	cout << str1 << endl;

  //反序列化是指,把序列化的值给解析出来
	string str2("2023 10 16");
	istringstream iss(str2);
	Date d2;
	iss >> d2;
	cout << d2 << endl;

	
	//数据在内存中的表示是很复杂的,例如:
	//基础类型:
	// 整形    补码存储
	// 浮点型  整形和浮点数部分分开存储
	// 布尔    其实就是整型,用非0表示真,0表示假
	// enum    实际上是整形常量	
	// 联合
	// 字符串
  //复杂类型:由基础类型组合而来
	// 结构
	// 类
  //实际中,计算机对这些数据不仅要进行存储、表达,还要在其基础上进行计算
  //数据在内存中的表示如此复杂,是为了方便存储和计算

	return 0;
}

 

#include<sstream>
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)
	{}
	operator bool()
	{
		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 ChatInfo
{
	string _name; // 名字
	int _id;      // id
	Date _date;   // 时间
	string _msg;  // 聊天信息
};

int main()
{
	// 结构信息序列化为字符串
	ChatInfo winfo = { "张三", 135246, { 2023, 10, 16 }, "晚上一起看电影吧" };
	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._msg << endl;
	cout << "-------------------------------------------------------" << endl;
	return 0;
}

 

【小结】stringstream流对象的使用须知

  1. stringstream实际是在其底层维护了一个string类型的对象用来保存结果。
  2. 多次数据类型转化时,一定要用clear()来清空,才能正确转化,但clear()不会将stringstream底层的string对象清空。
  3. 可以使用s. str("")方法将底层string对象设置为""空字符串。
  4. 可以使用s.str()将让stringstream返回其底层的string对象。
  5. stringstream使用string类对象代替字符数组,可以避免缓冲区溢出的危险,而且其会对参数类型进行推演,不需要格式化控制,也不会出现格式化失败的风险,因此使用更方便,更安全。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值