注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

jasonyang9的博客

随便写写

 
 
 

日志

 
 

(怀旧系列)VC程序设计(孙鑫老师)听课笔记:12 文件操作  

2013-02-15 13:50:12|  分类: programming |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
(怀旧系列)VC程序设计(孙鑫老师)听课笔记:12 文件操作 - jasonyang9 - jasonyang9的博客
 

========
文件操作
========
很多和文件相关的操作函数,其参数的类型都是指向常量的指针类型。

指向常量的指针
--------------

char ch[5] = "lisi"; // 定义一个字符数组,有5个元素,用常量字符串为其赋值(只能在该字符数组声明时赋值,多一个元素是因为常量字符串会自动加上\0表示结束)
const char* pStr = ch; // 用字符数组的首地址给pStr这个指向常量的指针赋值
// 同char const* pStr = ch;,*号在const和char的后面


假设字符数组ch的首地址为0088:4400,那么pStr中存放的就0088:4400。指针的值可以修改(可以指向XXXX:YYYY等等),但指向的内容不可以改变。

*pStr = 'w'; // 错误!不能修改指向的内容
pStr = "wangwu"; // 正确,可以修改指针的值(将常量字符串的首地址赋值给指针)


注意:虽然不能通过指向常量的指针来修改其指向的内容,但内容或许可以通过其他方法进行修改。

可以利用指向常量的指针来声明函数的形参,那么实际调用时,函数就不能通过实参来修改指针所指向的内容,保证了数据的一致性。

指针常量
--------

char ch[5] = "lisi"; // 同样定义一个字符数组
char* const pStr = ch; // 用字符数组的首地址给pStr这个指针常量赋值(指针常量必须在定义的同时进行初始化赋值,这和所有常量类型的变量是一样的)
// *号在const的前面


假设字符数组ch的首地址为0088:4400,那么pStr中存放的就是0088:4400。指针的值不可修改(只能指向0088:4400),但其指向的内容可以修改(指针常量表示指针本身是常量)。

pStr = "zhangsan"; // 错误!指针本身是常量,不能修改指针的值
*pStr = 'W'; // 正确,可以修改指针常量所指向的内容


新建一个单文档程序,增加一个“文件”菜单,菜单项“写入文件”(ID号:IDM_FILE_WRITE),“读取文件”(ID号:IDM_FILE_READ),在View类中增加它们的消息响应函数。

以下是C语言函数的实现。

void CXXXView::OnFileWrite()
{
FILE *pFile = fopen("1.txt", "w"); // fopen的2个参数都是指向常量的指针,第1个参数是文件名,第2个参数是打开的类型("w"表示写)
fwrite("http://www.sunxin.org", 1, strlen("http://www.sunxin.org"), pFile); // fwrite的参数较多,第1个参数表示要写入的数据,第2个参数是项目的字节数,第3个参数是要写入多少个项目,最后一个参数是文件的指针
// fclose(pFile); // C语言的文件系统带有缓冲区,需要关闭文件,将缓冲区中的数据写入磁盘文件(否则要等到程序退出,或缓冲区填满后才会进行写盘操作)。
// 但是,关闭文件后要再次写入需要重新打开它,很不方便,可以用fflush函数改进
fflush(pFile); // 强制将缓冲区中的数据写入磁盘文件
}


如果再次调用fwrite对文件写入数据,则写的内容会紧跟在之前的内容后面。具体写入的位置由一个文件指针记录,文件指针可由fseek函数设定。

void CXXXView::OnFileWrite()
{
FILE *pFile = fopen("1.txt", "w");
fwrite("http://www.sunxin.org", 1, strlen("http://www.sunxin.org"), pFile);
fseek(pFile, 0, SEEK_SET); // 搜索到文件的开始处
fwrite("ftp:", 1, strlen("ftp:"), pFile); // 在文件的开始处写入,覆盖原有内容
}

文件读取的操作。

