[MFC]CFile类实现低级文件I/O

来源:互联网 发布:知乎 谈恋爱经历 编辑:程序博客网 时间:2024/04/26 04:53

1. CFile类简介:

    1) CFile封装了操作系统对文件读写的相关操作API,是MFC中一种较为低级的I/O操作手段,MFC常用CArchive类对数据进行串行化写入读出磁盘;

    2) CFile的几个基本的数据成员:

         i. UINT m_hFile:即和CFile相关联的文件的句柄,实为文件描述符,是一个UINT型的值;

         ii. CString m_strFileName:即文件的名称,包含的是文件的完整的绝对路径名称;

    3) 和文件名有关的几个CFile的成员函数:

         i. 获取完整的绝对路径名:virtual CString CFile::GetFilePath() const;

         ii. 只获取文件名(不包括路径,但是包括后缀,比如note.txt):virtual CString CFile::GetFileName() const;

         iii. 只获取文件的标题(即不包括后缀,比如note.txt只返回note):virtual CString CFile:GetFileTitle() const;


2. 打开文件:

     i. 共有两种方法,一种是使用CFile的构造函数直接打开文件,另一种是使用CFile::Open函数打开,这两种函数都考虑到了文件打开失败的情形,因此需要进行相关的异常处理;

     ii. 构造函数打开:

          a. 函数原型:CFile::CFile(LPCTSTR lpszFileName, UINT nOpenFlags) throw(CFileException);

!!第一个参数是文件名,第二个参数是打开方式(只读、读写等),该函数还会抛出异常,可以通过该异常对象来查看是何种类型的异常

          b. 使用范例:

try{CFile file(_T("File.txt"), CFile::modeReadWrite);...}catch (CFileException* e){e->ReportError();e->Delete();};
!!这种方式必须要处理异常,如果不接受处理异常则异常将会抛向上一层函数,直至最终的操作系统,一般来说都是要在当前层的当前位置处理异常;

     iii. CFile::Open打开文件:

          a. 函数原型:virtual BOOL CFile::Open(LPCTSTR lpszFileName, UINT nOpenFlags, CFileException* pError = NULL);

          b. 如果打开成功返回TRUE,否则返回FALSE,因此可以通过一个if语句判断文件是否打开成功,如果打开不成功则可以跳过或进行相应的处理,可以看到第三个参数就是用来接收抛出的异常,这项是可选的,如果你想查看异常类型是什么,则可以填上这项参数并在else语句中查看异常的类型:

CFile file;CFileException e;if (file.Open(_T("file.txt"), CFile::modeReadWrite, &e)){ // 打开成功,进行相关的操作...}else{e.ReportError();e.Delete();}

3. 文件异常类CFileException:

    1) 各种文件操作的异常(不仅仅是Open一个函数,而是所有和CFile有关的操作所产生的异常)都用CFileException类来处理;

    2) 最重要的public数据成员m_cause,该成员变量是一个int型的值,MFC已经定义好了所有的文件异常类型,这些异常类型是该类的静态数据成员,使用时以CFileException::作为前缀;

    3) 常见的m_cause值:

none:没有发生异常

generic:未知异常

diskFull:磁盘已满,发生在写磁盘的时候

fileNotFound:文件未找到

badPath:文件路径有误

    4) 最常用的两个成员函数:

         i. ReportError():弹出一个MessageBox提示出错,框中会包含异常的类型;

         ii. Delete():将MFC发送的这个异常对象删除掉,这其实是MFC的一个安全编程保护机制,该函数内部会检测异常对象是否是在堆上动态分配的,如果是则会在内部调用delete运算符将其释放,若是在栈上则不使用delete操作符释放,这避免了一个问题,那就是代码一多程序员可能也忘记掉了该异常对象是new出来的还是全局的还是局部栈上的,而这一切都交由Delete自动判断并处理,这就省去了程序员内存管理上的压力了,因此约定俗成的好习惯就是异常对象使用完毕之后就调用Delete函数将其安全释放!!

