- Visual C++2013从入门到精通(视频教学版)
- 朱文伟
- 5164字
- 2025-02-23 19:54:09
2.13 文件操作
在C语言中,CRT库提供了一组操作文件的函数,比如打开文件fopen、读取文件fread、写入文件fwrite、关闭文件fclose等。在C++中,提供了不同功能的类来支持文件的输入输出,比如ofstream是一个用来写文件的类、ifstream是一个读文件的类、fstream是一个可同时读写操作的文件类,等等。
在Visual C++开发中,除了可以使用上述两种方式来操作文件外,还可以使用Win32 API函数来操作文件,以及MFC类(比如CFile、CStdioFile)来操作文件。
2.13.1 Win32 API操作文件
Visual C++提供了一组API函数来操作文件,比如打开文件、读写文件、复制文件、关闭文件等,并且用一个句柄来表示一个已经打开的文件对象,这个句柄通常称为文件句柄。我们看一下Win32 API中对文件操作的几个常用函数。
(1)CreateFile函数
该函数用来创建或打开一个文件。函数声明如下:
HANDLE CreateFile( LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile);
其中参数lpFileName表示要创建或打开的文件的名字;dwDesiredAccess表示对文件的访问权限,是只读还是只写,还是既要读又要写,如果取值为GENERIC_READ表示允许对文件进行读访问;如果为GENERIC_WRITE表示允许对文件进行写访问(可组合使用,比如GENERIC_READ| GENERIC_WRITE),如果为零,表示只允许获取与文件有关的信息;dwShareMode表示文件的共享属性,0表示不共享;FILE_SHARE_READ或FILE_SHARE_WRITE表示允许对文件进行读/写共享访问;lpSecurityAttributes指向一个SECURITY_ATTRIBUTES结构的指针,该结构定义了文件的安全特性;dwCreationDisposition表示文件如何创建,比如:
● CREATE_NEW:新建文件,但如果文件已经存在则会出错。
● CREATE_ALWAYS:创建文件,而且同名文件存在,会改写已经存在的文件。
● OPEN_EXISTING:文件必须已经存在,否则会出错。
● OPEN_ALWAYS:如文件不存在,则创建它。
● TRUNCATE_EXISTING:将现有文件缩短为零长度。
dwFlagsAndAttributes表示文件属性,它通常取值为下列一个或多个宏的组合,比如:
● FILE_ATTRIBUTE_ARCHIVE:标记归档属性。
● FILE_ATTRIBUTE_COMPRESSED:将文件标记为已压缩,或者标记为文件在目录中的默认压缩方式。
● FILE_ATTRIBUTE_NORMAL:默认属性。
● FILE_ATTRIBUTE_HIDDEN:隐藏文件或目录。
● FILE_ATTRIBUTE_READONLY:文件为只读。
● FILE_ATTRIBUTE_SYSTEM:文件为系统文件。
● FILE_FLAG_WRITE_THROUGH:操作系统不得推迟对文件的写操作。
● FILE_FLAG_OVERLAPPED:允许对文件进行重叠操作。
● FILE_FLAG_NO_BUFFERING:禁止对文件进行缓冲处理。文件只能写入磁盘卷的扇区块。
● FILE_FLAG_RANDOM_ACCESS:针对随机访问对文件缓冲进行优化。
● FILE_FLAG_SEQUENTIAL_SCAN:针对连续访问对文件缓冲进行优化。
● FILE_FLAG_DELETE_ON_CLOSE:关闭了上一次打开的句柄后,将文件删除,特别适合临时文件。
hTemplateFile表示一个模板文件的句柄,该文件具有只读访问权限。如果函数执行成功,则返回文件句柄;否则返回INVALID_HANDLE_VALUE,可以调用函数GetLastError来获得错误码。
(2)WriteFile函数
该函数用来向某文件的特定位置写入数据,这个特定位置由文件指示器所确定。这个函数既可以用于同步操作也可以用于异步操作。函数声明如下:
BOOL WINAPI WriteFile( HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped);
其中参数hFile是文件句柄;lpBuffer指向一个缓冲区,该缓冲区中的内容就是要写入到文件中的内容;nNumberOfBytesToWrite为要向文件写入的字节数;lpNumberOfBytesWritten指向一个变量,该变量返回实际写入的字节数;lpOverlapped指向一个OVERLAPPED结构,当文件句柄hFile的打开方式是FILE_FLAG_OVERLAPPED,则该结构被需要。如果函数成功,返回非零;否则返回零。
(3)ReadFile函数
该文件从某个文件中读取数据,读取的位置由当前文件指示器确定。该函数可以用来设计成同步操作或异步操作。函数声明如下:
BOOL WINAPI ReadFile( HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped);
其中参数hFile是文件句柄;lpBuffer指向一个缓冲区,该缓冲区用来存放从文件中读取的数据;nNumberOfBytesToRead要读取的数据字节数;lpNumberOfBytesRead实际读到的字节数;lpOverlapped指向OVERLAPPED结构,当文件句柄hFile的打开方式是FILE_FLAG_OVERLAPPED,则该结构被需要。如果函数成功,返回非零;否则返回零。
(4)SetFilePointer函数
该函数移动一个打开文件的指针,这个指针也称指示器(也称文件指针,这指针不是C语言中的指针),用来指示文件中的当前位置,以便在一个文件中设置一个新的读取或写入位置。函数声明如下:
DWORD SetFilePointer(HANDLE hFile, LONG lDistanceToMove, PLONG lpDistanceToMoveHigh, DWORD dwMoveMethod);
其中参数hFile为文件句柄;lDistanceToMove为偏移量(低位); lpDistanceToMoveHigh为偏移量(高位); dwMoveMethod决定文件指针的起始位置,取值如下:
● FILE_BEGIN:起始位置是文件开头。
● FILE_CURRENT:起始位置是当前位置。
● FILE_END:起始位置是文件末尾。
如果函数成功并且lpDistanceToMoveHigh为NULL,它返回文件指针的DWORD值的低字节序;如果函数成功并且lpDistanceToMoveHigh不为NULL,它返回文件指针的DWORD值的低字节序部分,并且lpDistanceToMoveHigh取值为文件指针的DWORD值的高字节序部分;如果函数失败,它返回INVALID_SET_FILE_POINTER,可以用函数GetLastError来获取错误码。
下列代码用来移动文件指针:
LARGE_INTEGER li={0}; li.QuadPart = 22333; //移动的位置 li.LowPart = SetFilePointer(handle, li.LowPart, &li.HighPart, FILE_BEGIN); //移动文件指针
LARGE_INTEGER是用来定义一个64位的有符号整数值。
typedef union _LARGE_INTEGER { struct { DWORD LowPart; LONG HighPart; }; struct { DWORD LowPart; LONG HighPart; } u; LONGLONG QuadPart; } LARGE_INTEGER, *PLARGE_INTEGER;
LARGE_INTEGER其实是个联合体,它通常可以表示数的范围为-3689348814741910324到+4611686018427387903。
【例2.52】 文件API函数简单应用
(1)新建一个对话框工程。
(2)切换到资源视图,在对话框上删除所有控件,然后添加一个按钮,并添加事件代码如下:
void CTestDlg::OnBnClickedButton1() { // TODO: 在此添加控件通知处理程序代码 HANDLE handle; DWORD Num; handle = ::CreateFile(_T("new.dat"), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE, NULL); if (INVALID_HANDLE_VALUE ! = handle) { ::SetFilePointer(handle,0,0, FILE_BEGIN); //设置文件指示器到开头 TCHAR Buffer[] = _T("这个字符串是文件里的内容"); ::WriteFile(handle, Buffer, sizeof(Buffer), &Num, NULL); ZeroMemory(Buffer, sizeof(Buffer)); ::SetFilePointer(handle,0,0, FILE_BEGIN); //设置文件指示器到开头 ::ReadFile(handle, Buffer, sizeof(Buffer), &Num, NULL); AfxMessageBox(Buffer); //显示读取的文件内容 ::CloseHandle(handle); } }
(3)保存工程并运行,运行结果如图2-106所示。

图2-106
2.13.2 MFC类操作文件
MFC类库中也提供了对文件进行处理的类,通常用到的是CFile或CStdioFile类。
CFile类是MFC文件类的基类,提供非缓冲方式的二进制磁盘输入、输出功能,但该类只能提供访问本地文件内容的功能,不支持访问网络文件的功能。CFile常见成员如下:
(1)m_hFile句柄
该成员是个句柄,表示一个打开文件的操作系统文件句柄。
(2)hFileNull句柄
该成员也是个句柄,而且是个静态变量,它用来判断CFile对象是否拥有一个有效的句柄。可以用下列代码判断一个文件句柄是否有效:
if (myFile.m_hFile ! = CFile::hFileNull) // myFile是CFile对象 ;//处理文件操作 else ;//显示文件句柄无效
(3)构造函数CFile
CFile的构造函数有多种形式:
CFile( ); CFile(HANDLE hFile ); CFile(LPCTSTR lpszFileName, UINT nOpenFlags );
其中参数hFile是由API函数CreateFile成功打开文件后返回的句柄;lpszFileName为需要打开文件的路径字符串,这个路径可以是相对路径,也可以是绝对路径;nOpenFlags表示文件的存取共享模式,比如可以取值如下:
● CFile::modeCreate:创建一个新文件,如果那个文件已经存在,把那个文件的长度重设为0。
● CFile::modeNoTruncate:通常同modeCreate一起用,如果要创建的文件已经存在,并不把它的长度设置为0。
● CFile::modeRead:打开文件仅仅供读。
● CFile::modeReadWrite:打开文件供读写。
● CFile::modeWrite:打开文件只供写。
● CFile::modeNoInherit:阻止这个文件被子进程继承。
● CFile::shareDenyNone:打开这个文件同时允许其他进程读写这个文件。
● CFile::shareDenyWrite:打开文件拒绝其他任何进程写这个文件。
比如,我们可以这样打开一个文件:
HANDLE hFile = CreateFile(_T("CFile_File.dat"), GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) AfxMessageBox(_T("Couldn't create the file! ")); else { // 把文件句柄依附到CFile对象 CFile myFile(hFile); /* 文件读写操作 */ myFile.Close(); //关闭文件句柄 }
(4)Open函数
该函数用来打开一个文件,函数声明如下:
BOOL Open( LPCTSTR lpszFileName, UINT nOpenFlags, CFileException* pError = NULL );
其中参数lpszFileName为需要打开文件的路径字符串,这个路径可以是相对路径也可以是绝对路径;nOpenFlags表示文件的存取共享模式,它指定文件打开时可以采取的操作。可以使用‘|’号来组合多个选项。比如可以取值如下:
● CFile::modeCreate:创建一个新文件,如果那个文件已经存在,把那个文件的长度重设为0。
● CFile::modeNoTruncate:通常同modeCreate一起用,如果要创建的文件已经存在,并不把它长度设置为0。
● CFile::modeRead:打开文件仅仅供读。
● CFile::modeReadWrite:打开文件供读写。
● CFile::modeWrite:打开文件只供写。
● CFile::modeNoInherit:阻止这个文件被子进程继承。
● CFile::shareDenyNone:打开这个文件同时允许其他进程读写这个文件。
● CFile::shareDenyWrite:打开文件拒绝其他任何进程写这个文件。
pError指向一个文件异常类CFileException的对象。如果打开成功,函数返回非零;否则返回零。
比如下列代码演示了用Open打开文件:
CFile f; CFileException e; TCHAR* pszFileName = _T("Open_File.dat"); if(! f.Open(pszFileName, CFile::modeCreate | CFile::modeWrite, &e)) TRACE(_T("File could not be opened %d\n"), e.m_cause);
(5)GetFileName函数
该函数得到文件的名字。函数声明如下:
CString GetFileName();
函数返回文件的名字。
比如,c:\windows\write\下有个文件myfile.txt,函数GetFileName返回的是“myfile.txt”。
(6)GetFilePath函数
该函数得到文件的全路径。函数声明如下:
CString GetFilePath();
函数返回文件的全路径。
比如,c:\windows\write\下有个文件myfile.txt,则GetFilePath返回的是“c:\windows\write\myfile.txt”。
(7)GetFileTitle函数
该函数得到文件的标题。函数声明如下:
CString GetFileTitle();
函数返回文件的标题。
比如,c:\windows\write\下有个文件myfile.txt,则GetFileTitle返回的是“myfile”。
(8)GetLength函数
该函数得到文件的长度。函数声明如下:
ULONGLONG GetLength();
函数返回文件的长度。
比如下列代码返回一个文件的长度:
CFile* pFile = NULL; pFile = new CFile(_T("C:\\WINDOWS\\SYSTEM.INI"), CFile::modeRead | CFile::shareDenyNone); ULONGLONG dwLength = pFile->GetLength(); CString str; str.Format(_T("Your SYSTEM.INI file is %I64u bytes long."), dwLength); pFile->Close(); delete pFile; AfxMessageBox(str);
(9)SeekToBegin函数
该函数重定位当前文件指针到文件的开头。函数声明如下:
void SeekToBegin( );
(10)SeekToEnd函数
该函数重定位当前文件指针到文件的结尾。函数声明如下:
void SeekToEnd ( );
比如,可以通过SeekToEnd函数获得文件大小:
CFile f; f.Open(_T("Seeker_File.dat"), CFile::modeCreate |CFile::modeReadWrite); f.SeekToBegin(); //移动文件指针到文件开头 ULONGLONG ullEnd = f.SeekToEnd(); //可以获得文件的大小
(11)Read函数
该函数从文件中读取nCount字节到缓冲区。函数声明如下:
UINT Read(void* lpBuf, UINT nCount );
其中参数lpBuf指向一个缓冲区,该缓冲区存放从文件中读取的数据;nCount为要读取的文件字节数。函数返回实际读取到的字节数,该值可能小于nCount。
(12)Write函数
该函数向文件写入数据。函数声明如下:
void Write( const void* lpBuf, UINT nCount );
其中参数lpBuf指向一个缓冲区,该缓冲区存放要写入到文件中的数据;nCount为要写入的数据的字节数。
(13)Flush函数
该函数强制系统缓存的内容马上写入文件。写数据时先写入内存(系统缓存),然后再写入文件。该函数只是为了确保数据尽快被写入文件,如果不调用flush函数,系统缓存达到一定的数据量也会自动写入磁盘文件,或者在关闭文件的时候也会把缓冲区的数据(如果有)强制写入磁盘文件。如果不是多线程写同一个文件,可以不用flush函数,最后结束前记得close就可以了。
比如下列代码演示了文件读取和写入的过程。
CFile cfile; cfile.Open(_T("Write_File.dat"), CFile::modeCreate | CFile::modeReadWrite); char pbufWrite[100]; memset(pbufWrite, 'a', sizeof(pbufWrite)); cfile.Write(pbufWrite, 100); cfile.Flush(); cfile.SeekToBegin(); char pbufRead[100]; cfile.Read(pbufRead, sizeof(pbufRead)); ASSERT(0 == memcmp(pbufWrite, pbufRead, sizeof(pbufWrite)));
下面来看个CFile使用的例子,对一个临时文件进行读写。临时文件是程序运行时建立的临时使用的文件,它通常位于C:\Windows\Temp目录下,扩展名为tmp。临时文件的使用方法基本与常规文件一样,只是文件名应该调用API函数GetTempFileName()获得,该函数声明如下:
UINT GetTempFileName( LPCTSTR lpPathName, LPCTSTR lpPrefixString, UINT uUnique, LPTSTR lpTempFileName);
其中参数lpPathName是临时文件的目录路径,它通常由API函数GetTempPath获得;lpPrefixString是临时文件的文件名前缀;uUnique指定生成文件名的十六进制数字,该参数和参数lpPrefixString一起形成临时文件名,如果该参数为零,则函数会使用系统时间来和前缀形成文件名;lpTempFileName指向一个缓冲区,缓冲区里存放获得的临时文件名。如果函数成功并且uUnique非零,则返回uUnique的值;如果uUnique为零,则返回文件名长度;如果函数失败,则返回零。
【例2.53】 读写一个临时文件
(1)新建一个控制台工程,并在应用程序向导中勾选“MFC”,这样可以在控制台程序中使用MFC库中的类或函数,如图2-107所示。

图2-107
(2)打开Test.cpp,在其中输入代码如下:
#include "stdafx.h" #include "Test.h" #ifdef _DEBUG #define new DEBUG_NEW #endif // 唯一的应用程序对象 CWinApp theApp; using namespace std; int _tmain(int argc, TCHAR* argv[], TCHAR* envp[]) { int nRetCode = 0; HMODULE hModule = ::GetModuleHandle(NULL); if (hModule ! = NULL) { // 初始化MFC并在失败时显示错误 if (! AfxWinInit(hModule, NULL, ::GetCommandLine(), 0)) { // TODO: 更改错误代码以符合您的需要 _tprintf(_T("错误: MFC初始化失败\n")); nRetCode = 1; } else { // TODO: 在此处为应用程序的行为编写代码。 TCHAR szTempPath[_MAX_PATH], szTempfile[_MAX_PATH]; GetTempPath(_MAX_PATH, szTempPath); GetTempFileName(szTempPath, _T("my_"), 16, szTempfile); CFile tmpfile(szTempfile, CFile::modeCreate | CFile::modeReadWrite); char sz[] = "abc\r\n我们大家"; tmpfile.Write(sz, strlen(sz)); tmpfile.Close(); } } else { // TODO: 更改错误代码以符合您的需要 _tprintf(_T("错误: GetModuleHandle失败\n")); nRetCode = 1; } return nRetCode; }
上面代码会在“x:\Users\Administrator\AppData\Local\Temp”下新建一个my_10.tmp文件,然后在其中写入数据。x是系统盘符。
(3)保存工程并运行,运行结果如图2-108所示。

图2-108
除了类CFile, MFC中还使用CStdioFile类封装了C++运行时刻文件流的操作,流文件采用缓冲方式,支持文件模式和二进制模式文件操作,默认方式为文本模式。CStdioFile类从CFile类继承,具有如下三个构造函数:
CStdioFile(); CStdioFile(FILE* pOpenStream); CStdioFile(LPCTSTR lpszFileName, UINT nOpenFlags);
其中参数pOpenStream是FILE指针;lpszFileName为要打开的文件的路径,可以是绝对路径或相对路径;nOpenFlags是打开文件的方式,它可以取值如下:
● CFile::modeCreate:创建新文件,并覆盖已有文件。
● CFile::modeRead:以只读方式打开文件。
● CFile::modeReadWrite:以读/写方式打开文件。
● CFile::modeWrite:以只写方式打开文件。
● CFile::shareExclusive:不允许其他进程读/写文件。
● CFile::typeText:表示以文本方式打开文件。
● CFile::typeBinary:表示以二进制方式打开文件。
比如我们可以这样新建打开一个文件:
TCHAR* pFileName = _T("CStdio_File.dat"); CStdioFile f1; if(! f1.Open(pFileName, CFile::modeCreate | CFile::modeWrite | CFile::typeText)) TRACE(_T("Unable to open file\n")); CStdioFile f2(stdout); try { CStdioFile f3( pFileName, CFile::modeCreate | CFile::modeWrite | CFile::typeText ); } catch(CFileException* pe) { TRACE(_T("File could not be opened, cause = %d\n"), pe->m_cause);pe->Delete(); }
类CStdioFile的重要成员函数如下:
(1)ReadString函数
该函数读取一行文本到缓冲区,遇到“0x0D,0x0A”时停止读取,并且去掉硬回车“0x0D”,保留换行符“0x0A”,在字符串末尾添加字符‘\0'。nMax个字符里包含字符‘\0'。函数声明如下:
LPTSTR ReadString(LPTSTR lpsz, UINT nMax ); BOOL ReadString( CString& rString);
其中参数lpsz指向一个用户缓冲区,用来存放从文件中读到的数据;nMax为要读取的数据的最大字节数;rString是一个CString对象的引用,用来存放读取到的字符串数据。如果第一个函数成功,返回缓冲区指针,否则返回NULL;第二个函数如果文件未读完返回TRUE,否则返回FALSE。
比如下列代码可以在控制台上输入数据:
CStdioFile f(stdin); TCHAR buf[100]=_T(""); f.ReadString(buf, 99);
当文件存在多行数据需要逐行读取时,可用函数ReadString(CString& rString),当遇到‘\n’时读取截断,如果文件未读完,返回TRUE,否则返回FALSE,代码如下:
//逐行读取文件内容,存入strRead while(file.ReadString(strRead)) { ...; }
(2)WriteString函数
该函数向文件写入数据。函数声明如下:
void WriteString(LPCTSTR lpsz );
其中参数lpsz指向一段NULL结尾的文本字符串。
【例2.54】 用WriteString向控制台窗口输出文本
(1)新建一个控制台工程,并且在应用程序向导中要勾选“MFC”。
(2)在Test.cpp中输入代码如下:
#include "stdafx.h" #include "Test.h" #ifdef _DEBUG #define new DEBUG_NEW #endif // 唯一的应用程序对象 CWinApp theApp; using namespace std; int _tmain(int argc, TCHAR* argv[], TCHAR* envp[]) { int nRetCode = 0; HMODULE hModule = ::GetModuleHandle(NULL); if (hModule ! = NULL) { // 初始化MFC并在失败时显示错误 if (! AfxWinInit(hModule, NULL, ::GetCommandLine(), 0)) { // TODO: 更改错误代码以符合您的需要 _tprintf(_T("错误: MFC初始化失败\n")); nRetCode = 1; } else { // TODO: 在此处为应用程序的行为编写代码。 setlocale(LC_CTYPE, "chs"); CStdioFile f(stdout); TCHAR buf[] = _T("阿凡达test string\n"); f.WriteString(buf); } } else { // TODO: 更改错误代码以符合您的需要 _tprintf(_T("错误: GetModuleHandle失败\n")); nRetCode = 1; } return nRetCode; }
(3)保存工程并运行,运行结果如图2-109所示。

图2-109