VC----MFC文件操作的那些事儿

时间:2023-02-04 05:25:39
今天状态不错,下午继续学习MFC的文件操作章节。自己以前经常使用C,因此最熟悉的当然是C的文件操作命令了;不过今天要学习的是在图形MFC下进行文件操作。在学习MFC为我们提供的文件操作类之前,首先简单回顾下C语言文件操作的知识点,然后分析下文本文件与二进制文件的不同,随后介绍win32提供的文件操作API,最后简单讨论下MFC提供的CFile类进行文件的创建、读写操作,此外介绍利用MFC的CFileDialog类实现“打开”与“保存”对话框。

一、C语言文件操作梳理
     C语言为我们提供了方便的库函数进行文件的读写操作,其实可以将C的文件操作函数看作两类:一类是用于创建、读写文件的fopen\fread\fwrite函数,另一类时文件读写辅助函数,如文件指针操作fseek\ftell以及缓存刷新fflush函数。这些函数的用法想必大家一定都和那熟悉了,那这里就不再赘述语法了,只是梳理下这部分需要注意的知识点。
1. 分清const char*与char * const
     当const放于*之前时,用来修饰指针类型,因此表示指向一个常量的指针,即指针的值可以改变,但是不可以通过该指针与改变其指向的变量;当const放于*之后但是指针名之前时,用来修饰指针本身,表示这是一个指针常量,即指针本身的值不可改变,但是可以通过该指针去改变指向的对象的数值;
2. C操作文件需要FILE指针,fopen打开文件后记得操作结束后一定要fclose将缓存刷新进文件,或者直接使用fflush刷新缓存;
3. 文件操作最容易犯的错误是读取文件时用来存放读取数据的缓冲区没有用0表示终止,导致读出错误数量的字符。解决的办法一般有两种,一种是定义足够大的缓冲区后直接调用memset初始化为0;另外一种是动态分配strlen(目标)+1的缓冲区,直接赋值最后一个元素数值为0,从而起到终止符的作用;
4. 文本文件与二进制文件
     文本文件与二进制文件都是内存中的数据在外界存储介质上的不同表现形式而已。需要注意的是,文本读写方式与二进制读写方式是不同的,二进制读写是将内存中的数据原原本本地读写到文件中,二者是同一的;而文本读写遇到换行符'10'时,会自动转换成“回车+换行”导致比实际的多出一个字节,因此,必须确保写入方式与读取方式一致即可。需要注意的是我们的记事本程序时文本读取方式,只能正确解码纯文本文件。

二、 Win32 API的文件读写操作
     这部分自己在学习《核心编程》时曾经简单涉及过,所以这里也不会详细讲解用法了,因为具体的API使用可以参照MSDN,这里通过一个小例子来复习下如何使用Win32 API实现文件读写操作。由于自己使用的是VS2008,API已经广泛使用Unicode,因此一些函数要使用宽字符版本。首先我们新建一个MFC单文本项目File,在CMainFrame的View类的菜单上新建“文件选项”和“打开/保存”菜单,下面我们会为“文件选项”菜单添加读取和写入文件的操作。
     在“文件选项”菜单下建立两个子菜单“写入文件”和“读取文件”,分别添加事件响应程序如下:
写入文件:

点击(此处)折叠或打开

  1. void CFileView::OnWriteFile()
  2. {
  3.     //使用Win32 API创建写入文件
  4.     HANDLE hFile;
  5.     //打开文件
  6.     hFile = CreateFileA("1.txt", GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  7.     //接收实际写入的字节数
  8.     DWORD dwWrite;
  9.     //写入数据
  10.     //strlen与wcslen都是返回字符串的字节数,对于宽字符而言,需要*2
  11.     WriteFile(hFile, L"This is a File Read/Write test!", 2*wcslen(L"This is a File Read/Write test!"), &dwWrite, NULL);
  12.     //弹出提示
  13.     MessageBox(L"文件写入完成!");
  14.     //关闭文件句柄
  15.     CloseHandle(hFile);
  16.     // TODO: 在此添加命令处理程序代码
  17. }

读取文件:

点击(此处)折叠或打开

  1. void CFileView::OnReadFile()
  2. {
  3.     //使用win32 API读取文件
  4.     HANDLE hFile;
  5.     hFile = CreateFileA("1.txt", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  6.     wchar_t ch[100];
  7.     memset(ch, 0, 100);
  8.     DWORD dwRead;
  9.     ReadFile(hFile, ch, 100, &dwRead, NULL);
  10.     //wchar_t str[10] = {0};
  11.     //_itow(dwRead, str, 10);
  12.     //MessageBox(str);
  13.     CloseHandle(hFile);
  14.     MessageBox(ch);
  15.     
  16.     // TODO: 在此添加命令处理程序代码
  17. }
      代码中的注释比较相近,大家可以用来参考,主要的思路就是CreateFile-->WriteFile/ReadFile-->CloseHandle,特别需要注意的是这里涉及到宽字符以后,原本的strlen函数要改用wcslen,但是返回的依旧是字节数,由于一个宽字符占两个字节,所以最后需要的空间是2*wcslen(),这里如果错误会导致写入的内容不全。运行之后:
VC----MFC文件操作的那些事儿

VC----MFC文件操作的那些事儿
      可以顺利实现文件的读写操作,当然,仅限于宽字符的文本文件,读取传统的文本文件也会发生错误,因为二者的空间占用是不同的。

三、MFC的文件操作
     MFC主要提供CFile类来进行文件读写,该类提供了没有缓存的二进制格式的磁盘文件输入输出功能,通过其派生类能够间接地支持文本文件和内存文件。我们一般可以使用其构造函数形式:

点击(此处)折叠或打开

  1. CFile(LPCTSTR lpszFileName, UINT nOpenFlags);

      第一个参数用来指定文件的名称,第二个参数则指定文件操作的模式,常见的有:
CFile::modeCreate:创建一个新文件,若文件已存在,长度截断为0;
CFile::modeRead:打开文件,仅可进行读操作;
CFile::modeWrite:打开文件,仅可进行写操作;
     CFile提供了丰富的成员函数来实现文件操作,C语言中的函数这里都对应了CFile的成员函数,如Write/Read方法用来读写文件,Seek方法用来将指针移动到指定位置,SeekToBegin/SeekToEnd用来设置文件指针到文件头/末尾,Getlength获得文件长度(字符个数)。
     我们在使用word时可以打开一个新文档或保存/另存为一个新文档,这样好用的功能是如何时间的呢?其实利用MFC就可以实现。弹出的打开/保存小窗口实质是一个小对话框,MFC提供了CFileDialog来实现打开/保存对话框。CFileDialog的构造函数参数很多:

点击(此处)折叠或打开

  1. explicit CFileDialog(
  2.    BOOL bOpenFileDialog,
  3.    LPCTSTR lpszDefExt = NULL,
  4.    LPCTSTR lpszFileName = NULL,
  5.    DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
  6.    LPCTSTR lpszFilter = NULL,
  7.    CWnd* pParentWnd = NULL,
  8.    DWORD dwSize = 0,
  9.    BOOL bVistaStyle = TRUE
  10. )
    参数的意义依次为:
-1. Bool类型,TRUE时表示构建一个“打开”对话框,FALSE时表示构建一个“保存”对话框。
-2. 指定默认的文件扩展名
-3. 指定显示在文件对话框中的初始文件名
-4. 定制文件对话框的标志组合
-5. 一连串的字符串对,用于提供过滤器
-6. 指向父窗口的CWnd指针
     虽然参数很多,但是由于除了第一个参数外都有默认值,因此一般我们可以只提供第一个参数就可以了。如果想修改对话框的标题或者过滤器属性,可以设置CFileDialog的成员变量m_ofn的数据成员,m_ofn是一个OPENFILENAME的结构体类型。好,下面我们看一个产生“保存”对话框的程序:

点击(此处)折叠或打开

  1. void CFileView::OnSaveFile()
  2. {
  3.     // TODO: 在此添加命令处理程序代码

  4.     //实现弹出一个保存对话框
  5.     CFileDialog f_Dlg(FALSE);
  6.     f_Dlg.m_ofn.lpstrTitle = L"文件保存对话框";
  7.     f_Dlg.m_ofn.lpstrFilter = L"Text Files(*.txt)\0*.txt\0All Files()*.*\0*.*\0\0";
  8.     f_Dlg.m_ofn.lpstrDefExt = L"txt";
  9.     if (IDOK == f_Dlg.DoModal())
  10.     {
  11.         //使用str拼接出文件保存的完整路径+文件名
  12.         //使用CFileDialog提供的GetPahtName()和GetFileName()可以方便地得到文件路径和文件名
  13.         wchar_t str[100];
  14.         ::swprintf(str, f_Dlg.GetPathName(), f_Dlg.GetFileName());
  15.         CFile file(str, CFile::modeCreate | CFile::modeWrite);
  16.         file.Write("This is a MFC File Write action!", strlen("This is a MFC File Write action!"));
  17.         file.Close();
  18.     }
  19. }
VC----MFC文件操作的那些事儿

     文件打开对话框类似:

点击(此处)折叠或打开

  1. void CFileView::OnOpenFile()
  2. {
  3.     // TODO: 在此添加命令处理程序代码

  4.     CFileDialog f_Dlg(TRUE);
  5.     f_Dlg.m_ofn.lpstrTitle = L"文件打开对话框";
  6.     f_Dlg.m_ofn.lpstrFilter = L"Text Files(*.txt)\0*.txt\0All Files()*.*\0*.*\0\0";
  7.     if (IDOK == f_Dlg.DoModal())
  8.     {
  9.         wchar_t str[100];
  10.         wsprintf(str, f_Dlg.GetPathName(), f_Dlg.GetFileName());
  11.         CFile file(str, CFile::modeRead);
  12.         wchar_t *pBuf;
  13.         DWORD dwFileLen;
  14.         dwFileLen = file.GetLength();
  15.         pBuf = new wchar_t(dwFileLen + 1);
  16.         pBuf[dwFileLen] = 0;
  17.         file.Read(pBuf, dwFileLen);
  18.         file.Close();
  19.         MessageBox(pBuf);
  20.         delete pBuf;
  21.     }

  22. }
      这里需要注意的是,我们利用CFileDialog类提供的GetFilePath和GetFileName方法,通过wsprintf的拼接得到用户指定的目标文件的完整路径:
VC----MFC文件操作的那些事儿
     打开文件“1”之后:
VC----MFC文件操作的那些事儿
      事情到这里看上去似乎圆满了,但是自己的程序继续运行却提示出来了堆错误:
VC----MFC文件操作的那些事儿
     直觉告诉自己应该是打开文件时动态分配内存导致的错误,但是不晓得问题出在哪里,慢慢调试找找错误吧!