void CXXXView::OnFileRead()
{
FILE *pFile = fopen("1.txt", "r"); // 用“r”表示以读取方式打开
char ch[100]; // 定义字符数组
fread(ch, 1, 100, pFile); // 读取100个字节到ch
MessageBox(ch); // 用消息框显示
fclose(pFile); // 关闭文件
}


显示的字符串后面跟着一串乱码,原因是ch这个字符数组没有用\0结束。解决的方法之一是在写入时多写一个字节,默认会多写一个0,在读取时就可以将其作为字符串的结束符号。
另一种方法是用memset函数,将字符数组中所有数据设置为0,当显示时,遇到第一个0就会停止输出了。

void CXXXView::OnFileRead()
{
FILE *pFile = fopen("1.txt", "r"); // 用“r”表示以读取方式打开
char ch[100]; // 定义字符数组
memset(ch, 0, 100); // 将ch中的所有数据设置为0
fread(ch, 1, 100, pFile); // 读取100个字节到ch
MessageBox(ch); // 用消息框显示
fclose(pFile); // 关闭文件
}


获取文件长度,据此分配字符数组大小。

void CXXXView::OnFileRead()
{
FILE *pFile = fopen("1.txt", "r"); // 用“r”表示以读取方式打开
char *pBuf;
fseek(pFile, 0, SEEK_END); // 移动文件指针到结尾
int len = ftell(pFile); // 得到文件长度
pBuf = new char[len + 1]; // 根据文件长度分配字符数组大小(多分配一个字节存放\0)
// fseek(pFile, 0, SEEK_SET); // 移动文件指针到头部(否则读出的都是乱码)
rewind(pFile); // 用rewind亦可
fread(pBuf, 1, len, pFile); // 读取指定长度到pBuf指向的字符数组
pBuf[len] = 0; // pBuf最后一个字节设为\0
MessageBox(pBuf); // 用消息框显示
fclose(pFile); // 关闭文件
}


文件写入经常遇到的问题。

void CXXXView::OnFileWrite()
{
FILE *pFile = fopen("2.txt", "w"); // 用“w”表示以写入方式打开
char ch[3]; // 定义字符数组
ch[0] = 'a'; // 第一个字符为“a”
ch[1] = 10; // 第二个字符为“换行符”(以数字表示的ASCII码)
ch[2] = 'b'; // 第三个字符为“b”
fwrite(ch, 1, 3, pFile); // 将ch的3个字符写入文件
fclose(pFile); // 关闭文件
}


但生成的文件却有4个字节,用具有十六进制模式的编辑器可以发现其内容是“61 0D 0A 62”,“61”是“a”,“0D”是回车,“0A”是换行符,“62”是b。程序自动为我们加上了一个回车,使文件多了一个字节。
现在尝试读取这个文件的内容。

void CXXXView::OnFileRead()
{
FILE *pFile = fopen("2.txt", "r"); // 用“r”表示以读取方式打开
char ch[100]; // 定义字符数组
fread(ch, 1, 3, pFile); // 读取3个字节
ch[3] = 0; // 将第4个字节设置为0
MessageBox(ch); // 用对话框显示
fclose(pFile);
}


显示的结果是:

a
b


可以看到读取的结果是正确的,“a”和“b”都显示出来,但为何只读取了3个字节就能够将4个字节的内容显示出来呢?
fopen默认采用文本方式打开文件,这种模式下,读取时,回车符和换行符被转换为单个换行符,写入时,单个换行符被转换为回车符和换行符。
现在用二进制模式打开文件。

void CXXXView::OnFileRead()
{
FILE *pFile = fopen("2.txt", "rb"); // 用“r”表示以读取方式打开,“b”表示用二进制方式
char ch[100]; // 定义字符数组
fread(ch, 1, 3, pFile); // 读取3个字节
ch[3] = 0; // 将第4个字节设置为0
MessageBox(ch); // 用对话框显示
fclose(pFile);
}


输出只有一个“a”,“b”没有读取到。

关于二进制文件和文本文件
* 文件是在计算机内存中以二进制表示的数据在外部存储介质上的另一种存放形式。
* 文件通常分为二进制文件和文本文件。
* 二进制文件是包含在ASCII及扩展ASCII字符中编写的数据或程序指令的文件。一般是可执行程序、图形、图像、声音等文件。
* 文本文件(也称为ASCII文件)是每一个字节存放的是可表示为一个字符的ASCII代码的文件。它是以“行”为基本结构的一种信息组织和存储方式的文件,可用任何文字处理程序阅读的简单文本文件。

关于文本方式和二进制方式
* 以文本方式往文件中写入数据时,一旦遇到换行符(ASCII为10),则会转换为回车+换行(ASCII为13、10)。在读取文件时,一旦遇到回车+换行的组合(即连续的ASCII 13、10),则会转换为换行符(ASCII为10)。
* 以二进制方式往文件中写入数据时,则将数据在内存中的存储形式原样输出到文件中。

在读取和写入文件的时候应保持一致,由文本方式写入的文件应用文本方式读取,用二进制方式写入的文件应用二进制方式读取。
文本文件可以用二进制方式打开,进行读取和写入,不会出错。但对于二进制文件,如果用文本方式打开进行读取,则可能出错(因为读取时有可能将回车+换行的组合转换为一个换行符,在写入时将一个换行转换为回车+换行,这样就将原来的数据破坏了)。

例题:
* 将所给定的整数(如:98341)保存到文件中,要求在记事本中打开此文件时显示的是98341。 

void CXXXView::OnFileWrite()
{
FILE *pFile = fopen("3.txt", "w"); // 以写入方式打开文件
int i = 98341; // 定义一个整数为98341
fwrite(&i, 4, 1, pFile); // 写入该整数,写4个字节(一个整数占用4个字节)
fclose(pFile); // 关闭文件
}


如上,直接将98341这个整数写入文件,则是以数值保存的,用记事本打开此文件会显示为乱码。
用十六进制查看该文件内容,可以发现将十六进制值(15 80 01 00)由于是低位优先,即0x018025转换为十进制就是98341。
显示为乱码的原因是,记事本会将(15 80 01 00)转换为ASCII码用以显示,但这些数据转换为ASCII码是一些不可读的字符,也就变成了乱码。

void CXXXView::OnFileWrite()
{
FILE *pFile = fopen("3.txt", "w"); // 以写入方式打开文件
int i = 98341; // 定义一个整数为98341
char ch[5]; // 定义一个字符数组
ch[0] = 9 + 48; // 由于ASCII中“0”的编码是48,那么就将数字加上48即得到对应的ASCII码
ch[1] = 8 + 48;
ch[2] = 3 + 48;
ch[3] = 4 + 48;
ch[4] = 1 + 48;
fwrite(ch, 1, 5, pFile); // 写入该字符数组
fclose(pFile); // 关闭文件
}


用以上的方法就可以将98341以ASCII码的形式写入文件。还有更简单的方法。

void CXXXView::OnFileWrite()
{
FILE *pFile = fopen("3.txt", "w"); // 以写入方式打开文件
int i = 98341; // 定义一个整数为98341
char ch[5]; // 定义一个字符数组
itoa(i, ch, 10); // 利用itoa函数按10进制方式转换整数为ASCII字符
fwrite(ch, 1, 5, pFile); // 写入该字符数组
fclose(pFile); // 关闭文件
}

以下是C++类的实现。

可用ofstream(写)和ifstream(读)类。

#include <fstream.h> // 需要包含fstream.h

void CXXXView::OnFileWrite()
{
ofstream ofs("4.txt"); // 定义ofstream对象,调用其构造函数打开文件
ofs.write("http://www.sunxin.org", strlen("http://www.sunxin.org")); // 写入内容
ofs.close(); // 关闭文件
}

void CXXXView::OnFileRead()
{
ifstream ifs("4.txt"); // 定义ifstream对象,同时打开文件
char ch[100]; // 定义字符数组
memset(ch, 0, 100); // 设置全部元素为0
ifs.read(ch, 100); // 读取100个字节
ifs.close(); // 关闭文件
MessageBox(ch); // 用消息框显示内容
}


以下是Win32 API函数的实现。

void CXXXView::OnFileWrite()
{
HANDLE hFile; // 定义文件句柄
hFile = CreateFile("5.txt", // 创建5.txt
GENERIC_WRITE, // 写模式
0, // 共享方式为0(不可共享)
NULL, // 安全属性为NULL(不支持安全属性)
CREATE_NEW, // 新建
FILE_ATTRIBUTE_NORMAL, // 常规文件属性
NULL); // 无模版文件
DWORD dwWrites; // 用于存放实际写入的字节数
WriteFile(hFile, // 对hFile句柄所指文件写入
"http://www.sunxin.org", // 写入的内容
strlen("http://www.sunxin.org"), // 写入的字节数
&dwWrites, // 返回实际写入的字节数
NULL); // 非异步模式(如果在CreateFile时给第6个参数加上了FILE_FLAG_OVERLAPPED,则表示使用一个异步读写的文件,IO操作会立即返回,操作系统调用线程在后台IO操作文件,完成后通知程序)
CloseHandle(hFile); // 关闭句柄
}

void CXXXView::OnFileRead()
{
HANDLE hFile; // 定义文件句柄
hFile = CreateFile("5.txt", // 文件名
GENERIC_READ, // 读模式
0, // 共享方式为0(不可共享)
NULL, // 安全属性为NULL(不支持安全属性)
OPEN_EXISTING, // 打开现有的文件
FILE_ATTRIBUTE_NORMAL, // 常规文件属性
NULL); // 无模版文件
char ch[100]; // 定义字符数组
DWORD dwReads; // 用于存放实际读取的字节数
ReadFile(hFile, // 对hFile句柄所指文件读取
ch, // 读取到ch字符数组
100, // 读取100字节
&dwReads, // 返回实际读取字节数
NULL); // 非异步模式
ch[dwReads] = 0; // 将字符串末尾设成0
CloseHandle(hFile); // 关闭句柄
MessageBox(ch); // 显示
}


CreateFile函数不仅能对文件进行操作,还能对多种设备(如:邮槽、管道)操作。

以下是MFC文件操作类的实现。

void CXXXView::OnFileWrite()
{
CFile file("6.txt", CFile::modeCreate | CFile::modeWrite); // 构造CFile对象,同时指定文件名为6.txt,模式为新建和写入
file.Write("http://www.sunxin.org", strlen("http://www.sunxin.org")); // 写入内容
file.Close(); // 关闭文件
}

void CXXXView::OnFileRead()
{
CFile file("6.txt", CFile::modeRead); // 以读取方式打开6.txt
char *pBuf; // 定义字符指针
DWORD dwFileLen; // 定义存放文件长度的变量
dwFileLen = file.GetLength(); // 获取文件长度
pBuf = new char[dwFileLen + 1]; // 根据文件长度分配字符内存
pBuf[dwFileLen] = 0; // 用0封闭字符串
file.Read(pBuf, dwFileLen); // 读取内容
file.Close(); // 关闭文件
MessageBox(pBuf); // 显示
}


增加打开和保存文件对话框。

void CXXXView::OnFileWrite()
{
CFileDialog fileDlg(FALSE); // 第一个参数为FALSE表示使用“另存为”对话框
fileDlg.m_ofn.lpstrTitle = "我的文件保存对话框"; // 修改对话框的标题
fileDlg.m_ofn.lpstrFilter = "Text files(*.txt)\0*.txt\0All files(*.*)\0*.*\0\0"; // 文件类型过滤
fileDlg.m_ofn.lpstrDefExt = "txt"; // 缺省扩展名

if (IDOK == fileDlg.DoModal())
{
CFile file(fileDlg.GetFileName(), CFile::modeCreate | CFile::modeWrite); // 创建文件,用fileDlg的GetFileName获取用户选择(或输入)的文件名
file.Write("http://www.sunxin.org", strlen("http://www.sunxin.org")); // 写入内容
file.Close(); // 关闭文件
}
}

