一、專案源代碼地址
本人Gitee專案地址:https://gitee.com/yuliu10/WordCount
二、PSP表格
| psp階段 |
預估耗時 (分鐘) |
實際耗時 (分鐘) |
| 計劃 | 30 | 10 |
| 估計這個任務需要多少時間 | 20 | 20 |
| 開發 | 600 | 660 |
| 需求分析 (包括學習新技術) | 40 | 60 |
| 生成設計檔案 | 60 | 30 |
| 設計復審 (和同事審核設計檔案) | 30 | 20 |
| 代碼規范 | 10 | 0 |
| 具體設計 | 50 | 30 |
| 具體編碼 | 500 | 600 |
| 基本功能實作 | 150 | 200 |
| 擴展功能實作 | 350 | 400 |
| 測驗(自我測驗,修改代碼,提交修改) | 60 | 50 |
| 報告 | 300 | 300 |
| 代碼復審 | 30 | 20 |
| 測驗報告 | 60 | 120 |
| 計算作業量 | 5 | 5 |
| 事后總結, 并提出程序改進計劃 | 10 | 10 |
| 合計 | ||
三、解題思路
在拿到題目之后,自己仔細閱讀了老師給的任務,一開始覺得文本的行數、字數、單詞數的統計沒多大問題,后來在寫代碼的程序中,才發現難點在于命令列,因為自己以前并沒有做過此類的練習,所以一片茫然,在搜集過很多資料以后才又繼續開始了,
總結了一下自己在編程之前的思考以及解決方案:
?用什么語言好呢?
自己學過C#,java,C,但是在涉及到命令列的解釋是一大盲區,之前看到過某同學寫的有關C語言main函式兩個引數argc、argv的文章,于是就參考著用C語言寫了,
?如何獲取命令列一長串的字符呢?
argc是指從命令列輸入的引數個數,包括固定的本檔案的路徑argv[0],char* argv[]是一個指標陣列,index是從0開始的,0存的是本檔案的絕對路徑,1存的是控制臺輸入的第一個引數,以此類推,因此控制臺輸入的命令就存在argv里面,
?如何對獲取到的命令列進行決議呢(即對哪個檔案進行操作、有哪些不同的選項)?
根據習慣,命令列的格式是 wc.exe [para] <filename>,有三個選項可供選擇,如何將不同數量的選擇與相應的檔案對應起來呢,最后想到的是用結構體(struct Node)來標注每一個file的各個狀態(是否有-l選項、是否有-w選項、是否有-c選項、輸入檔案的名字、行數row、字符數characters、字數words),這樣一來,節點Node里面包含了要處理的檔案的所有資訊,要處理某個選項就呼叫對應的寫好的函式即可,
?如何處理多檔案呢?
根據習慣,命令列的格式是wc.exe [para] <filename> [para] <filename>,用鏈表將不同的file節點串起來,每個檔案是獨立的,因此可以將檔案名和檔案要執行的操作封裝在結構體里面,
參考鏈接:
有關main函式的兩個命令列引數argc、argv詳細解釋參見:
https://blog.csdn.net/theLostLamb/article/details/79304203
原本以為在把基礎功能做好以后,還能把擴展功能做一做,后來發現自己還是太年輕了,一個基礎功能就快要了我的小命(我滴中秋啊啊啊),
四、程式設計程序
大致思路就是先實作統計行數、單詞數、字符數各個模塊的功能,封裝在每一個函式里面,這個比較簡單,使用簡單的累加就可以實作,后來碰到命令列的解釋,就將之前的全部推翻了,用上了結構體,先獲取命令列的引數,用 strcat函式對字串進行拼接成commandStr,這個程序在main函式里面直接實作,其次是對commandStr進行決議,將檔案的名字以及對應的選項提取出來放在一個Node里面,每一個檔案為一個單獨的Node,每個Node用鏈表串起來,Node有了以后,就可以根據里面保存的資訊對檔案進行相應的操作了!
設計模塊包括:
- init():對Node節點進行初始化
- analysis():對獲取的命令列字串進行決議
- counLine():統計行數
- countWords():統計單詞個數
- countChars():統計字符數
五、代碼說明
根據我的開發程序,逐步闡述我的代碼:
struct Node { //可供選擇的三個選項 bool _l; bool _c; bool _w; char inputFile[100]; //作為輸入的檔案名 int row; int words; int character; struct Node *next;//指向下一個結點的指標 }; void init(struct Node *node) { node->_l = false; node->_c = false; node->_w = false; //將inputfile變為空(全0) memset(node->inputFile, 0, sizeof(node->inputFile)); node->row = 0; node->words = 0; node->character = 0; node->next = NULL; }
以上代碼段為節點的定義和初始化,用一個節點來存放一個檔案的所有資訊,為每個節點的資訊賦默認初值,以便于后面對命令列決議以后更新其中的狀態,
void analysis(struct Node *Head, char commandStr[]) { init(Head); struct Node *cur; cur = Head; // 對這個字串進行遍歷,依次分析 for (int i = 0;; i++) { char c = commandStr[i]; // c是當前遍歷到哪個字符了 if (c == 0) { // 讀到'\0'代表讀到了字串末尾 return; } else if (c == ' ') { continue; } else if (c == '-') { /* 如果讀到了- 就說明這是一個選項 那么我應該決議它后面的那一個字符,判斷是哪種選項,是l,w還是c */ i++; // 先讓i往后移一位,代表i指向-后面的引數 c = commandStr[i]; // 現在將選項讀取出來了 // 接下來就是判斷是哪種操作 if (c == 'l') { // 這里判斷出了有統計行數這個選項 // 接下來應該是指定當前檔案有這一操作 cur->_l = true; continue; } else if (c == 'w') { cur->_w = true; continue; } else if (c == 'c') { cur->_c = true; continue; } else if (c == 'o') { // 如果-后面是o,則表示要將結果保存在指定檔案里面 // 根據規則,-o后面要緊跟一個輸出的檔案名 // -o res.txt i += 2; char path[100] = ""; for (int j = 0;; j++) { char ch = commandStr[i++]; if (ch == ' ') { break; } path[j] = ch; } // 把原來的result.txt擦出掉 memset(outputFile, 0, sizeof(outputFile)); // 把新的檔案名放進去 strcpy(outputFile, path); } else { printf("after - must a para"); exit(-1); } } else { // 如果既不是0,又不是-, 也不是空格 // 那么就認為它是檔案名字的開頭 char path[100] = ""; // 遍歷每一個字符,存放在path里面 for (int j = 0;; j++) { char ch = commandStr[i++]; if (ch == ' ') { break; } path[j] = ch; } // 將path復制到當前檔案的輸入檔案中 strcpy(cur->inputFile, path); // 這一步結束以后 // 我已經得到了當前的檔案名和這個檔案要執行哪些操作 // 因此這個檔案統計完畢,進行下一個檔案的統計 // wc.exe -l -w -c file1.c -l -w file2.c -o res.txt if (commandStr[i] != 0) { if (commandStr[i + 1] != 'o') { // 如果字串還沒有結束,就new一個新的結點 // 并讓當前節點指向新的節點 struct Node *node; node = (struct Node *)malloc(sizeof(struct Node)); init(node); cur->next = node; cur = node; } i--; } } } }
以上代碼段是對命令列進行解釋,是整個專案最核心的代碼部分,對傳進來的commandStr引數的每個字符一個一個進行遍歷,如果遍歷到‘\0’這個字符時,代表決議完畢,如果讀到‘ ’空格繼續決議,如果讀到‘-’這個字符,判斷下一個字符是‘l’、‘c’、‘w’中的哪一個選項,則對應給結構體中的回應變數做上標記,如若之后是‘o’字符,則后面要緊跟一個指定的輸出檔案,若后面沒有指定檔案,或指定檔案不在當前檔案下,就會報錯“write file failure”,btw,如果沒有“-o”選項,就默認輸出到result.txt,如果遍歷到的 字符既不是‘\0’,也不是‘ ’,也不是‘-’,就說明遍歷到檔案名了,將檔案名保存下來,并將默認的輸出檔案更改成保存下來的檔案名,繼續往后遍歷,還未碰到‘\0’字符表明還有其他檔案,則創建新的節點,繼續以上同樣的操作,
void countLine(struct Node *node) { // 將當前正在處理的檔案節點傳進來,進行統計字符 // 如果沒有讀到檔案末尾 // 就一直執行 while (!feof(rp)) { // 讀取檔案中的一個字符 if (fgetc(rp) == '\n') { node->row++; } } node->row++; rewind(rp); fprintf(wp, "%s,row:%d\n", node->inputFile, node->row); }
以上代碼段是對文本的行數進行統計,并將統計到的資料輸出在指定檔案,
void countWords(struct Node *node) { // 統計單詞個數 char c; int flag = 1; while (!feof(rp)) { c = fgetc(rp); if (flag == 1) { if (c != ' ') { node->words++; flag = 0; } } else if (c == ' ' || c == '\n') { flag = 1; } } rewind(rp); fprintf(wp, "%s,words:%d\n", node->inputFile, node->words); }
以上代碼段是對文本的單詞個數進行統計,并將統計到的資料輸出在指定檔案,
void countChars(struct Node *node) { while (!feof(rp)) { if (fgetc(rp)) { node->character++; } } node->character--; rewind(rp); fprintf(wp, "%s,character:%d\n", node->inputFile, node->character); }
以上代碼段是對文本的字符數進行統計,并將統計到的資料輸出在指定檔案,
int main(int argc, char *argv[]) { // 命令剛開始是在argv陣列里面的 // 可以將命令拼接成一個字串 // 把這個字串傳到一個決議函式里面決議一下 // 將決議的對應結果放在結構體里面 char commandStr[100] = ""; for (int i = 1; i < argc; i++) { strcat(commandStr, argv[i]); strcat(commandStr, " "); } // 將拼接好的字串傳到分析函式里面進行分析 // 分析函式需要接受命令字串作為引數 // 另外還需要接受一個頭結點 struct Node Head; analysis(&Head, commandStr); // 接下來要做的是打開檔案 if ((wp = fopen(outputFile, "w+")) == NULL) { printf("write file failure"); exit(-1); } struct Node *cur; cur = &Head; while (cur != NULL) { if ((rp = fopen(cur->inputFile, "r")) == NULL) { printf("read file failure"); exit(-1); } if (cur->_l) { countLine(cur); } if (cur->_w) { countWords(cur); } if (cur->_c) { countChars(cur); } cur = cur->next; } system(“pause”); return 0; }
以上代碼段是整個代碼的入口main函式,先獲取命令列引數commandStr,創建一個當前節點cur,再呼叫analysis()函式對傳進去的命令列commandStr進行決議,用節點進行標注,通過標注的資訊打開對應的檔案,根據不同的選項進行不同的統計操作,最后將資料輸出到指定檔案中,
以上是對各個代碼段的解釋說明,完整的代碼鏈接:
https://gitee.com/yuliu10/WordCount
六、測驗設計程序
在此次的專案中,我寫了一個批處理檔案對餓哦的專案進行測驗,這樣可以確保每個功能函式能夠正常運行,眾所周知,測驗的高風險點包括代碼中包含分支判定及回圈的位置,因此,在測驗中采用的覆寫方法覆寫到了所有程式代碼陳述句,用以應對高風險點,以下是代碼展示:

測驗后的結果截圖如下:

檔案輸出后的結果:

這里注明一下:我是用VC++6.0進行除錯的,因為這個祖傳的編譯器太老了,在運行的之后不能從控制臺輸入引數,若要進行除錯,具體在vc6.0中按如下步驟: 工程->設定->除錯->程式變數,在此輸入引數:
七、參考文獻鏈接
有關博客的使用和排版,范飛龍老師的這篇博客:http://www.cnblogs.com/math/p/se-tools-001.html
鄒欣老師在《構建之法》中設計的第一項個人作業:http://www.cnblogs.com/xinz/p/7426280.html
有關main函式的兩個命令列引數argc、argv詳細解釋參見:https://blog.csdn.net/theLostLamb/article/details/79304203
如何在VC++6.0環境中運行帶參main函式:https://blog.csdn.net/weiqiang_huang/article/details/17124255
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/222237.html
標籤:其他
下一篇:第二周作業——詞頻統計
