MFC初探实例——简易文本编辑器的编辑注意点 文件流、字符串格式转换、KMP算法


  最近为了做课程的大作业自学了VS2010 MFC编程,选的题目非常简单——文本编辑器。控制台程序仅仅花了一个多小时就完成了,但为了使作业看起来更高端霸气一些,最后还是决定用MFC做个窗体界面的程序出来。

  本人半年前开始接触C++,学了一学期,编控制台程序基本凑合,但为了这次这个小东西,前前后后还是花了一周有余的。先谈一点学习语言的经验教训,刚开始的几天我根本没有半点头绪,就是自己从图书馆借书看,知道有MFC这个东西,就是不知道怎么用,白看了三四天的书和视频。后来,经过同学的指点,我才熟悉了VS2010 MFC的界面,正式开始学习,其实现在回过头看,学习一门语言,一定得先熟悉它的编译器,熟悉了编译器之后,才能和计算机有更好的互动。实际操作才是学习语言最好的办法,这次的学习,到后期基本没看书,基本就是编译器+百度的搭配搞定了所有问题。

  好了,废话不多说,先上程序的最终界面图

  这个编辑器共实现了5个功能,打开、保存、统计、查找和删除。因为使用了编辑框,所以这个文本编辑器比DOS下的编辑器要好用的多得多。下面给出各个函数的代码,并且介绍编程时遇到的一些问题和心得。其中一些变量名下文都会介绍,如果疏忽了自己读一遍应该也能懂,基本不影响理解。

一、打开文件

  前半段我调用了CFileDialog类,使得打开时能够跳出我们常见的文件打开窗体。值得一说的是,CFileDialog类中,只有获取文件路径的GetPathName函数(网上教程都只教到这儿就没了)。我做的时候还想,有没有这样一个函数,能直接打开我选择的文件,结果我找了半天,硬是没找到、、汗。。所以,为了实现点击文件即在编辑框(其变量为IDC_EDIT1)中打开文件内容的功能,最后还是采用文件流的形式。因为可以用GetPathName函数获取文件地址,所以使用CFile.Read函数,通过字符数组将文本内容导入cstring类里面,再通过SetDlgItemText在编辑器中显示文本。打开功能就此实现。

  在这里我也有一个问题,大家可以看到,我使用的是char a[10000]这种静态分配方式(因为还没打开文件,无法获知文件长度),但真的文档编辑器绝对不可能这么做,一定是动态分配空间的,但我想到现在也没想到比较好的办法解决这个问题,如果有大鸟出现的话顺手帮我解决一下,感激不尽~

void CMy005Dlg::OnBnClickedButton1()
{
    // TODO: 在此添加控件通知处理程序代码
    char a [10000];
    // 设置过滤器   
    TCHAR szFilter[] = _T("文本文件(*.txt)|*.txt|所有文件(*.*)|*.*||");   
    // 构造打开文件对话框   
    CFileDialog fileDlg(TRUE, _T("txt"), NULL, 0, szFilter, this);   
    CString strFile,strFilePath;   
  
    // 显示打开文件对话框   
    if (IDOK == fileDlg.DoModal())   
    {   
        // 如果点击了文件对话框上的“打开”按钮,则将选择的文件显示到编辑框里   
    strFilePath=fileDlg.GetPathName();    
    CFile file(strFilePath,  CFile::modeReadWrite);   
    // 将数据写入到文件中   
    file.Read(a,10000);
    a[file.GetLength()]='\0';
    CString str(a);
    SetDlgItemText(IDC_EDIT1,str);   
    }   
}


二、保存

void CMy005Dlg::OnBnClickedButton2()

