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