在看C++编程思想中,每个练习基本都是使用ofstream,ifstream,fstream,以前粗略知道其用法和含义,在看了几位大牛的博文后,进行整理和总结:
这里主要是讨论fstream的内容:
-
#include <fstream>
-
ofstream
//文件写操作 内存写入存储设备
-
ifstream
//文件读操作,存储设备读取到内存中
-
fstream
//读写操作,对打开的文件可进行读写操作
1.打开文件
在fstream类中,成员函数open()实现打开文件的操作,从而将数据流和文件进行关联,通过ofstream,ifstream,fstream对象进行对文件的读写操作
函数:open()
-
void open ( const char * filename,
-
ios_base::openmode mode = ios_base::in | ios_base::out );
-
-
void open(const wchar_t *_Filename,
-
ios_base::openmode mode= ios_base::in | ios_base::out,
-
int prot = ios_base::_Openprot);
参数: filename 操作文件名
mode 打开文件的方式
prot 打开文件的属性 //基本很少用到,在查看资料时,发现有两种方式
打开文件的方式在ios类(所以流式I/O的基类)中定义,有如下几种方式:
ios::in | 为输入(读)而打开文件 |
ios::out | 为输出(写)而打开文件 |
ios::ate | 初始位置:文件尾 |
ios::app | 所有输出附加在文件末尾 |
ios::trunc | 如果文件已存在则先删除该文件 |
ios::binary | 二进制方式 |
-
ofstream out;
-
out.open(
"Hello.txt", ios::in|ios::out|ios::binary)
//根据自己需要进行适当的选取
0 | 普通文件,打开操作 |
1 | 只读文件 |
2 | 隐含文件 |
4 | 系统文件 |
很多程序中,可能会碰到ofstream out("Hello.txt"), ifstream in("..."),fstream foi("...")这样的的使用,并没有显式的去调用open()函数就进行文件的操作,直接调用了其默认的打开方式,因为在stream类的构造函数中调用了open()函数,并拥有同样的构造函数,所以在这里可以直接使用流对象进行文件的操作,默认方式如下:
-
ofstream out("...", ios::out);
-
ifstream in("...", ios::in);
-
fstream foi("...", ios::in|ios::out);
ofstream:打开文件不存在,默认会创建这个文件。(除非指定ios::nocreate)
ifstream:打开文件存在与否,默认不会创建在个文件.
fstream:打开文件不存在,默认会创建这个文件。(除非只是指定ios::in 或者指定ios::nocreate)
当使用默认方式进行对文件的操作时,你可以使用成员函数is_open()对文件是否打开进行验证
2.关闭文件
当文件读写操作完成之后,我们必须将文件关闭以使文件重新变为可访问的。成员函数close(),它负责将缓存中的数据排放出来并关闭文件。这个函数一旦被调用,原先的流对象就可以被用来打开其它的文件了,这个文件也就可以重新被其它的进程所访问了。为防止流对象被销毁时还联系着打开的文件,析构函数将会自动调用关闭函数close。
3.文本文件的读写
类ofstream, ifstream 和fstream 是分别从ostream, istream 和iostream 中引申而来的。这就是为什么 fstream 的对象可以使用其父类的成员来访问数据。
一般来说,我们将使用这些类与同控制台(console)交互同样的成员函数(cin 和 cout)来进行输入输出。如下面的例题所示,我们使用重载的插入操作符<<:
-
// writing on a text file
-
#include <fiostream.h>
-
int main ()
-
{
-
ofstream out("out.txt");
-
if (out.is_open())
-
{
-
out <<
"This is a line.\n";
-
out <<
"This is another line.\n";
-
out.close();
-
}
-
return
0;
-
}
-
//结果: 在out.txt中写入:
-
This is a line.
-
This is another line
从文件中读入数据也可以用与 cin>>的使用同样的方法:
-
// reading a text file
-
#include <iostream.h>
-
#include <fstream.h>
-
#include <stdlib.h>
-
-
int main () {
-
char buffer[
256];
-
ifstream in("test.txt");
-
if (! in.is_open())
-
{
cout <<
"Error opening file";
exit (
1); }
-
while (!in.eof() )
-
{
-
in.getline (buffer,
100);
-
cout << buffer <<
endl;
-
}
-
return
0;
-
}
-
//结果 在屏幕上输出
-
This is a line.
-
This is another line
- dec 格式化为十进制数值数据 输入和输出
- endl 输出一个换行符并刷新此流 输出
- ends 输出一个空字符 输出
- hex 格式化为十六进制数值数据 输入和输出
- oct 格式化为八进制数值数据 输入和输出
- setpxecision(int p) 设置浮点数的精度位数 输出
上面的例子读入一个文本文件的内容,然后将它打印到屏幕上。注意我们使用了一个新的成员函数叫做eof ,它是ifstream 从类 ios 中继承过来的,当到达文件末尾时返回true 。
状态标志符的验证(Verification of state flags)
除了eof()以外,还有一些验证流的状态的成员函数(所有都返回bool型返回值):
- bad()
如果在读写过程中出错,返回 true 。例如:当我们要对一个不是打开为写状态的文件进行写入时,或者我们要写入的设备没有剩余空间的时候。
- fail()
除了与bad() 同样的情况下会返回 true 以外,加上格式错误时也返回true ,例如当想要读入一个整数,而获得了一个字母的时候。
- eof()
如果读文件到达文件末尾,返回true。
- good()
这是最通用的:如果调用以上任何一个函数返回true 的话,此函数返回 false 。
要想重置以上成员函数所检查的状态标志,你可以使用成员函数clear(),没有参数。
获得和设置流指针(get and put stream pointers)
所有输入/输出流对象(i/o streams objects)都有至少一个流指针:
- ifstream, 类似istream, 有一个被称为get pointer的指针,指向下一个将被读取的元素。
- ofstream, 类似 ostream, 有一个指针 put pointer ,指向写入下一个元素的位置。
- fstream, 类似 iostream, 同时继承了get 和 put,fstream 中seekg和seekp是联动的,移动读指针,写指针随之移动,移动写指针,读指针也会随之移动。
我们可以通过使用以下成员函数来读出或配置这些指向流中读写位置的流指针:
- 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 的返回值进行修改。对二进制文件,你可以任意使用这些函数,应该不会有任何意外的行为产生。
以下例子使用这些函数来获得一个二进制文件的大小:
-
// obtaining file size
-
#include <iostream.h>
-
#include <fstream.h>
-
-
const
char * filename =
"test.txt";
-
-
int main () {
-
long l,m;
-
ifstream in(filename, ios::in|ios::binary);
-
l = in.tellg();
-
in.seekg (
0, ios::end);
-
m = in.tellg();
-
in.close();
-
cout <<
"size of " << filename;
-
cout <<
" is " << (m-l) <<
" bytes.\n";
-
return
0;
-
}
-
-
//结果:
-
size of example.txt is
40 bytes.
4.二进制文件
在二进制文件中,使用<< 和>>,以及函数(如getline)来操作符输入和输出数据,没有什么实际意义,虽然它们是符合语法的。
①put()
put()函数向流写入一个字符,其原型是ofstream &put(char ch),使用也比较简单,如file1.put('c');就是向流写一个字符'c'。
②get()
get()函数比较灵活,有3种常用的重载形式:
一种就是和put()对应的形式:ifstream &get(char &ch);功能是从流中读取一个字符,结果保存在引用ch中,如果到文件尾,返回空字符。如file2.get(x);表示从文件中读取一个字符,并把读取的字符保存在x中。
另一种重载形式的原型是: int get();这种形式是从流中返回一个字符,如果到达文件尾,返回EOF,如x=file2.get();和上例功能是一样的。
还 有一种形式的原型是:ifstream &get(char *buf,int num,char delim='n');这种形式把字符读入由 buf 指向的数组,直到读入了 num 个字符或遇到了由 delim 指定的字符,如果没使用 delim 这个参数,将使用缺省值换行符'n'。例如:
file2.get(str1,127,'A');//从文件中读取字符到字符串str1,当遇到字符'A'或读取了127个字符时终止。
③write 和 read
文件流包括两个为顺序读写数据特殊设计的成员函数:write 和 read。第一个函数 (write) 是ostream 的一个成员函数,都是被ofstream所继承。而read 是istream 的一个成员函数,被ifstream 所继承。类 fstream 的对象同时拥有这两个函数。它们的原型是:
read ( char * buffer, streamsize size );
这里 buffer 是一块内存的地址,用来存储或读出数据。参数size 是一个整数值,表示要从缓存(buffer)中读出或写入的字符数。
-
// reading binary file
-
#include <iostream>
-
#include <fstream.h>
-
const
char * filename =
"test.txt";
-
-
int main () {
-
char * buffer;
-
long size;
-
ifstream in (filename, ios::in|ios::binary|ios::ate);
-
size = in.tellg();
-
in.seekg (
0, ios::beg);
-
buffer =
new
char [size];
-
in.read (buffer, size);
-
in.close();
-
-
cout <<
"the complete file is in a buffer";
-
-
delete[] buffer;
-
return
0;
-
}
-
//运行结果:
-
The complete file is in a buffer
5.缓存和同步(Buffers and Synchronization)
当我们对文件流进行操作的时候,它们与一个streambuf 类型的缓存(buffer)联系在一起。这个缓存(buffer)实际是一块内存空间,作为流(stream)和物理文件的媒介。例如,对于一个输出流, 每次成员函数put (写一个单个字符)被调用,这个字符不是直接被写入该输出流所对应的物理文件中的,而是首先被插入到该流的缓存(buffer)中。
当缓存被排放出来(flush)时,它里面的所有数据或者被写入物理媒质中(如果是一个输出流的话),或者简单的被抹掉(如果是一个输入流的话)。这个过程称为同步(synchronization),它会在以下任一情况下发生:
- 当文件被关闭时: 在文件被关闭之前,所有还没有被完全写出或读取的缓存都将被同步。
- 当缓存buffer 满时:缓存Buffers 有一定的空间限制。当缓存满时,它会被自动同步。
- 控制符明确指明:当遇到流中某些特定的控制符时,同步会发生。这些控制符包括:flush 和endl。
- 明确调用函数sync(): 调用成员函数sync() (无参数)可以引发立即同步。这个函数返回一个int 值,等于-1 表示流没有联系的缓存或操作失败。
步骤1:添加头文件 #include <afx.h>
步骤2:定义CFile类的对象
格式:文件流类型 文件流对象名
-
//定义输入/输出文件流对象,可读可写
-
CFile iofile;
步骤3:打开要操作的文件
格式:文件流对象名.Open( 文件对象, 打开方式 );
-
//打开当前工作空间下的名为FileName的文件,如txtfile.txt、wavfile.wav
-
iofile.Open(
"FileName", 打开方式 );
-
//打开绝对路径FilePath下的文件,如D:\\txtfile.txt。注:必须路径是双反斜杠
-
iofile.Open(
"FilePath", 打开方式 );
-
//以输入方式打开文件,只读。前提是文件必须存在
-
iofile.Open( 文件对象, CFile::modeRead );
-
//建立输出方式文件,只写。如存在此名字文件,则清除原有内容
-
iofile.Open( 文件对象, CFile::modeCreate|CFile::modeWrite );
-
//以输入输出方式打开文件,可读可写
-
iofile.Open( 文件对象, CFile::modeWrite|CFile::modeRead );
-
//建立输出方式文件,只写。如存在此名字文件,则保留原有内容
-
iofile.Open( 文件对象, CFile::modeCreate|CFile::modeNoTruncate );
-
//以二进制方式方式打开输出文件
-
iofile.Open( 文件对象, CFile::modeWrite|CFile::typeBinary );
步骤4:设置文件读写指针位置
格式:文件流对象名.GetPosition(); //获得文件指针当前位置
格式:文件流对象名.SeekToBegin(); //将文件指针移到文件头
格式:文件流对象名.SeekToEnd()(); //将文件指针移到文件尾
格式:文件流对象名.Seek( 位移量,参照位置值 ); //以参照位置为基础将文件指针移动位移量
-
//将文件读/写指针移到距文件起始100字节位置
-
iofile.Seek(
100, CFile::begin );
-
//将文件读/写指针从当前位置向文件尾方向移50字节
-
iofile.Seek(
50, CFile::current );
-
//将文件读/写指针从当前位置向文件头方向移50字节
-
iofile.Seek(
-50, CFile::current );
-
//将文件读/写指针从文件尾回移50字节
-
iofile.Seek(
-50, CFile::end );
步骤5:对文件进行读写操作
格式:文件流对象名.Read/Write( 数据地址, 数据长度 );
-
//从文件中读取100字节数据按序从Rbuf地址开始存
-
iofile.Read( &Rbuf,
100 );
-
//将Wbuf地址开始的100字节数据按序写入文件
-
iofile.Write( &Wbuf,
100 );
步骤6:操作结束,关闭文件
格式:文件流对象名.Close();
备注
【1】当需要判断文件读写是否到文件尾时,可联合使用xxfile.GetPosition()函数与xxfile.GetLength()函数。
【2】在VC6.0下编译程序无误后,生成链接时报错:
nafxcwd.lib(thrdcore.obj) : error LNK2001:unresolved external symbol __endthreadex
nafxcwd.lib(thrdcore.obj) : error LNK2001: unresolved external symbol __beginthreadex
fatal error LNK1120: 2 unresolved externals
是因为CFile属于MFC类,非C++编译系统提供,故需在软件Project/ Setting/ General下将默认的Not Using MFC改为Use MFC in a Static Library
CStdioFile类继承自CFile类,CStdioFile对象表示一个C运行函数fopen打开的的流式文件。流式文件是被缓冲的,而且可以以文本方式(默认)或者二进制方式打开。
CStdioFile类不支持CFile类中的Duplicate、LockRange、UnlockRange函数,如果你使用了,会得到C Not Supported Exception类的错误。
CStringFile类默认的是按照Text模式操作文件。
CFile 类默认的是按照二进制模式操作文件。
成员变量
m_pStream成员变量:打开文件的指针。
构造函数
-
CStdioFile();
-
-
CStdioFile(FILE *pOpenStream);
-
-
CStdioFile(LPCTSTR lpFileName, UINT nOpenFlags);
-
-
throw(CFileException);
-
-
FILE *pOpenStream:指的是c运行函数fopen调用后返回的文件指针。
-
-
LPCTSTR lpFileName:指的是被打开的文件(绝对地址或相对地址)
-
-
UINT nOpenFlags:指的是CFile类中所描述的打开文件的方式。
-
-
//
读函数
-
virtual LPTSTR ReadString(LPTSTR lpsz, UINT nMax);
-
throw(CFileException);
如果使用该函数读取文本文件,当遇到'\r\n',停止读取,并去掉'\r',保留'\n',并在字符串尾部增加'\0',nMax的长度包含有'\0'字符,实际的分析如下:
- 如果nMax <= 字符数,读取(nMax-1)个字符+0x00;
- 如果nMax = 字符数 + 1,读取nMax个字符+0x00;
- 如果nMax > 字符数,读取nMax个字符+0x0A('\n') + 0x00;
- 如果文件有多行,则当文件没有读完时,返回NOT NULL,读到文件尾,返回NULL。
-
BOOL ReadString(CString& rString);
-
throw(CFileException);
读取一行文本到rString中,遇到回车换行符停止读取,回车和换行符均不读到rString中,尾部也不添加'0x00'。如果文件有多行,则当文件没有读完时,返回TRUE,读到文件尾,返回FALSE。
写函数
-
virtual void WriteString(LPTSTR lpsz);
-
throw(CFileException);
将缓冲区中的数据写入到与CStdioFile对象相关联的文件中,不支持CString类型数据写入,结束的'\0'不被写入到文件中,lpsz缓冲区中的所有换行符被替换为回车换行符,即'\n'转换为'\r\n'。
三、CStdioFile类编程原理
假如要进行的文件操作只是简单的读写整行的字符串,那么最好使用CStdioFile。首先把文本文件的每行数据读到一个缓冲区,然后使用sscanf把它转化为字符格式。
例如在一个txt文件里每一行数据格式是这样的:
A1 A2 A3 A3 ......An
那么读取的主体代码是:
-
CStdioFile File;
// 定义一个CStdioFile类变量File
-
CString FileData;
// 定义一个CString,作为一个缓冲区
-
//定义n个临时字符串变量,大小依据实际情况,这里暂设为10
-
char TempStr1[
10],TempStr2[
10]......TempStrN[
10];
-
File.ReadString(FileData);
// 将一行数据读到缓冲区
-
//将该行数据的n个字符读到n个临时字符串变量
-
sscanf(FileData,
"%s %s %s %s ......%s",TempStr1,TempStr2......TempStrN);
这种读法的一个好处是对文本格式要求不严,如下面的格式也可以
(前面可有未知个空格) A1 A2 (两个数据之间也可有未知个空格) A3 A3 ......An
四、CStdioFile类编程应用
以一个单文档程序为例。该程序的主要功能是读取文本文件的坐标数据,然后在客户区里用直线将这些坐标连起来,并显示。
1、 启动Visual C++6.0,生成一个单文档的工程,将该工程命名为ReadCoodinate。
2、 添加一个“读取文本数据”的菜单项。
3、 给视图类添加两个public变量:
-
CArray<CPoint,CPoint> m_PointArray;
// 用于记录坐标点数据
-
int m_PointNum;
// 用于记录坐标点个数,在视图类构造函数中初始化为0。
4、 给“读取文本数据”添加相应的单击消息响应函数。代码如下:
-
void CReadCoodinateView::OnReaddata()
-
{
-
// TODO: Add your command handler code here
-
CFileDialog dlg(TRUE);
// 定义一个文件对话框变量
-
if(dlg.DoModal()==IDOK)
-
{
-
CString m_FilePath = dlg.GetPathName();
//取得文件路径及文件名
-
CStdioFile File;
-
File.Open(m_FilePath,CFile::modeRead);
//以读模式打开文本文件
-
CString FileData;
//定义一个CString变量作为缓冲区
-
//定义两个临时字符串,并初始化为'\0'
-
char TempStr1[
10];
-
char TempStr2[
10];
-
memset(TempStr1,
'\0',
10);
-
memset(TempStr2,
'\0',
10);
-
File.ReadString(FileData);
//读取第一行数据,第一行数据为坐标点数据
-
sscanf(FileData,
"%s",TempStr1);
-
m_PointNum = atoi(TempStr1);
// 获取坐标点个数
-
//逐行读取坐标数据
-
for (
int i =
0;i<m_PointNum;i++)
-
{
-
File.ReadString(FileData);
-
sscanf(FileData,
"%s %s",TempStr1,TempStr2);
-
m_PointArray.Add(CPoint(atoi(TempStr1),atoi(TempStr2)));
//将其存入坐标点数组
-
}
-
CDC *pDC = GetDC();
//获取设备环境;
-
//根据坐标点绘出直线
-
for (i =
0;i<m_PointNum
-1;i++)
-
{
-
pDC->MoveTo(m_PointArray[i].x, m_PointArray[i].y);
-
pDC->LineTo(m_PointArray[i+
1].x, m_PointArray[i+
1].y);
-
}
-
ReleaseDC(pDC);
//使用完后,释放设备环境
-
}
-
}
其中示例数据文件的格式是这样的:(第一行为坐标个数,余下的是坐标点数据)
5
10 20
30 40
45 85
100 120
200 300
这个程序的一个优点是对文本数据格式并不严格,只要x坐标和y坐标中间有一个空格就可以了。若要将文本数据传化为其它形式的数据(如将字符型数据转化为浮点型可以利用函数atof()),其中原理是一样的。