C++----IO流(参考C++ primer)

Posted 4nc414g0n

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++----IO流(参考C++ primer)相关的知识,希望对你有一定的参考价值。

IO流

C++IO库


—截图自cplusplus.com

标准IO流

IO无拷贝和赋值

头文件iostream有三种类:

  • 从流读取数据(istream, wistream)
  • 向流提取数据(ostream, wostream)
  • 读写流(iostream, wiosream)

其中开头为w的是其对应的宽字节版本(为了支持宽字节语言)


注意: IO对象无拷贝和赋值

截自c++ primer :

摘自c++ primer

  • ------由于不能拷贝IO对象,因此我们也不能将形参或返回类型设置为流类型(参见6.2.1节,第188页)。进行I0操作的函数通常以引用方式传递和返回流。读写一个I0对象会改变.其状态,因此传递和返回的引用不能是const的(正确用法见下面自定义类重载全局<<

IO条件状态及管理 (MARK有问题)

条件状态(截自c++ primer) :

  • 在ios_base.h源码中有这样两段:

  • 1L<<0为1,1L<<1为2,1L<<2为4,但是根据实际测试与ios_base.h内的不相符,测试结果为:goodbit=0(正常可用状态)badbit=4(出现系统故障的读写错误状态(无法恢复))eofbit=1(遇到文件结束符的状态), failbit=2(读入错误数据的状态(可恢复))
    可以理解为:

例如:

  • cin输入与定义的i不对应类型会产生failbit状态
  • 测试fail()
  • 管理条件状态
  • 复位产生新状态

管理输出缓冲区

导致输出缓冲区刷新有很多情况 (-----摘自C++ primer):

  • 程序正常结束,作为main函数的return操作的一部分, 缓冲刷新被执行
  • 缓冲区满时,需要刷新缓冲,而后新的数据才能继续写入缓冲区,我们可以使用操纵符如endl (参见1.2节,第6页)来显式刷新缓冲区
  • 在每个输出操作之后,我们可以用操纵符unitbuf设置流的内部状态,来清空缓冲区。默认情况下,对cerr是设置unitbuf的,因此写到cerr的内容都是立即刷新的
  • 一个输出流可能被关联到另一个流。在这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新。例如,默认情况下,cin和cerr都关联到cout.因此,读cin或写cerr都会导致cout的缓冲区被刷新

主动刷新输出缓冲区方式:

  • cout<<"test"<<endl;输出test和一个换行,然后刷新缓冲区

  • cout<<"test"<<flush;输出test,然后刷新缓冲区,不附加任何额外字符

  • cout<<"test"<<ends;输出test和一个空字符,然后刷新缓冲区

manipulators

  • cout<<unitbuf;: 所有输出操作后都会立即刷新缓冲区
  • cout << nounitbuf;: 回到正常的缓冲方式

注意:如果程序崩溃,输出缓冲区不会被刷新


关联输入输出流:

  • 第一种形式返回一个指向绑定输出流的指针
  • 第二种形式将对象绑定到 tiestr 并返回指向在调用之前绑定的流的指针(如果有)
  • 标准库默认讲cin和cout关联在一起:当一个输入流被关联到–个输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流(交互式系统通常应该关联输入流和输出流),如下两者等价
  • 解除绑定并保存久绑定:ostream* old_tie = cin.tie(nullptr);
  • 关联新的输入输出流
  • cin.tie(old_tie);重建cin和cout的关联

IO的运算符重载


C++标准库提供了4个全局流对象cincoutcerrclog:

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

(cout、cerr、clog是ostream类的三个不同的对象,因此这三个对象现在基本没有区别,只是应用场景不同)
iosfwd文件中:

iostream中:


注意

ostream支持参数为streambuf的构造(streambuf有两种:filebuf和stringbuf)

istream同


C++ IO流,使用面向对象+运算符重载的方式
类ostream重载 << :

  • 成员函数
  • 非成员函数:
int i = 1;
double j = 2.2;
cout << i << endl;// 自动识别类型的本质--函数重载
cout << j << endl;// 内置类型可以直接使用--因为库里面ostream类型已经实现了

调用的并不是同一个函数,分别调用的是ostream& operator<< (int val);ostream& operator<< (double val);,同时之所以可以连续输出是因为有返回值

  • 对于内置类型可以自动识别直接使用
  • 对于自定义类型,需要全局重载 (类需要加上友元:friend ostream& operator << (ostream& out, const Date& d); ),以一个成员为内置类型的自定义日期类为例
ostream& operator << (ostream& out, const Date& d)

	out << d._year << " " << d._month << " " << d._day;
	return out;


类istream重载 >> :

  • 成员函数:

  • 非成员函数:

  • 自定义类型Date重载>>:

istream& operator >> (istream& in, Date& d)

	in >> d._year >> d._month >> d._day;
	return in;


对于OJ题中的io输入常出现的while (cin >> str)如何结束:换行+ctrlZ
while里的cin>>str实际上是operator>>(cin, str)

  • 在C++98中istream类里重载了void*
  • C++11中istream类里重载了bool

支持ios及其继承类型的对象支持显示类型转换成bool,也就是以为这些类型的对象可以去做条件逻辑判断
以C++11来说也就是:while ((cin >> str).operator bool()),cin在完成cin>>i之后,将自己转换为了一个bool值,当接收到错误状态,bad、eof、fail在对应错误位置时,cin会在错误状态,就返回false,即循环中止,结束

  • 注意:这里是显式的类型转换,istream类定义了一个explicit operator bool() 的成员函数。也就是显式的类型转换运算符函数,需要将此类的对象转换为另一种类型时,需要显式调用,如 (bool)cin 或 static_cast(cin),而若这个成员函数不是explicit的,则可以进行隐式转换,则形如cout<<cin+1;这样的语句就合法了,但是while的条件部分并没有显式转换为什么也合法呢?其实是因为一个例外:即如果表达式被用作条件,则编译器会将显式的类型转换自动应用于它
    参考:C++ 类型转换运算符;条件判断部分使用流对象的实质:while(cin>>i)

对于自定义类型Date如果也要这样使用可以重载 operator bool()
逻辑可以自订(这里是成员_year=0时终止)

  • 在类Date内部加上成员函数operator bool()
operator bool()

	if (_year == 0)
		return false;
	else
		return true;

想要这样调用while ((cin>>d).operator bool()),需要改库文件里istream里的bool重载,没有意义

文件IO流

C++推荐使用流来读取配置文件
注意:

  • 检查if(out):如果open失败,failbit会被置位。如果open失败,条件会为假,我们就不会去使用out了
  • 当一个fstream对象离开其作用域时,与之关联的文件会自动关闭
  • 默认情况下,当我们打开一个ofstream时,文件的内容会被丢弃。阻止一个ofstream清空给定文件内容的方法是同时指定app模式:

ifstream读


注意

  • 删除了拷贝构造
  • 可以创建一个无参文件再使用成员函数open手动打开
  • 使用initialization(2)构造,其中的mode有:(多个使用 ‘|’ 连接)
  • 继承了istream的成员函数
    对于内置类型直接使用>>读出文件内容

ofstream写


ofstream同理:

  • 继承ostream的成员函数:
    对于内置类型直接<<写入文件

文件读写&二进制读写

使用文件IO流用文本及二进制方式模拟读写配置文件:

  • 文件信息:
struct ServerInfo

	char _address[32];
	//string _address;
	int _port;
;
  • 操作:
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 <<endl<<info._port;
	
	void ReadText(ServerInfo& info)
	
		ifstream ifs(_filename);
		ifs >> info._address >> info._port;
	
private:
	string _filename; // 配置文件
;

测试

  • 二进制写和文本写 注意文本写入多个值加上空格换行间隔
    对于char[32]类型的_address以二进制方式写入还可以读出,但int类型的port就是未知字符
  • 二进制读和文本读
    二进制虽然本地打开是乱码,但可以正常读取

    文本正常读取

注意:二进制读写如果将_address改为string类型会出现野指针问题

  • 写入的时候是写入的string指针(string对象实际上是一个指针,内存中是什么样子,就按字节,直接写到文件中读进来也一样,按字节原模原样读进来),测试sizeof() string对象占40个字节,但string的字符串所占的空间是从堆中动态分配 的,与sizeof()无关
  • 如果分两个进程来读取,上一次写入的指针,第二个进程来读取这个指针就不一定会指向这个string了
  • 即使是写了之后马上读取也会出错(析构两次)


    两个对象(winfo,rbinfo)的string _address地址是一样的,会析构两次,况且,写string并没有把真正的数据写进去
  • 至于文本插入没有问题的原因:string类重载了流插入(<<)和流提取(>>),遍历string所有元素返回std::ostream& out,也就是字符串本身而不是指针

对于自定义类型Date:

  • 前面已经重载了cin,cout,这里ifstream/ofstream切片继承父类istream/ostream, 直接用即可

string流

istringstream

构造:
成员函数:

  • str():获取/设置内容(字符串)构造空对象再用str()设置

ostringstream

同理:

使用

有一个学生信息struct

struct Info

	string _name;
	int _id;
	Date _date;
	string _msg;
;

序列化反序列化:

// 结构信息序列化为字符串
Info winfo =  "test", 10,  2022, 9, 4 , "message" ;
ostringstream oss;
oss << winfo._name << " " << winfo._id << " " << winfo._date << " " << winfo._msg;
string str = oss.str();
cout << str << endl;
// 获取
Info rInfo;
istringstream iss(str);
iss >> rInfo._name >> rInfo._id >> rInfo._date >> rInfo._msg;
cout << "name:" << rInfo._name << " (" << rInfo._id << ") ";
cout << rInfo._date << endl;
cout << rInfo._name << ":>" << rInfo._msg << endl;

其他

加速输入输出

  • 标准库默认关联cin和cout底层自动会调用很多flush(同步刷新缓冲区)
  • C++为了兼容C,sync_with_stdio(输入同步开关)。如果流是同步的,则程序可以将 iostream 操作与 stdio 操作混合,并保证它们的可观察效果遵循与线程中使用的相同顺序
  • 关闭提升速度:std::cin.tie(nullptr) ,std::ios::sync_with_stdio(false);

注意关闭同步后不要混用scanf ,getchar,gets,fgets,fscanf和cin

IO库再探(C++ Primer-17.5)

格式化输入输出

非格式化输入输出

流随机访问

以上是关于C++----IO流(参考C++ primer)的主要内容,如果未能解决你的问题,请参考以下文章

C++ Primer 0x08 练习题解

C++ IO流小结

C++ Primer 5th笔记(chap 17 标准库特殊设施)未格式化的输入/输出操作

C++ Primer 0x08 学习笔记

C++ Primer 5th笔记(chap 17 标准库特殊设施)IO库 之操纵符

C++Primer 5th Chap8 The IO Library(未完)