最近為了做課程的大作業自學了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已經差不多被半淘汰了。。。),總之學習了很多內容,希望對大家有所幫助。