void CXXXView::OnFileRead()
{
CFileDialog fileDlg(TRUE); // 第一个参数为TRUE表示使用“打开”对话框
fileDlg.m_ofn.lpstrTitle = "我的文件打开对话框"; // 修改对话框的标题
fileDlg.m_ofn.lpstrFilter = "Text files(*.txt)\0*.txt\0All files(*.*)\0*.*\0\0"; // 文件类型过滤

if (IDOK == fileDlg.DoModal())
{
CFile file(fileDlg.GetFileName(), CFile::modeRead); // 打开文件,用fileDlg的GetFileName获取用户选择(或输入)的文件名
char *pBuf; // 定义字符指针
DWORD dwFileLen; // 定义存放文件长度的变量
dwFileLen = file.GetLength(); // 获取文件长度
pBuf = new char[dwFileLen + 1]; // 根据文件长度分配字符内存
pBuf[dwFileLen] = 0; // 用0封闭字符串
file.Read(pBuf, dwFileLen); // 读取内容
file.Close(); // 关闭文件
MessageBox(pBuf); // 显示
}
}


对Win.ini读写信息(这是16位Windows程序的方式)

一般来说,在CXXXApp的InitInstance函数中处理。

BOOL CXXXApp::InitInstance()
{
...
SetRegistryKey(_T("Local AppWizard-Generated Applications"));

::WriteProfileString("http://www.sunxin.org", "admin", "zhangsan"); // 调用WriteProfileString向Win.ini写内容,第一个参数是section name,第二个参数是key name,第三个参数是string
CString str;
::GetProfileString("http://www.sunxin.org", "admin", "lisi", // 读取Win.ini的内容
str.GetBuffer(100), // 由于这里需要一个指向字符串的指针变量作为参数,用CString的GetBuffer函数获取(函数返回一个指向CString对象内部字符缓存区的指针(LPTSTR),所返回的指针不是一个常量,是可以被直接修改的。如果使用这个返回的指针修改了其内容,那么在调用其他CString函数前必须ReleaseBuffer)
100); // 读取100个字节
AfxMessageBox(str); // 显示(注意,CXXXApp不是从CWnd派生,没有MessageBox函数,只能用框架函数AfxMessageBox)

LoadStdProfileSettings();
...
}


CXXXApp从CWinApp派生,CWinApp有自己的WriteProfileString和GetProfileString函数,它们和SDK中的同名函数区别在于可以将配置信息写入注册表或者INI文件(NT系统写入注册表,3.x系统写入WIN.INI,95系统写入WIN.INI缓冲)。

BOOL CXXXApp::InitInstance()
{
...
SetRegistryKey(_T("Local AppWizard-Generated Applications"));

WriteProfileString("http://www.sunxin.org", "admin", "zhangsan"); // 不加::表示调用CWinApp的WriteProfileString向注册表写内容
CString str;
str = GetProfileString("http://www.sunxin.org", "admin"); // 这个GetProfileString比SDK的参数少
AfxMessageBox(str);

LoadStdProfileSettings();
...
}

写入的内容保存在注册表的HKEY_CURRENT_USER\Software\Local AppWizard-Generated Applications\File中。"Local AppWizard-Generated Applications"这个项是SetRegistryKey(_T("Local AppWizard-Generated Applications"));指定的,可以自定义。

对注册表的编程
==============
对程序增加2个菜单项(IDM_REG_WRITE,写注册表;IDM_REG_READ,读注册表),增加命令响应函数。

void CXXXView::OnRegWrite()
{
HKEY hKey; // 定义句柄
RegCreateKey(HKEY_LOCAL_MACHINE, // 在HKEY_LOCAL_MACHINE分支下创建表项
"Software\\http://www.sunxin.org\\admin" // 要创建的表项
&hKey); // 返回所创建表项的句柄
RegSetValue(hKey, // 设置注册表项的值,第一个参数是创建注册表项时返回的句柄
NULL, // 子项为空表示对admin这个父项设值
REG_SZ, // 类型只能为REG_SZ(字符串)
"zhangsan", // 项目的内容
strlen("zhangsan")); // 写入的长度
RegCloseKey(hKey); // 关闭句柄
}