!!!Delete和ReportError都是基类CException的成员函数,CFileException和其它异常类都会继承这两个函数;


4. 文件打开方式:

    1) 即nOpenFlags参数,该参数主要有两类,一种是访问方式,还有一种是共享方式;

    2) nOpenFlags有CFile定义好的静态数据成员列表,以CFile::为前缀,所有的打开方式(不管是访问方式还是共享方式或是这两种方式的组合)都可以用OR进行组合;

    3) 访问方式:

modeWrite:写的方式打开,如果文件不存在则创建,如果文件存在则会将文件截断成0(相当于现删除再重新创建)后再打开;

modeNoTruncate:打开时不要截断,该方式经常和modeWrite组合,这样可以避免写文件是先将原文件删除了;

modeRead:只读方式打开,文件必须存在,不存在会抛出异常;

modeReadWrite:读写方式打开,文件必须存在,否则会抛出异常,记住只要有Read的一定都要文件事先存在的;

modeCreate:表示要创建文件,如果文件已存在则会截断至0,该项经常和modeReadWrite配合使用;

最常用的组合:

CFile::modeWrite | CFile::modeNoTruncate,若存在则不要清空

CFile::modeReadWrite | CFile::modeCreate | CFile::modeNoTruncate,读写方式要求文件必须存在,但是有了modeCreate了以后则可以允许文件不存在(不存在就直接创建),但是该选项会导致原来已存在的文件被清空,因此最后一个NoTruncate就能避免原来已存在的被清空了,这三个的组合就能保证已存在的不清空,不存在的创建了!!

    4) 共享方式:用以解决多个进程同时对一个文件读写的问题,防止发生冲突

         i. CFile定义静态数据成员列表,以CFile::share为前缀,主要有以下4个

shareDenyNone:什么都不禁止,即非独占访问方式

shareDenyRead:读锁,文件打开后禁止其它进程读文件

shareDenyWrite:写锁,禁止写

shareExclusive:禁止读写,这也是默认情况下的共享权限,即参数中不说明共享权限则默认使用该权限

!!共享原则:读的时候禁止写但可以允许读,写的时候读写都禁止!!如果你违反这个原则MFC也会在内部自动避免的,因为任何操作系统都有这方面的安全机制;

!示例:读的时候上写锁,CFile file(_T("file.txt"), CFile::modeRead | CFile::shareDenyWrite));


5. 关闭文件:

    1) 直接使用CFile::Close()即可,比如file.Close();,这种就是显示调用关闭;

    2) 第二种方式就是通过CFile的析构函数自动释放文件资源,析构函数中包含了对Close的调用;

    3) 显示调用Close主要发生在如下场合,那就是调用Close后还要使用同一个CFile对象来打开其它文件;


6. 读写文件modeReadWrite的使用——示例:将文件中的大写字母替换成小写字母写回

BYTE buffer[0x1000]; // 4KB缓存try{CFile file(_T("file.txt"), CFile::modeReadWrite);DWORD dwBytesRemaining = file.GetLength();while (dwBytesRemaining){DWORD dwPosition = file.GetPosition(); // 获取文件指针当前位置UINT nBytesRead = file.Read(buffer, sizeof(buffer)); // 一次读取4kb,但最后一次可能4KB不到::CharLowerBuff((LPTSTR)buffer, nBytesRead); // 将缓存中读取的内容转换成小写file.Seek(dwPosition, CFile::begin); // 读取后文件指针后移4KB,现在再调回原样file.Write(buffer, nBytesRead); // 把小写化后的内容重新写回文件dwBytesRemaining -= nBytesRead; // 剩余处理的内容量更新}}catch (CFileException* e) // CFile、Read、Write会抛出异常{e->ReportError();e->Delete();}
    1) virtual DWORD CFile::GetLength() const throw(CFileException);  // 获取文件内容长度

    2) virtual DWORD CFile::GetPosition() const throw(CFileException); // 获取文件指针的当前位置

    3) virtual LONG CFile::Seek(LONG lOff, UINT nFrom) throw(CFileException); // 设定文件指针的位置

         i. 第一个参数设定新指针的偏移位置,第二参数决定偏移位置是相对于哪儿偏移的;

         ii. nFrom:

CFile::begin:相对于文件开头偏移

CFile::current:相对于当前位置偏移

CFile::end:相对于末尾位置偏移

!!注意:lOff为负表示向前偏移,为正表示向后偏移,当nFrom为CFile::end时lOff一定是负的,否则就偏移出了EOF了!!

         iii. 如果正确执行则返回值是新位置(相对于CFile::begin的,不管nFrom设定的是什么),否则将返回一个不确定的值并抛出CFileException异常;

!!其他Seek系列函数:直接定位到起始或者末尾

void CFile::SeekToBegin() throw(CFileException);

DWORD CFile::SeekToEnd() throw(CFileException);

!注意,SeekToEnd还返回一个文件的大小!!

    4) 读和写:

         i. 读:

virtual UINT CFile::Read(void* lpBuf, UINT nCount) throw(CFileException);

DWORD CFile::ReadHuge(void* lpBuf, DWORD dwCount) throw(CFileException);

         ii. 写:

virtual void CFile::Write(const void* lpBuf, UINT nCount) throw(CFileException);

void CFile::WriteHuge(const void* lpBuf, DWORD dwCount) throw(CFileException);

         iii. 第一个参数都是读入或写出的缓存,第二个参数都是处理的字节个数;

         iv. 注意!读的时候返回值表示实际读入的字节数,当读到文件尾部的时候返回值可能会小于指定的读取字节数;

         v. Huge和不是Huge的区别就是Huge可以一次读写大于64KB的内容,对于大量读写有卓越的优化效果;

    5) DWORD ::CharLowerBuff(LPTSTR lpsz, DWORD cchLength);

         i. 将lpsz缓存中的所有大写字母转换成小写字母,cchLength表示处理的字符数量;

         ii. 注意!该函数明确指出处理的是字符而不是字节,字符是TCHAR,因此会根据实际是ASCII还是UNICODE决定一个字符是几个字节的;

         iii. 返回值表示实际真正处理了多少字符;

         iv. 相对的,还有DWORD ::CharUpperBuff(LPTSTR lpsz, DWORD cchLength);


7. CStdioFile:

    1) 该类直接继承与CFile,只多了两个函数,一个是ReadString另一个是WriteString;

    2) 该类用于支持C语言的文件读写,包装了fopen等C运行时函数,Open、构造函数等都和CFile一样;

    3) ReadString:

         i. 有两种形式,一种是传统'\0'结尾的字符串,一种是CString

virtual LPTSTR CStdioFile::ReadString(LPTSTR lpsz, UINT nMax) throw(CFileException);

BOOL CStdioFile::ReadString(CString& rString) throw(CFileException); // r前缀表示referrence,即引用类型的量

!如果调用成功前者返回lpsz,后者返回FALSE,否则前者返回NULL,后者返回FALSE;

         ii. 注意!微软的文本中的换行包括两个字符\r\n,ReadString读到内存中的时候只保留\n,并且末尾添加'\0',即在‘\n’后面再添加一个'\0';

    4) WriteString:

         i. virtual void CStdioFile::WriteString(LPCTSTR lpsz) throw(CFileException);

         ii. 写入的时候将\n变成\r\n一并写入文件,并且在\n后面不添加'\0';

!!注意:ReadString和WriteString的读取单位都是行,一行一行的读取,因此只支持字符型数据;

!!如果想读写二进制等的数据还是得用CFile继承来的Read和Write;


8. 枚举文件和文件夹:


0 0