{   
    // TODO: Add your control notification handler code here   
    // 设置过滤器   
    TCHAR szFilter[] = _T("文本文件(*.txt)|*.txt|Word文件(*.doc)|*.doc|所有文件(*.*)|*.*||");   
    // 构造保存文件对话框   
    CFileDialog fileDlg(FALSE, _T("doc"), _T("my"), OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, szFilter, this);   
    CString strFilePath;   
  
    // 显示保存文件对话框   
    if (IDOK == fileDlg.DoModal())   
    {   
        // 如果点击了文件对话框上的“保存”按钮,则将选择的文件路径显示到编辑框里   
        strFilePath = fileDlg.GetPathName();  
        CString strText(_T("")); 
GetDlgItemText(IDC_EDIT1, strText); //获取edit box中的数据

try 

{ 
CStdioFile file;  
file.Open(strFilePath, CFile::modeCreate | CFile::modeWrite | CFile::typeText); 
//打开D盘的txt文件
file.WriteString(strText); 
//写入edit box中的数据

file.Close();  

}  


catch (CFileException* e) 

{ 

e->ReportError(); 

e->Delete();  

}  

}  
    else return;
    }   


  保存功能的实现基本与打开类似,使用了WriteString函数,代码与其他网络版本大同小异,不赘述。

 

三、统计

  这一部分在DOS中是最简单的,但到了MFC中,由于对字符串格式CString和char[]之间的转化问题不够了解,成了花费我最多时间的一块内容。

  先谈一下字符串转化的机制:在MFC中,如果想要使控件中的内容传递到变量中去,要给控件添加相关变量,在此例中,我们应该给编辑框添加CString变量。作为数据结构的作业,要实现统计功能不能直接用类库函数,而cstring类又不支持str[i]形式的统计法,所以只能用数组定位这种方法实现统计功能,这就牵扯到字符串格式的转换问题。

  首先,利用UpdateData函数将编辑框中内容传到其变量cstr中后,我们需要将其转化为字符数组。

  最开始,我使用的是memcpy函数,代码段为memcpy(s,cstr,cstr.GetLength()),但鬼知道怎么回事,最后做统计的时候,所有统计都只统计恰好前一半的文本内容,一开始我以为是算法问题,后来,我检验了半天才发现是字符串转化时的问题,利用memcpy函数转化时,文本内容只转换了一半,但文本长度一样,被传递字符串的数组中每两个字符中有一个空字符,即若我传递的str=“abcdef”,数组s[0]=a,s[1]内容为空,s[2]=b,s[3]内容又为空,依次类推。。。所以最后只统计出一半的结果。这应该是函数的BUG,在此也请教各位高手。

  后来的话,我就换了如下的方式,采用循环语句赋值(虽然从数据结构角度上讲时间复杂度上升了),但起码这个方案能够正确转换。再通过CString.Format函数与Messagebox实现统计结果的输出。

void CMy005Dlg::OnBnClickedButton3()
{

    // TODO: Add your control notification handler code here   
    INT_PTR nRes;
    UpdateData(TRUE); 
    int len=cstr.GetLength();
    char *s=new char[len+1];
    int i;
    for(i=0;i<len;i++)
    {
        s[i]=cstr[i];
    }
    s[len]='\0';

    int a,b,c;
    a=b=c=0;

    for(i=0;i<len;i++)
    {
        if(s[i]>='0' && s[i]<='9')
        {
            a++;continue;
        }
        else if(s[i]>='A' && s[i]<='Z')
        {
            b++;continue;
        }
        else if(s[i]>='a' && s[i]<='z')
        {
            b++;continue;
        }
        else if(s[i]==' ')
        {
            c++;continue;
        }
    }
    CString str2;
    str2.Format(_T("文本中共有:\n数字个数:%d \n字母数:%d \n空格数:%d \n文章总字数:%d \n"),a,b,c,len);
    nRes = MessageBox(str2, _T("统计结果"), MB_OK);   
    // 判断消息对话框返回值。如果为IDOK就return   
    if (IDOK == nRes)
    return;
}


四、查询

  查询算法是数据结构作业的核心,在该查询算法中我使用了KMP算法,利用代码将其实现时,主要是分两步:

  1,计算查询串NEXT值,存入数组

  2,查询

  这里next[0]设定为-1而不是书上的0,是为了更方便的实现字符的比较,设定为-1后,next值即对应其移动比较的位置,不用再做处理。dstr1为查询框文本变量