void CXXXView::OnRegRead()
{
LONG lValue;
RegQueryValue(HKEY_LOCAL_MACHINE, // 得到注册表中存放的数据的长度(包括终止的0),第一个参数是句柄(直接给HKEY_LOCAL_MACHINE)
"Software\\http://www.sunxin.org\\admin", // 表项
NULL, // 第三个参数为NULL时,第四个参数会返回此表项中数据的长度
&lValue); // 用来返回长度

char *pBuf = new char[lValue]; // 根据返回的长度分配字符数组内存

RegQueryValue(HKEY_LOCAL_MACHINE, // 第二次调用RegQueryValue获取注册表项目的值
"Software\\http://www.sunxin.org\\admin", // 表项
pBuf, // 第三个参数是返回的值
&lValue); // 返回长度

MessageBox(pBuf); // 显示
}

用RegSetValueEx可以写入多种类型的值(不仅是REG_SZ字符串)。

void CXXXView::OnRegWrite()
{
HKEY hKey; // 定义句柄
DWORD dwAge = 30;
RegCreateKey(HKEY_LOCAL_MACHINE, // 在HKEY_LOCAL_MACHINE分支下创建表项
"Software\\http://www.sunxin.org\\admin" // 要创建的表项
&hKey); // 返回所创建表项的句柄
RegSetValue(hKey, // 设置注册表项的值,第一个参数是创建注册表项时返回的句柄
NULL, // 子项为空表示对admin这个父项设值
REG_SZ, // 类型只能为REG_SZ(字符串)
"zhangsan", // 项目的内容
strlen("zhangsan")); // 写入的长度
RegSetValueEx(hKey, // 写DWORD类型的值
"age", // 键值的名称
0, // 保留(设为0即可)
DWORD, // 类型为DWORD
(CONST BYTE*)&dwAge, // 值本身(注意需要强制类型转换,并加上取地址符)
4); // 值的长度(整型占4个字节)
RegCloseKey(hKey); // 关闭句柄
}

用RegQueryValueEx可以读取多种类型的值(不仅是REG_SZ字符串)。

void CXXXView::OnRegRead()
{
LONG lValue;
RegQueryValue(HKEY_LOCAL_MACHINE, // 得到注册表中存放的数据的长度(包括终止的0),第一个参数是句柄(直接给HKEY_LOCAL_MACHINE)
"Software\\http://www.sunxin.org\\admin", // 表项
NULL, // 第三个参数为NULL时,第四个参数会返回此表项中数据的长度
&lValue); // 用来返回长度

char *pBuf = new char[lValue]; // 根据返回的长度分配字符数组内存

RegQueryValue(HKEY_LOCAL_MACHINE, // 第二次调用RegQueryValue获取注册表项目的值
"Software\\http://www.sunxin.org\\admin", // 表项
pBuf, // 第三个参数是返回的值
&lValue); // 返回长度

MessageBox(pBuf); // 显示

HKEY hKey; // 定义句柄
RegOpenKey(HKEY_LOCAL_MACHINE, // 打开注册表键
"Software\\http://www.sunxin.org\\admin", // 表项
&hKey); // 返回句柄
DWORD dwType;
DWORD dwValue;
DWORD dwAge;
RegQueryValueEx(hKey, // 读取注册表,第一个参数是句柄
"age", // 键值名称
0, // 保留(设为0即可)
&dwType, // 接收数据的类型(取地址)
((LPBYTE)*dwAge, // 接收数据的缓存(取地址,强制类型转换)
&dwValue); // 接收到数据的大小
CString str;
str.format("age=%d", dwAge);
MessageBox(str);
}


还有RegDeleteKey等可以对注册表进行各种操作,需要自己查MSDN了。

  评论这张
 
阅读(245)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017