一、流的概念
- 数据从内存的一个地址移动到另一个地址称为数据流动——流操作
- 流操作是通过缓冲区(buffer)机制实现的。
- 缓冲区:内存的一块区域——用作文件与内存交换数据。
- 数据从文件中读出:文件 → 缓冲区 → 内存
- 将数据写入文件:内存 → 缓冲区 → 文件
为什么要使用缓冲区而不直接从文件中读取数据到内存或者直接有内存写入文件呢?我们的文件通常都存在磁盘中,程序从磁盘读取一个字符需要大量的硬件活动,速度非常慢。缓冲方法则从磁盘上读取大量信息,将这些信息存储在缓冲区,然后每次从缓冲区里读取一个字节,因为从内存中读取单个字节的速度比从硬盘上读取快很多,所以这种方法更快,也更方便。说了这么多,只需知道缓冲方法更高效就可以了。
二、流库(stream library)介绍
使用继承的方法建立的输入输出类库,包含两个平行的基本类:streambuf和ios类,所有流类均以两者之一作为基类。
- streambuf类提供对缓冲区的低级操作:设置缓冲区,对缓冲区指针操作,向缓冲区存取字符等
- ios类及其派生类提供用户使用流类的接口
本文主要介绍ios,因为ios是编程中几乎必用的类,而streambuf类很少直接使用。下面图片来自网络:
三、文件打开与关闭
- 当程序中进行文件操作时,应加上头文件“fstream”
- 若要打开文件进行相应的操作,必须定义相应的流对象。如:ifstream in; // 文件输入流对象,ofstream out; // 文件输出流对象, fstream both; // 文件输入/输出流对象
文件打开用成员函数open();
- 原型 void(const char * s, ios_base::openmode, mode = ios_base::out|ios_base::trunc)
- 第一个参数表示打开文件的路径,第二个参数表示文件打开方式,第三个参数表示访问方式,
-
标志 含义 ios::ate 打开文件并移动到文件尾 ios::app 追加到文件尾 ios:in 作为输入文件(ifstream默认) ios::out 作为输出文件(ofstream默认) ios::trunc 若文件存在,清除文件内容(默认) ios::nocreate 若文件不存在,返回错误 ios::noreplace 若文件存在,返回错误 ios::binary 以二进制方式打开
文件打开的方法:
- 方法一:先定义一个文件流对象,再用文件流对象调用成员函数open( )打开一个文件。如:
ifstream f1; //定义文件输入流对象f1
f1.open( “d:\\vcprg\\-.cpp”); //打开D盘vcprg文件夹(目录)下的7-3.cpp文件,可进行文件读操作
- 方法二:在定义文件流对象时利用构造函数打开文件。如:
ifstream f1( “d:\\vcprg\\-.cpp”)
用文件流对象打开文件后,该对象就代表了被打开的文件。
#include<iostream>
#include <fstream> using namespace std; void main(void)
{
ifstream f1; //定义文件输入流对象
f1.open("d:\\vcprg\\7-3.cpp"); //打开文件
char c;
while(!f1.eof()) //判断文件未结束则循环,eof()在文件结束时返回true
{
f1.get(c);
cout << c;
}
cout << endl;
f1.close(); //关闭文件
}
文件访问方式:
当用ifstream定义流对象并打开一个文件时,默认为ios_base::in方式;当用ofstream定义流对象并打开一个文件时,默认为ios_base::out方式;当用fstream定义流对象并打开一个文件时,应给出打开方式(可用“位或”运算以多种方式打开文件)如:
fstream f;
f.open(“file.cpp”,ios_base::in|ios_base::out)
例:将D盘vcprg文件夹(目录)下的7-3.cpp文件,复制为text.txt文件。
#include <iostream>
#include <fstream> using namespace std; void main(void)
{
ifstream f1; //定义文件输入流对象
ofstream f2;
f1.open("d:\\vcprg\\7-3.cpp"); //以读方式打开文件
f2.open("d:\\vcprg\\text.txt"); //以写方式打开文件
char c;
while(!f1.eof()) //判断文件未结束则循环,eof()会在文件结束时返回true
{
f1.get(c);
f2<<c;
}
cout << "文件复制成功" << endl;
f1.close(); //关闭文件
f2.close();
}
文件异常处理:
当打开一个文件可能失败,在程序中通常应加上异常处理程序。
(1) 用条件语句判断文件流标志位failbit是否为true。
if(!文件流对象){<异常处理程序>}
if(文件流对象){<正常处理程序>}(上面一段示例的13行就是用这种方式)
(2)用成员fail()函数
if(文件流对象.fail()){<异常处理程序>}
if(!文件流对象.fail()){<正常处理程序>}
(3)用成员函数good()
if(!文件流对象.good()){<异常处理程序>}
if(文件流对象.good()){<正常处理程序>}
(4)较新的C++实现提供了一种更好的方式is_open()方法,推荐用此方法,但是老式C++没有实现这种方法,用上面三种均可
文件的关闭:
格式:文件流对象.close();
关闭文件操作包括把缓冲区数据完整地写入文件,添加文件结束标志,切断流对象和外部文件的连接若流对象的生存期没有结束,可以重用当一个流对象的生存期结束,系统也会自动关闭文件
例如:
ofstream ofile ; // 创建输出文件流 ofile . open ( "myfile1" ) ; // ofile流与文件“myfile1”相关联 …… // 访问文件“myfile1” ofile . close ( ) ; // 关闭文件“myfile1” ofile . open ( "myfile2" ) ; // 重用ofile流
四、文件读写
文本文件的读写:
方法get(char&)和get()提供不跳过空白的单字符输入功能;
方法put(char&)和put()提供不跳过空白的单字符输出功能;
函数get(char*,int,char)和getline(char*,int,char)在默认情况下读取整行;
二进制文件的读写:
二进制文件的读/写分别用成员函数read( )、write( )实现。写二制文件的格式,输出文件流对象.write((char*)&对象或&对象数组名[下标],sizeof(对象名或所属类名));
读二进制文件的格式,输入文件流对象.read((char*)&对象或&对象数组名[下标],sizeof(对象名或所属类名)); 使用格式控制(关于格式控制可以参考http://wonderow.cnblogs.com/archive/2005/06/21/178719.html,写得很详细)建立的文本文件:
#include <fstream>
#include <iomanip> using namespace std; int main ()
{
ofstream ost ;
ost.open ( "d:\\my2.dat" ) ;
ost << "" << endl ;
int a = ;
ost << a << endl ;
ost << setw ( ) << a << endl ;
ost << resetiosflags ( ios :: right ) << setiosflags ( ios :: left )
<< setfill ( '#' ) << setw ( ) << a << endl ;
ost << resetiosflags ( ios :: left ) << setiosflags ( ios :: right )
<< setprecision ( ) << setw ( ) << 12.34567890 << endl ;
ost . close ( ) ;
return ;
}
建立一个包含学生学号、姓名、成绩的文本文件:
#include <iostream>
#include <fstream>
#include <cstdlib> using namespace std; int main()
{
char fileName[] , name[] ;
int number , score ; ofstream outstuf; //建立输出流对象 cout << "Please input the name of students file :\n" ;
cin >> fileName ; //输入文件名 outstuf.open(fileName, ios::out) ; //以输出方式打开文件 if ( !outstuf )
{
cerr << "File could not be open." << endl ;
exit();
}
outstuf << "学生成绩文件\n" ;
cout << "Input the number, name, and score : (Enter Ctrl-Z to end input)\n? " ;//windows中Ctrl-Z 结束输入和输出 while( cin >> number >> name >> score )
{
outstuf << number << ' ' << name << ' ' << score << '\n' ;
cout << "? " ;
}
outstuf.close() ;
return ;
}
二进制文件,从一个学校教程里搜罗的图,讲的很清晰:
五、总结
- istream类定义了多个版本的抽取运算符(>>),用于识别所有基本的C++类型,并将字符输入转换为这些类型。get()方法族和getline()方法为单字符输入和字符串输入提供了进一步支持;
- ostream类定义了多个版本的插入运算符(>>),用于识别所有基本的C++类型,并将字符输入转换为这些类型输出。put()方法为单字符输入和字符串输入提供了进一步支持;
- 使用ios_base类方法以及文件iostream和iomanip中定义的控制符(参考http://wonderow.cnblogs.com/archive/2005/06/21/178719.html)可以控制格式化输出;
- 对文件操作,必须将文件与流关联起来,通过ifstream对象与文件关联,可以使用所有istream方法来读取文件,使用ofstream对象与文件关联起来,可以使用ofstream方法来写文件,使用fstream对象关联文件,可以将fstream的输入和输出方法用于文件;
- 要将文件与流关联,先创建一个文件流对象,然后用open()方法将这个流与文件关联,close()方法终止流与文件的关联;
- 文本文件是以字符格式存储信息,例如数字值将会被转变为字符表示;
- seekg()和seekp()通过指针提供对文件的随机存取。tellg()和tellp()方法报告当前文件的位置.