MFC对TXT文档的操作—读、写、删、查

来源:互联网 发布:塔士多礼服 知乎 编辑:程序博客网 时间:2024/06/03 16:21

1. 前言

  这部分内容设置知识点相对比较杂乱,刚入门的读者可能看不太懂,作者水平也有限,但是会尽力讲清楚,有些地方只需做到“不求甚解”,大概了解如何使用即可,不必太过于钻研,欢迎私下交流讨论,QQ:739616037
  先看一下我们文档中已存在的几组数据
这里写图片描述

2. 初始化全局变量

这里写图片描述

这里写图片描述

  这里解释一下DWORD这个数据类型,DWORD 代表 unsigned long,它是MFC的数据类型。一般用于返回值不会有负数的情况,读者可以就把它当作一个特殊的整型就行了,不用过于纠结

long  32位有符号整数int   32位有符号整数DWORD 32位无符号整数

3. “读”文档

void CFileDlg::Onnext() {    CString strline;//定义一个变量用于接收读取的一行内容    CStdioFile file;//定义一个CStdioFile类的对象 file    BOOL flag = file.Open("D:\\test.txt",CFile::modeRead);//open函数需要传两个参数,前一个是文件路径,后一个是文件的打开模式    if(flag == FALSE)    {        MessageBox("文件打开失败!");    }    file.Seek(m_dwPos,CFile::begin);    if(file.ReadString(strline) != FALSE)    {        m_dwPos = file.GetPosition();//获取当前文件指针        m_id = strtok(strline.GetBuffer(0)," ");        m_name = strtok(NULL," ");        m_score = strtok(NULL," ");        UpdateData(FALSE);    }    else    {        MessageBox("已是最后一条记录");    }    file.Close();}

Open函数

  CStdioFile类CSFile类的派生类,这里我们不用过多了解,有兴趣的读者可以下去自己钻研。我们首先定义一个CStdioFile类的对象, 在代码的第五行,我们使用了类中的构造函数open,下面我们先给出这个函数的原型

virtual BOOL Open( LPCTSTR lpszFileName, UINT nOpenFlags, CFileException* pError = NULL );/*lpszFileName 表示路径所需文件的字符串。 该路径可以是相对路径,也可以是绝对路径*//*nOpenFlags 共享和访问模式。 因此,在打开文件时,指定该操作。 您可以合并选项使用按位或(|)运算符*//*pError 要接收失败的操作状态的现有文件异常对象的指针*/

  后面一个参数在此例当中不重要,不用过多了解,最重要的是前两个参数:

  第一个参数是指打开文件的路径,这里我txt文本的路径是D盘根目录下,有一点要注意的是,我们平时见到的路径是”D:\…”或”E:\…”,但是在c/c++语言中,”\\”才表示一个“\”,这个细节要注意

  第二个参数是指文件的打开模式,我这里写的是”modeRead”,表示只读,但是这里可以通过“|”添加多个模式,例如“CFile::modeCreate|CFile::modeWrite”就表示,创建并写入文档

  我们可以看到open的返回值类型是BOOL类型的,成功返回非0值,否则返回0,这里提一下BOOL和bool的区别

boolBOOLbool为布尔型BOOLintbool取值falsetrue,是01的区别 BOOL取值FALSETRUE,是0和非0的区别

  所以我们前面用一个BOOL类型的变量flag去接收open的返回值,并且在下面判断它是否成功打开文档

Seek函数

  打开文档成功以后我们要做的第一步工作就是初始化”指针”,这里的”指针”指的是鼠标点击txt文本任意位置后光标闪烁的位置,实现这个操作需要用到CFile类中的Seek函数,下面我们给出这个函数的原型

CFile::Seek(LONG lOff, UINT nFrom)函数用于在文件内移动到特定的偏移量/*LONG lOff 表示指针(光标)移动的字节数*/ /*nFrom 表示指针移动的方式 CFile::begin,CFile::current ,CFile::end ,分别表示从文件的开头位置,当前位置和末尾位置移动lOff个字节*/函数返回值是指针相对于文件开始位置的偏移量

ReadString函数

  我们读取数据的时候要进行判断,如果当前显示的不是文档的最后一条内容,才能继续读,否则就不行,同时,不光判断,我们要需要将读取的数据保存至我们的变量里面,这里我们用到的函数是ReadString,下面给出函数原型

CStdioFile::ReadString(LPTSTR lpsz, UINT nMax)/*lpsz 指定指向将接收文本字符串的用户提供的缓冲区的指针,以null字符终止*//*nMax 指定能接受的最大字符数限制,以null字符终止*/如果文件有多行,则当文件没有读完时,返回TRUE,读到文件尾,返回FALSE

  这里面提到一个名词叫“缓冲区的指针”,这一点因为作者水平有限,也不是很懂,但是在这里,”用户提供的接受本文的字符串”就是我们的”strline”,所以我们将他作为函数参数,用于判断当不是文档结尾的时候,就继续读

GetPosition函数

   GetPosition() 获取当前文件的指针

strtok函数
  在本例中,我们需要将学号,姓名,成绩分隔开,分别存储至每一个变量当中,所以需要用到strtok函数,下面给出函数原型

char* strtok (char* str,constchar* delimiters);/*str 在第一次被调用的时候str是传入需要被切割字符串的首地址,在后面调用的时间传入NULL*//*delimiters 表示以什么字符分割字符串(字符串中每个字符都会以delimiters当作分割符)*/

  所以我们在程序中,第一次调用strtok函数,将id保存出来,就需要用
  m_id = strtok(strline," ");
  但是这里有个问题,strtok传入的参数是char *型的,而我们传入的“strline”是CString类型的,所以肯定不行,这里还需要用到CSting类库当中的一个函数”GetBuffer”

GetBuffer函数
  先给出函数原型

LPTSTR GetBuffer( int nMinBufLength )/*这个函数是为一个CString对象重新获取其内部字符缓冲区的指针,返回的LPTSTR为非const的,从而允许直接修改CString中的内容,如果仅仅是读出CString中的内容,那么只需要用GetBuffer(0)即可*/

  所以我们看到GetBuffer(0)在这里的作用就是将CString类型的”strline”转换为”char *”
  所以真正将id保存出来,就应该这么写

m_id = strtok(strline.GetBuffer(0)," ");

  而m_name和m_score是继续从”strline”中切割出来,所以只需要将strtok函数前面的参数改为NULL即可

  到此位置,文档的读取操作已经完成,读者可以自己试一试,作者这里展示的例子是针对“下一条”的操作,读者可以增加一个“上一条”


4. “写”文档

  如果读者弄清楚了“读”文档,”写”档其实很简单,下面先给出代码

void CFileDlg::Onadd() {    CStdioFile file;    CString strline;    BOOL flag = file.Open("D:\\test.txt",CFile::modeReadWrite);    if(flag == FALSE)    {        MessageBox("文件打开失败!");    }    UpdateData(TRUE);    if(m_id!=""&&m_name!=""&&m_score!="")    {           file.SeekToEnd();        strline = "\n" + m_id + " " + m_name + " " + m_score;        file.WriteString(strline);        MessageBox("成功添加至文档末尾");        file.Close();    }    else    {        MessageBox("添加失败,不能添加空字符");    }}

  这里首先要弄清楚的一点,写文档的时候,Open函数里面的打开模式就不是只读了,所以这里可以写“CFile::modeWrite“,也可以写“CFile::modeReadWrite”。打开了文档之后,我们要先将光标的位置移到文档的末尾,即最后一个字节的末尾,所以要用到SeekToEnd函数,然后将我们输入的值插入文档末尾下一行。用到SeekToEnd函数,这里就不讲解这个函数了,原型比较简单,传入要写的文本即可


5. “查”数据

void CFileDlg::Onsearch() {    CString i,j,k;    CStdioFile file;    CString strline;    BOOL flag = file.Open("D:\\test.txt",CFile::modeRead);    if(flag == FALSE)    {        MessageBox("文件打开失败!");    }    file.Seek(m_dwPos,CFile::begin);    while(file.ReadString(strline) != FALSE)    {        UpdateData(TRUE);        m_dwPos = file.GetPosition();        i = strtok(strline.GetBuffer(0)," ");        j = strtok(NULL," ");        k = strtok(NULL," ");        if(m_id == i||m_name == j||m_score == k)        {            m_id = i;            m_name = j;            m_score = k;            UpdateData(FALSE);            break;        }    }        UpdateData(FALSE);}

  查数据这里用到的方法主要是循环遍历文本中的所有行,当用户给定的id或name或score(因为并不知道用户会以什么为关键字进行查找)找到时,就停止,break出来,并且将所有的数据显示在编辑框中,函数和上面的差不多,多了一个while循环和if判断,读者认真阅读即可领会


6. “删”数据

  说实话,增、删、查、改四个操作中,最难的就是删,这里读者首先考虑一个问题,是真正在文本中删除一行数据,还是只是隐藏起来,让程序读不到,其实作者一开始想的是第一种,但是仔细想想就会发现,MFC在对TXT文本进行操作的时候,尤其是写入,全部是覆盖性操作,即在前面新增字符,就会替换掉后面的字符,而作者也不清楚有什么函数能直接删除一行数据,所以第一种方法不可行。那就只有第二种方法了,所以我们要思考的是,如何让程序读不出被删除的数据,这里有很多种方法,读者尽管去试,下面我给出我的方法

void CFileDlg::Ondelete(){    int n = 1;    CString i,j,k;    CStdioFile file;    CString strline;    BOOL flag = file.Open("D:\\test.txt",CFile::modeReadWrite);    if(flag == FALSE)    {        MessageBox("文件打开失败!");    }    file.SeekToBegin();    UpdateData(TRUE);    while(file.ReadString(strline) != FALSE)    {        i = strtok(strline.GetBuffer(0)," ");        j = strtok(NULL," ");        k = strtok(NULL," ");        if(m_id == i || m_name == j || m_score == k)        {            m_dwPos = file.GetPosition();            file.Seek(m_dwPos-i.GetLength()-j.GetLength()-k.GetLength()-6,CFile::begin);            file.WriteString("  ");            m_id = "";            m_name = "";            m_score = "";            UpdateData(FALSE);            MessageBox("该条数据已被删除");            break;        }    }}

  作者这种方法比较巧妙,下面我会附上一些程序运行的过程图进行讲解。首先先不考虑代码,我们只考虑逻辑上的问题,要想”删除”一行数据,首先得要用户输入数据,然后找到该数据坐在位置,注意这里的找到既然是找,所以要用到”查”数据的部分思想,这里我就不再提了,不清楚的读者往上看看”查”数据即可。找到数据以后,我们用变量”m_dwPos”去接收返回的光标位置值,这里读者首先搞清楚,ReadString读取一行数据后,光标位置在这一行的末尾,所以当我们点击下一条时,界面上假设显示的id是2,但这个时候光标已经在这行的最末尾了,如果这个时候我们插入数据,那么被覆盖的会是下一行的内容。到这,基本的概念说清楚了,现在作者说说我的方法,如何隐藏一行数据,先看下图

这时起始时,文本当中的内容
这里写图片描述

运行程序,点三次“下一条”(作者随便点的次数,别的作者都试过,不存在偶然性的问题),然后点击删除
这里写图片描述

提示成功
这里写图片描述


接下来就是最重要的地方,文档中变成了什么样子,其实如果读者仔细阅读了代码,可以试试想想,下面我就附上删除后文档内的截图
这里写图片描述

  这里是重点也是难点,首先读者得知道,txt文本除了写字符占用字节以外,“回车”也是占用字节的,而且在TXT文本中,回车是<回车><换行>,即“\r\n”,所以一行文字,除了文字占用的字节外,还要加上空格占用的字节和“\r\n”占用的两个字节,所以如果光标指向行末,那么除了第一行以外,要想从开头移动到光标当前指向的上一行,还需要移动2个空格+2个空格+2(\r\n),即6,所以这就是为什么这里要减6的原因m_dwPos-i.GetLength()-j.GetLength()-k.GetLength()-6,同时,因为id,name,score字符的长度不确定,所以最好不要用固定的数值,而用GetLength()函数去获取,还有一点要注意的是file.WriteString(" ");这段代码中间空了两个空格,因为如果只空一个空格,还是会被strtok函数分割,仍然会被读取进来,所以要用两个空格,当然,三个,四个也可以

  剩下的就没什么很难的部分了,不懂的读者自己运行一遍程序即可,以上就是“删”数据的操作


7. 总结

  MFC对TXT文本的操作应该说不难,读者只要认真理解使用到的函数的用法,很容易便可以上手,最后,感谢观看!

阅读全文
4 0