9-IO流
1、简介
- 流/流数据(Stream)
- 字节序列形式的数据如:二进制数据、文本字符等等),犹如流水一般,从一个对象流向另一个对象
- 输入流(Input Stream)
- 数据从表示输入设备(如:键盘、磁盘文件等)的对象流向内存对象。
- 输出流(Output Stream)
- 数据从内存对象流向表示输出设备(如:显示器、打印机等)的对象。
- 流缓冲(Stream Buffer)
- 介于各种I/O设备和内存对象之间的内存缓冲区
- 当从键盘输入时,数据首先进入键盘缓冲区,直到按下回车键,才将键盘缓冲区中的数据灌注到输入流缓冲区,之后再通过流操作符“>>”,进入内存对象
- 当向显示器输出时,数据首先通过流操作符“<<”从内存对象进入输出流缓冲区,直到缓冲区满或遇到换行符,才将其中的数据灌注到显示器上显示出来
- 介于各种I/O设备和内存对象之间的内存缓冲区
- 流对象(Stream Object)
- 表示各种输入输出设备的对象,如键盘、显示器等,以流的方式接收或提供数据,故称为流对象
- 向下访问各种物理设备接口,向上与应用程序交互,中间维护流缓冲区
- 三个预定义的标准流对象
- cin:标准输入设备 如:键盘
- cout:标准输出设备 如:显示器
- cerr:标准错误输出设备 如:显示器,不带缓冲
- 流类(Stream Class)
- 用于实例化流对象的类
- cin和cout分别是istream_withassign和ostream_withassign类的对象
- 流类库(Stream Class Library)
- C++以继承的方式定义了一组流类,并将其作为标准C++库的一部分提供给用户
- 基于流类库可以构建三种形式的流对象
- 面向控制台的I/O流
- 面向文件的I/O流
- 面向内存的I/O流
2、I/O流类库
fstreambase:封装对文件的操作
istream:封装输入的操作
ostream:封装输出的操作
strstreambase:封装对字符串的操作
3、I/O流的打开与关闭
3.1 打开I/O流
在C++中我们一般是使用构造函数的方式打开I/O流,比如:
- 打开输入流
ifstream (const char* filename,openmode mode);
- 打开输出流
ofstream (const char* filename,openmode mode);
- 打开输入输出流
fstream (const char* filename,openmode mode);
filename:文件路径,mode:打开方式
3.2 文件的打开方式
文件的打开方式有6种,分别是:
- 1:ios::out
- 打开文件用于写入,文件不存在则创建该文件,文件存在的情况下会清空里面的内容
- 适用于ofstream/fstream
- 2:ios::app
- 打开文件用于追加,文件不存在则创建该文件,文件存在的情况下会在文件末尾追加内容
- 适用于ofstream/fstream
- 3:ios::trunc
- 打开时清空原内容,文件不存在则创建该文件
- 适用于ofstream/fstream
- 4:ios::in
- 打开文件用于读取,文件不存在则打开失败,文件存在时不会清空里面的数据
- 适用于ifstream/fstream
- 5:ios::ate
- 打开时定位到文件尾
- 适用于ifstream/fstream
- 6:ios::binary
- 以二进制模式读写
- 适用于ifstream/ofstream/fstream
int main(void){
ofstream ofs("./abc", ios::app);// 以追加的方式打开文件
if (!ofs){ // 这里会调用ofs.operator bool() 函数
cerr << "ofs流对象状态失败--读取文件失败" << endl;
}
ofs << "13 17.54 hello world" << endl;
if (!ofs){ // 每一步IO操作(打开文件、写文件、设置文件等等)结束后,都可以将流对象放置在布尔上下文中,来判断IO操作是否成功
cerr << "ofs流对象状态失败--写入数据失败" << endl;
}
ofs.close();
ifstream ifs1("./abc", ios::in);// 打开文件用于读取
if (!ifs1){
cerr << "ifs1流对象状态失败--打开文件失败" << endl;
}
int a; double b; string c, d;
ifs1 >> a >> b >> c >> d;
if (!ifs1){
cerr << "ifs1流对象状态失败--读取文件失败" << endl;
}
cout << a << " " << b << " " << c << " " << d << endl;
return 0;
}
3.3 I/O流对象的状态
I/O流类对象内部保存当前状态,其值为
- ios::goodbit
- 值为0:表示一切正常
- ios::badbit
- 值为1:表示发生致命错误
- ios::eofbit
- 值为2: 表示遇到文件尾
- ios::failbit
- 值为4: 表示打开文件失败或实际读写字节数未达到预期
注意:I/O流对象支持到bool类型的隐式转换(利用类型转换操作符函数) - 当流的状态值为1、2、4等情况时,流对象返回的是false,否则返回true
- 每一次的I/O操作(打开文件、读取数据等)结束后都可以将流对象直接放置于布尔上下文中,用来判断I/O操作是否成功
- 处于状态1或4的流,在复位之前是无法工作的
- 值为4: 表示打开文件失败或实际读写字节数未达到预期
int main(){
ifstream if2("./ofs", ios::ate); // 打开时文件指针在文件尾
if (!if2){
cout << "if2流对象状态错误--打开文件失败" << endl;
}
int a; double b; string c;
if2 >> a >> b >> c;
if (!if2){
cout << "if2流对象状态错误--if2文件读取失败" << endl;
cerr << "if2是0状态吗?" << if2.good() << ",if2是1状态吗?" << if2.bad() << ",if2是2状态吗?" << if2.eof() << ",if2是4转态吗?" << if2.fail() << endl;
// 获取当前文件的状态
cout <<"if2的状态码是"<< if2.rdstate() << endl;
}
cout << a << b << c;
if2.close();
return 0;
}
3.4 I/O流状态成员函数
注意当以ate模式打开的文件时,如果需要读取里面的内容,需要移动读取指针的位置,以保证能读取到数据
int main(){
ifstream if2("./ofs", ios::ate); // 打开时文件指针在文件尾
if (!if2){
cout << "if2流对象状态错误--打开文件失败" << endl;
}
cout << if2.rdstate()<<endl;// 读取文件的状态
if2.seekg(0, ios::beg);// 移动读指针到文件的第一个字节
int a; double b; string c;
if2 >> a >> b >> c;
if (!if2){
cout << "if2文件读取失败" << endl;
}
cout << a << b << c;
if2.close();
return 0;
}
4、非格式化I/O
- 写入字符
- 函数:ostream & ostream::put(char ch);
- 一次向输出流写入一个字符,返回流本身
- 函数:ostream & ostream::put(char ch);
- 刷输出流
- 函数:ostream & ostream::flush(void);
- 将输出流缓冲区中的数据刷到设备上,返回流本身
- 函数:ostream & ostream::flush(void);
int main(){
ofstream of2("./out", ios::out); //
if (!of2){
cerr << "of2流对象状态错误--打开文件失败" << endl;
}
for (char c = ' '; c <= '~'; c++){
of2.put(c).flush();// 向输出流中写入一个字符,并刷新缓冲区
}
of2.close();
return 0;
}
- 读取字符
- 无参get函数:int istream::get(void);
- 成功返回读到的字符,失败或遇到文件尾则返回EOF
- 单参get函数:istream& istream::get(char& ch);
- 返回输入流本身,其在布尔上下文中的值,成功尾true,失败或遇到文件结尾为false
- 无参get函数:int istream::get(void);
int main(){
ifstream ifs("./out", ios::in);
if (!ifs){ cerr << "ifs流对象状态错误--打开文件失败" << endl; }
char c;
// 单参get
while (1){
ifs.get(c);
if (!ifs){
cout << endl;
cout << "文件读取完成" << endl;
break;
}
else{
cout << c;
}
}
ifs.clear();// 清除文件状态
ifs.seekg(0, ios::beg);
// 无参get
while (1){
c = ifs.get();
if (c == EOF){
cout << endl;
cout << "文件读取完成" << endl;
break;
}
else{
cout << c;
}
}
ifs.close();
}
- 读取行
- 函数:istream& istream::getline(char* buffer,streamsize num,char delim=‘\n’);
- 读取字符(至定界符)到buffer中
- 一旦读取了num个字符还未读取定界符,第num个字符设置为’\0’,返回(输入流对象状态为4)
- 如果因为遇到定界符(缺省为’\0’)则返回定界符会被读取并丢弃,并在末尾追加结尾空字符’\0’,而此时输入流对象状态为0,,读指针停在该定界符的下一个位置。
- 遇到文件尾,直接返回,此时的输入流对象状态为6
- 注意:使用getline函数的时候需要注意,在读取定界符是会将定界符替换为’\0’,或者是将第num个字符替换为’\0’,这么操作修改了源数据
- 函数:istream& istream::getline(char* buffer,streamsize num,char delim=‘\n’);
int main(){
ifstream ifs1("./getline", ios::in);
if (!ifs1){ cerr << "ifs流对象状态错误--打开文件失败" << endl; }
while (1){
char buff[256];
ifs1.getline(buff, 256, '\n');// 如果因为遇到定界符(缺省为‘\n’)返回(输入流对象状态为0)定界符被读取并丢弃,追加结尾空字符‘\0’,读指针停在该定界符的下一个位置。
if (ifs1){
cout << ifs1.rdstate() << "数据:" << buff;// 因为缺省值为‘\n’,所以最终输出出来的数据没有换行,这是因为定界符被读取并丢弃l,追加结尾空字符‘\0’导致的
}
else{
break;
}
}
ifs1.close();
return 0;
}
5、二进制I/O
- 读取二进制数据
- 函数:istream& istream::read (char* buffer,streamsize num);
- 从输入流中读取num个字节到缓冲区buffer中
- 返回流对象本身,其在布尔上下文中的值,成功(读满)为true,失败(没读满)为false
- 如果没读满num个字节,函数就返回了,比如遇到文件尾,最后一次读到缓冲区buffer中的字节数,可以通过istream::gcount()函数获得
- 函数:istream& istream::read (char* buffer,streamsize num);
- 获取读长度
- 函数:streamsize istream::gcount (void);
- 返回最后一次从输入流中读取的字节数
- 函数:streamsize istream::gcount (void);
- 写入二进制数据
- 函数ostream& ostream::write (const char* buffer,streamsize num);
- 将缓冲区buffer中的num个字节写入到输出流中
- 返回流本身,其在布尔上下文中的值,成功(写满)为true,失败(没写满)为false
- 函数ostream& ostream::write (const char* buffer,streamsize num);
int main(){
ifstream ifs("./getline", ios::in);
ofstream ofs("./outwrite", ios::out);
if (!ifs){ cerr << "ifs流对象状态错误--打开文件失败" << endl; }
if (!ofs){ cerr << "ofs流对象状态错误--打开文件失败" << endl; }
char buff[3];
while (1){
ifs.read(buff, 3);//从输入流中读取num个字节到缓冲区buffer中,返回流对象本身,其在布尔上下文中的值,成功(读满)为true,失败(没读满)为false
if (ifs){// 意味着读满了3个字节
ofs.write(buff, 3);
}
else{ // 没读满3字节
int count = ifs.gcount();// 获取最后一次读取到的字节数
ofs.write(buff, count);
break;
}
}
ifs.close();
ofs.close();
return 0;
}
6、读写指针与随机访问
- 设置读/写指针位置
- 函数:istream& istream::seekg (off_type offset,ios::seekdir origin);// 设置读指针
- ostream& ostream::seekp (off_type offset,ios::seekdir origin);// 设置写指针
- origin表示偏移量offset的起点有下面三种表示方式
- ios::beg:从文件的第一个字节
- ios::cur:从文件的当前位置
- ios::end:从文件最后一个字节的下一个位置
- offset表示距起点位置的偏移量,为负/正表示向文件头/尾的方向偏移
- 当读/写指针被移到文件头之前或文件尾之后,则失败
- origin表示偏移量offset的起点有下面三种表示方式
- 获取读/写指针位置
- 函数:pos_type istream::tellg (void);// 返回读指针的位置
- pos_type ostream::tellp (void);// 返回写写指针的位置
- 返回读/写指针当前位置相对于文件头的字节偏移量
int main(){
ifstream ifs("./getline", ios::ate);// 将文件读指针设置到文件尾
ofstream ofs("./zpyl", ios::out);
if (!ifs){ cerr << "ifs流对象状态错误--打开文件失败" << endl; }
if (!ofs){ cerr << "ofs流对象状态错误--打开文件失败" << endl; }
int size = ifs.tellg();// 文件大小
char buff[size];
ifs.clear();
ifs.seekg(0, ios::beg);
ifs.read(buff, size);
ofs.write(buff, size);
ifs.close();
ofs.close();
return 0;
}
\\\\\\\\\ 一下内容只做了解 \\\\\\\\\\\\\\\\\
7、字符串流
- 输出字符串流
- 将不同类型的数据组合成一个字符串
# include <sstream>
int main(){
ostringstream oss;
oss << 1234 << " " << 56.78 << " " << "ABCD";
string str = oss.str();
cout << str <<endl;
return 0;
}
- 输入字符串流
- 将一段字符串中的数据,分别取出来
#include <sstream>
int main(){
string is("1234 56.78 ABCD");
istringstream iss(is);
int i;
double b;
string c;
iss >> i >> b >> c;
cout << i << c << b << endl;
return 0;
}
8、格式化I/O
8.1 简介
- 流函数(一组成员函数)
- I/O流类(ios)定义了一组用于控制输入输出格式的公有成员函数,调用这些函数可以改变I/O流对象内部的格式状态,进而影响后续输入输出的格式化方式
- 流控制符(一组全局函数)
- 标准库提供了一组特殊的全局函数,它们有的带有参数(在iomanip头文件中声明),有的不带参数(在iostream头文件中声明)
- 因其可被直接嵌入到输入输出表达式中,影响后续输入输出格式,故形象称之为流控制符
8.2 I/O流格式化函数
- 一般而言,对I/O流格式的改变都是持久的,即只要不再设置新格式,当前格式将始终保持下去
- 显示域宽是个例外,通过ios::width(int)所设置的显示域宽,只影响紧随其后的第一次输出,再往后的输出又恢复到默认状态
8.3 I/O流格式标志
- 例子
8.4 I/O流格式化控制符
- 例子