void CMy005Dlg::OnBnClickedButton4()
{
    // TODO: Add your control notification handler code here   
    INT_PTR nRes;
    UpdateData(TRUE);
    //将编辑框中的字符串赋给字符数组
    int slen=cstr.GetLength();
    char *s=new char[slen+1];
    int i;
    for(i=0;i<slen;i++)
    {
        s[i]=cstr[i];
    }
    s[slen]='\0';
    int tlen=dstr1.GetLength();
    char *t=new char[tlen+1];
    for(i=0;i<tlen;i++)
    {
        t[i]=dstr1[i];
    }
    t[tlen]='\0';
    //KMP算法,求next值
    int *next = new int[tlen];
    i = 0;
    int j = -1;
    next[0] = -1;
    while(i<tlen-1)
    {
        if(j==-1 || t[i]==t[j])
        {
            ++i;
            ++j;
            next[i] = j;
        }
        else
        {
            j = -1;
        }
    }
    //查找
    int flag=0;
    i = 0;
    j = 0;
    while(i<slen && j<tlen)
    {
        if(j==-1 || s[i]==t[j])
        {
            ++i;
            ++j;
        }
        else
        {
            j = next[j];
        }
        if(j==tlen) 
        {
            flag++;j=0;
        }
    }
    delete[] next;
    CString str;
    str.Format(_T("字符串共出现%d次 \n"),flag);
    nRes = MessageBox(str, _T("查询结果"), MB_OK);   
    if (IDOK == nRes)   
    return; 
}


五、删除

  删除与查询的实现类似,也利用了KMP算法,所不同的是,为了实现删除功能,我们还得另外定义一个和文本内容一样长的int数组(原先使用的是bool数组,但无法实现动态分配空间,只得改用int),给其赋初值均为0,若查询到相同的字符串,首先将该段字符串所对应int数组均改为1,最后,再将int数组所有0对应的字符拼接起来,达到删除的效果。

void CMy005Dlg::OnBnClickedButton5()
{
    // TODO: Add your control notification handler code here   
    INT_PTR nRes;
    UpdateData(TRUE);
    int slen=cstr.GetLength();
    char *s=new char[slen+1];
    int i,j,k=0;
    int dlen=dstr2.GetLength();
    int *visit=new int[slen];
    memset(visit,0,sizeof(visit));
    //将编辑框中的字符串赋给字符数组
    for(i=0;i<slen;i++)
    {
        s[i]=cstr[i];
    }
    s[slen]='\0';
    char *d=new char[dlen+1];
    for(i=0;i<dlen;i++)
    {
        d[i]=dstr2[i];
    }
    d[dlen]='\0';
    //KMP算法,求next值
    int *next = new int[dlen];
    i = 0;
    j = -1;
    next[0] = -1;
    while(i<dlen-1)
    {
        if(j==-1 || d[i]==d[j])
        {
            ++i;
            ++j;
            next[i] = j;
        }
        else
        {
            j = -1;
        }
    }
    //查找删除
    int flag=0;
    i = 0;
    j = 0;
    while(i<slen && j<dlen)
    {
        if(j==-1 || s[i]==d[j])
        {
            ++i;
            ++j;
        }
        else
        {
            j = next[j];
        }
        if(j==dlen) 
        {
            for(k=i-dlen;k<i;k++) visit[k]=1;
            j=0;
        }
    }
    i=j=0;
    while(j<slen)
    {
        while(j<slen && visit[j]==1) j++;
        if(j==slen) break;
        s[i++]=s[j++];
    }
    s[i]='\0';
    delete[] next;
    delete[] visit;
    cstr=CA2CT(s);
    UpdateData(FALSE);
}

  

 

  趁热打铁,总算写完了,尽管写代码的时候千万种煎熬,发各种誓今后不当程序员,但做出程序的感觉真的还是很不错的,通过这次大作业也算是对MFC入了门(虽说MFC已经差不多被半淘汰了。。。),总之学习了很多内容,希望对大家有所帮助。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM