C++ ---- IO库
IO类
C++的输入输出由标准库提供,标准库提供了一族类型,支持对控制窗口、文件和string对象等设备的读写。一方面,这些IO类型都定义了如何读写内置类型的的值,另一方面,用户在设计类时可以仿照IO标准库设施读写内置类型的方式设计自己的输入输出作。
分别定义在三个独立的头文件中:iostream 定义了用于读写流的基本类型, fstream 定义了读写命名文件的类型, sstream 定义了读写内存 string 对象的类型。
通过网上的一张图,我们可以看到它们直接的派生关系。
其中,为了支持使用宽字符语言,标准库定义了一组类型和对象来操纵wchar_t 类型的数据。宽字符版本的类型和函数的名字以一个 w 开始。例如 wcin、wcout 和 wcerr。
IO 对象无拷贝或赋值
ofstream out1, out2; // 错误:不能对流对象赋值
out1 = out2; // 错误:不能对流对象赋值
ofstream print(ofstream); // 错误:不能初始化 ofstream 参数
out2 = print(out2); // 错误:不能拷贝流对象
由于不能拷贝IO对象,因此我们也不能将形参或返回类型设置成流类型。
进行IO操作的函数一般以引用方式传递和返回流。
条件状态
IO 库定义了一个与机器无关的 iostate 类型,它提供了表达流状态的完整功能。
-
所有流对象都包含一个条件状态成员,他是strm::iostate类型的对象,以二进制位的形式使用。
-
failbit, eofbit, badbit 和 goodbit 是四个strm::iostate类型的常量值,他们本身分别表示一种条件状态,具体如下表:
badbit, eofbit, failbit,goodbit 构成了四个基本流状态。
用 cout 检测 goodbit, badbit, eofbit, failbit的值分别是0,1,2,4,这与上面的表格正好完全对应(goodbit:0000 0000; badbit:0000 0001;eofbit:0000 0010; failbit:0000 0100)。
可使用 clear 和 setstate 来操作和管理条件状态成员,他们常与badbit,eofbit,failbit,goodbit以及位或“|”结合起来使用。
用 bad(), fail(), eof()和 good() 操作可以检测流状态是否属于,还可以用 rdstate() 来返回整个条件状态成员。
其中,clear 用于设置标志位而不是清除标志位,即按照实参设置流状态,强制覆盖原有状态,其工作方式有两种:
1. 带有failbit, eofbit, badbit,goodbit中的一个或多个作为参数,按照具体实参设置流对象,例如goodbit作实参时就设置流状态各位为0。
2. 不带参数,即默认状态,效果如同带goodbit作为参数,会将所有标志位设置为有效。
setstate 用于设置标志位状态,与 clear 的区别在于它只是在原有状态的基础上以叠加的方式更新实参对应状态标志,而不会强制覆盖原有状态,使用方式:
传递 一个strm::iosatate 类型的参数(可以是单个的,也可以是用“位或”连接的多个iostate对象)
程序如下:
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
cout << std::iostream::good
<< std::iostream::badbit
<< std::iostream::eofbit
<< std::iostream::failbit << endl; // 输出 0 1 2 4
std::iostream::iostate coutstate = cin.rdstate(); // 得到cin对象的原始状态值
cout << coutstate << endl; // 输出 0 cin的状态值是 std::iostream::good
int i;
cin >> i; // 输入"123"
cout << cin.rdstate() << endl; // 输出 0 因为“123”可以被正确转成int并被存入i 所以cin的状态置为 std::iostream::good
cout << cin.good() << cin.eof() << cin.fail() << cin.bad() << endl; // 输出 1 0 0 0
cin >> i; // 输入"abd"
cout << cin.rdstate() << endl; // 输出 4 因为“abc”无法转成int存入i 所以cin的状态置为 std::iostream::failbit
cout << cin.good() << cin.eof() << cin.fail() << cin.bad() << endl; // 输出 0 0 1 0
cin.clear(); // 重置cin状态为std::iostream::good 否则下面的cin << i不会执行, 或者这样设置cin.clear(std::iostream::failbit)
cin >> i; // 输入"568"
cout << cin.rdstate() << endl; // 输出 0 因为“568”可以被正确转成int并被存入i 所以cin的状态置为 std::iostream::good
cout << cin.good() << cin.eof() << cin.fail() << cin.bad() << endl; // 输出 1 0 0 0
return 0;
}
输出缓冲区的管理
导致缓冲刷新(即,数据真正写到输出设备或文件)的原因很多:
1. 程序正常结束,作为 main 函数 return 操作的一部分,缓冲刷新被执行。
2. 缓冲区满时,需要刷新缓冲。
3. 在每个输出操作之后,我们可以用操纵符 unitbuf 设置流的内部状态,来清空缓冲区。(默认状态下,对 cerr 是设置 unitbuf 的,因此写到 cerr 的内容是立即刷新的。)
4. 一个输出可能被关联到另一个流。在这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新。(例如,默认情况下,cin 和 cerr 都关联到 cout。因此,读 cin 或写 cerr 都会导致 cout 的缓冲区被刷新。)
其中,刷新缓冲区有几个操纵符:endl、ends、flush。效果如下:
cout << "hi!" << endl; // 输出 hi 和一个换行,然后刷新缓冲区
cout << "hi!" << flush; // 输出 hi,然后刷新缓冲区,不附加任何额外字符
cout << "hi!" << ends; // 输出 hi 和一个空字符,然后刷新缓冲区
unitbuf 操纵符
使用该操纵符,就相当于告诉流在接下来的每次写操作后都进行一次 flush 操作。而 nounitbuf 操纵符则重置流。
cout << unitbuf; // 所有输出操作后都会立即刷新缓冲区
// 任何输出都立即刷新,无缓冲
cout << nounitbuf; // 回到正常的缓冲方式
注意:若程序崩溃,输出缓冲区不会被刷新。
tie函数:
tie 函数可以由 istream 或 ostream 对象调用,使用一个 ostream 流对象的指针做形参。
关联的情况:
将一个 istream 对象关联到另一个 ostream。
将一个 ostream 关联到另一个 ostream。 例如: x.tie(&o) 将流 x 关联到输出流 o。
每个流同时最多关联到一个流,但多个流可以同时关联到同一个 ostream。
tie 函数的使用:
cin.tie(&cout); // 标准库将cin 关联到 cout,cin>>ival; 时将导致 cout 的缓冲区被刷新。
ostream *old_tie = cin.tie(nullptr); // old_tie 指向当前关联到 cin 的流,cin 不再与其他流关联
cin.tie(0); // 接受0作为参数时,解除当前绑定// 将 cin 与 cerr 关联;这不是一个好主意,因为 cin 应该关联到 cout
cin.tie(&cerr); // 读取 cin 会刷新 cerr 而不是 cout
cin.tie(old_tie); // 重建 cin 和 cout 间的正常关联
程序:
#include <fstream>
#include <iostream>
using namespace std;
int main()
{
//创建文件test.txt并打开
ofstream outfile("test.txt");
//将输出流对象outfile和输入cin关联起来
cin.tie(&outfile);
//向test.txt文件中写入字符串
outfile << "abcdef";
// while (1); // 不使用 cin,则 test.txt 没有数据。注释后,执行程序到while(2),执行了cin,刷新 outfile缓存。
int in;
// 执行输入语句时会立刻刷新关联的输出流,字符串被写到文件中
cin >> in;
while(2);
return 0;
}
文件输入输出
C++ 通过以下几个类支持文件的输入输出:
ofstream: 写操作(输出)的文件类 (由ostream引申而来)
ifstream: 读操作(输入)的文件类(由istream引申而来)
fstream: 可同时读写操作的文件类 (由iostream引申而来)
附加:
void open (const char *filename, openmode mode);
其中,mode 有以下几种:
这些标识符可以被组合使用,中间以”或”操作符(|)间隔。例如,如果我们想要以二进制方式打开文件"example.bin" 来写入一些数据,我们可以通过以下方式调用成员函数open() 来实现:
ofstream file;
file.open ("example.bin", ios::out | ios::app | ios::binary);
几个类的默认参数设置:
类 | 参数的默认方式 |
---|---|
ofstream | ios::out | ios::trunc |
ifstream | ios::in |
fstream | ios::in | ios::out |
程序:
#include <fstream>
#include <iostream>
#include <stdlib.h>
using namespace std;
int main ()
{
ofstream examplefile ("example.txt");
if (examplefile.is_open())
{
// 输出到(写入)文件
examplefile << "This is a line.\n";
examplefile << "This is another line.\n";
examplefile.close();
}
ifstream f1 ("test.txt");
if (f1.is_open()){
char str[256];
// 从文件输输入(读出)一行内容到 str
f1.getline(str,10); // 读出 10 - 1 个字符
cout << str << endl;
}
else
cerr << "example.txt open failed!\n";
return 0;
}
获得和设置流指针(get and put stream pointers)
所有输入/输出流对象(i/o streams objects)都有至少一个流指针:
- ifstream, 类似istream, 有一个被称为get pointer的指针,指向下一个将被读取的元素。
- ofstream, 类似 ostream, 有一个指针 put pointer ,指向写入下一个元素的位置。
- fstream, 类似 iostream, 同时继承了get 和 put
我们可以通过使用以下成员函数来读出或配置这些指向流中读写位置的流指针:
-
tellg() 和 tellp()
这两个成员函数不用传入参数,返回pos_type 类型的值(根据ANSI-C++ 标准) ,就是一个整数,代表当前 get 流指针的位置 (用tellg) 或 put 流指针的位置(用tellp).
-
seekg() 和seekp()这对函数分别用来改变流指针get 和put的位置。两个函数都被重载为两种不同的原型:
seekg ( pos_type position ) ;
seekp ( pos_type position ) ;使用这个原型,流指针被改变为指向从文件开始计算的一个绝对位置。要求传入的参数类型与函数 tellg 和tellp 的返回值类型相同。
seekg ( off_type offset, seekdir direction ) ;
seekp ( off_type offset, seekdir direction ) ;
使用这个原型可以指定由参数 direction 决定的一个具体的指针开始计算的一个位移(offset)。它可以是:ios::beg 从流开始位置计算的位移 ios::cur 从流指针当前位置开始计算的位移 ios::end 从流末尾处开始计算的位移
流指针 get 和 put 的值对文本文件(text file)和二进制文件(binary file)的计算方法都是不同的,因为文本模式的文件中某些特殊字符可能被修改。由于这个原因,建议对以文本文件模式打开的文件总是使用seekg 和 seekp的第一种原型,而且不要对tellg 或 tellp 的返回值进行修改。对二进制文件,你可以任意使用这些函数,应该不会有任何意外的行为产生。
程序如下:
#include <iostream>
#include <fstream>
using namespace std;
const char *filename = "example.txt";
int main ()
{
long l,m;
ifstream file(filename, ios::in|ios::binary);
l = file.tellg(); // 流指针的初始位置
file.seekg(0, ios::end); // 流指针改为流末尾处
m = file.tellg(); // 流指针现在的位置
file.close();
cout << "size of " << filename << " is " << (m-l) << " bytes.\n"; // 输出 size of example.txt is 40 bytes.
return 0;
}
二进制文件(Binary files)
在二进制文件中,使用 << 和 >>,以及函数(如getline)来操作符输入和输出数据,没有什么实际意义,虽然它们是符合语法的。
文件流包括两个为顺序读写数据特殊设计的成员函数:write 和 read。第一个函数 (write) 是ostream 的一个成员函数,都是被ofstream所继承。而read 是istream 的一个成员函数,被ifstream 所继承。类 fstream 的对象同时拥有这两个函数。它们的原型是:
write ( char * buffer, streamsize size ) ;
read ( char * buffer, streamsize size ) ;
这里 buffer 是一块内存的地址,用来存储或读出数据。参数 size 是一个整数值,表示要从缓存(buffer)中读出或写入的字符数。
参考程序如下:
#include <iostream>
#include <fstream>
#include "string.h"
using namespace std;
const char * filename = "example.txt";
int main ()
{
char *buffer;
long size;
ifstream file (filename, ios::in|ios::binary|ios::ate); // ios::ate 表示指向文件末尾
size = file.tellg(); // 取得文件大小
file.seekg (0, ios::beg);
buffer = new char[size];
file.read(buffer, size);
file.close();
cout << "the complete file is in a buffer" << endl;
fstream File("test.txt",ios::out | ios::in | ios::binary); // 需自己建立 test.txt
string arr("hello,world!"); // 将Hello World!存入数组
File.write(arr.c_str(),12);// 将12个字符写入文件
File.seekg(ios::beg);// 定位至文件首部
static char read_array[10];// 在此我将打算读出些数据
File.read(read_array,12);// 读出前12个字符——"Hel"
cout << read_array << endl;// 将它们输出
File.close();
delete[] buffer;
return 0;
}
string 流
ssstream 头文件定义了三个类型来支持IO,这些类型可以向 string 写入数据,从 string 读取数据,就像 string 是个IO流一样。分别为 istringsream、ostringstream 和 stringstream 。类似于 fstream
待续。。
参考:
文件流:
https://blog.****.net/miss_acha/article/details/7194695
https://www.cnblogs.com/kingcat/archive/2012/05/09/2491847.html