“人向猿進階”之軟件工程第三課----WORDCOUNT.EXE統計程序


---恢復內容開始---

WC項目要求

  這個項目要求寫一個命令行程序,模仿已有的wc.exe的功能,並加以擴充,給出某程序設計源語言文件的字符數、單詞數和行數。給實現一個統計程序,它能正確統計程序文件的字符數、單詞數、行數,以及其他擴展功能,並能夠快速的處理多個文件。

用戶需求

  程序員處理需求的模式為:wc.exe [paramter][file_name]

  各個參數的意義:

  基本功能列表:wc.exe -c file.c:char count;

         wc.exe -w file.c:char count;

         wc.exe -l file .c:line count;

  擴展功能:-s 遞歸處理目錄下符合條件的文件

       -a 返回高級選項(代碼行、空行、注釋行)

        空行:本行全部是空格或者格式控制字符,如果包括代碼,則只有不超過一個可顯示的字符,例如“}”

        代碼行:本行包括多於一個字符的代碼。

        注釋行:本行不是代碼行,並且本行包括注釋,例如:}//注釋。這種情況下,這一行屬於注釋行。

        [file_name]:文件的目錄名,可以處理一般通配符。

        文本文件,確定字/詞/句

  高級功能:-x參數 這個參數單獨使用,如果命令行有這個參數,則程序會顯示圖形界面,用戶可以通過界面選取單個文件,程序就會顯示文件的字符數、行數等信息。

需求舉例:wc.exe -s -a *.c  返回當前目錄、子目錄所有.c文件的代碼行數,空行數,注釋行數。

  根據需求編寫程序,C++學的不好,就只有用C了,然而看見別人在用C#寫心里着實好癢,總歸根據個人情況而定,有些東西是羡慕不來的。

1:頭文件,宏定義;在此次編程過程中,深刻體會到了基礎薄弱的可怕,最初的程序不是這樣,這是程序第二版。在第一版的編寫過程中,出現了以下的問題:

大神們肯定一看就明白了到底哪兒出錯了,哦,對了,這個截圖是我發現問題之后重新建的一個項目,來體會用的,所以代碼少。首先遇到這種問題是看自己是不是以前遇到的,可惜沒有(或者遇到過,但是忘了),然后就百度谷歌去了,,然后搜索了N多個答案,都沒有找到想找的答案,或者說是類似的答案,這里截圖就不奉上了。然后我就那個愁吶,愁啊,換着法子的搜索答案,但是找不到,,此處飛去倆小時,,然后直接給大神發消息了,大神立馬就會了我,當時激動完了,覺得大神不愧是大神,然而打開消息一看,大神說我在吃飯,等會看。然后我就繼續編寫其他的代碼去了,,終於等到大神回我了,然后我和大神一頓討論啊,邊討論邊實踐,然后就這樣又是1小時過去了,然后大神說,那我也不知道了。。。。。。。。然后我就重寫代碼了,准備看看到底哪兒出問題了,然后剛沒寫多少行,就發現了'\'這個符號,不對啊,這個轉義符號怎么能這樣呢,,,然后就出現了下面的截圖

頓時,我的心里一群羊駝飛奔而去,,,,,,說不出的心酸和淚啊╮(╯▽╰)╭。。。

第一版運行成功了,當然只是簡單的基本功能實現了。然胡開始了程序第二版,然后又體現出了自己的基礎薄弱,還有的就是動手上機編程的缺乏,我想實現在某個目錄下遞歸進行處理一類文件,但是不知道,用哪個函數和頭文件,然后又是谷歌了。。。。又是幾個小時過去了,會了一點點,然后就開始頭文件<io.h>之旅。

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <io.h>

void open_file(int a,int b,int c,int d,char data[]);
FILE *letter(FILE *fp1,char ch1);
FILE *message(FILE *fp1,char ch2);
FILE *null(FILE *fp1,char ch3);
void printDir( const char* path );

char str1[50],str3[50];
int word_N=0,line_N=0,ch_N=0;//記錄單詞數、行數、字符數
int null_h=0,code_h=0,mess_h=0;//記錄空行、代碼行、注釋行
int s_ch=0,s_word=0,s_line=0,hang_a=0,s_s=0;//記錄輸入的查詢數據

2:main 函數,對於main 函數是采取了取巧的方式來讀取輸入的命令行命令,沒辦法,實在想不到更好的方法了,當沒有方法的時候只有暴力解決了。。用s[]字符串數組保存命令行命令。然后for判斷用戶輸入的是什么命令,並給相應的變量賦值。用strncpy函數查找到文件地址起始值,然后賦給字符串數組str3,最后根據輸入的命令調用文件函數或者遍歷目錄函數。

int main ()
{
    char s[100];
    gets(s);
    
    while(1)
    {
        memset(str1, 0, sizeof(str1));
        memset(str3, 0, sizeof(str3));
        word_N=0,line_N=0,ch_N=0,null_h=0,code_h=0,mess_h=0;
        s_ch=0,s_word=0,s_line=0,hang_a=0,s_s=0;
        int length_s=0;
        length_s=strlen(s);
        
        int i=0;        
        for(i=7;i<length_s;++i)
        {
            if((s[i]=='-')&&(s[i+1]=='c')){
                s_ch=1;
            }
            if((s[i]=='-')&&(s[i+1]=='w')){
                s_word=2;
            }
            if((s[i]=='-')&&(s[i+1]=='l')){
                s_line=3;
            }
            if((s[i]=='-')&&(s[i+1]=='a')){
                hang_a=4;
            }
            if((s[i]=='-')&&(s[i+1]=='s'))
            {
                s_s=5;
            }
            if(s[i]=='F'){
                strncpy(str1,s+i,length_s-i);
                strcpy(str3,str1);
                break;
            }//獲取文件名
        }
        if(s_s==5)
        {
            s_ch=1;
            s_word=2;
            s_line=3;
            hang_a=4;
            printDir(str1);
        }
        else open_file(s_ch,s_word,s_line,hang_a,str1);
        
        printf("\n");
        gets(s);
    }
    return 0;
}

 3:查找同一目錄下同類文件的函數,當找到這類文件時調用文件函數,讀取文件中的字符。當輸入的是目錄時,這部分功能還不完善,還不能遞歸調用某目錄下的所有同類文件,包括子目錄的。目前只能處理同目錄下的同類文件,但不包括子目錄下的。從網上找到了在C++編譯環境下遞歸查找某類文件的程序,然后動手改了一下午,先是把程序全部改為C++,但是改到一半,發現自己的程序設計結構不是很合理,導致如果改成C++了,那么更加會加大工作量。然后就開始了把C++改為C之旅,但是幾個小時之后還是沒有成功,,差不多也到了要提交程序的時候了,就放棄了遞歸處理目錄和子目錄以及之后的高級功能,這是C++遞歸查找文件的網址:http://blog.csdn.net/u012234115/article/details/43533667

/*
struct _finddata_t
{
    unsigned attrib;     //文件屬性
    time_t time_create;  //文件創建時間
    time_t time_access;  //文件上一次訪問時間
    time_t time_write;   //文件上一次修改時間
    _fsize_t size;  //文件字節數
    char name[_MAX_FNAME]; //文件名
};
//按FileName命名規則匹配當前目錄第一個文件
_findfirst(_In_ const char * FileName, _Out_ struct _finddata64i32_t * _FindData); 
 //按FileName命名規則匹配當前目錄下一個文件
_findnext(_In_ intptr_t _FindHandle, _Out_ struct _finddata64i32_t * _FindData);
 //關閉_findfirst返回的文件句柄
_findclose(_In_ intptr_t _FindHandle);
*/

void printDir( const char* path )
{
    struct _finddata_t data;

    long hnd = _findfirst( path, &data );    // 查找文件名與正則表達式chRE的匹配第一個文件
    if ( hnd < 0 )
    {
        perror( path );
    }
    int  nRet = (hnd <0 ) ? -1 : 1;
    while ( nRet >= 0 )
    {
        if ( data.attrib == _A_SUBDIR )  // 如果是目錄
        {
            printf("文件名:[%s]*\n", data.name );
            open_file(s_ch,s_word,s_line,hang_a,data.name );
        }
        else
        {
            printf("文件名:[%s]\n", data.name );
            open_file(s_ch,s_word,s_line,hang_a,data.name);
        }
        nRet = _findnext( hnd, &data );
    }
    _findclose( hnd );     // 關閉當前句柄
}

4:下面介紹的是本次程序的重頭戲。在程序第一版當中,我沒有像現在這樣分類子函數進行判斷讀取出來的是字符、空行、單詞等,因為第一版只是進行了簡單的基本功能的實現,對於程序結構要求不高,但是當我想實現-a -s這些功能的時候,發現第一版完全不能用了,so果斷的重寫程序,然后就想現在這樣的第二版程序一樣了。由data傳入文件名,再由str3提供文件目錄,拼接在一起就成為文件地址,然后打開文件,逐個讀取字符。用switch語句判斷字符類型,進入到相應的子程序,說到switch,又是一把辛酸淚,,,由於太久沒有像大一暑假時在ACM隊那樣天天刷題,經常做算法等,導致編程水平一天不如一天,,然后就在用switch的時候,寫了幾個case,直接寫了子函數,直接就把break忽略了,總覺得哪兒有問題,但是就是想不起來,。等到把程序大體完成之后,單步調試時,運行到switch,悲吹的我還是沒有發現這個問題,等到子程序跳出來的時候,發現怎么所有的子程序都能進了,我還傻傻了半天,,,,然后就把“陌生人”break給請到了case后面。程序最后,通過判斷主函數傳遞過來的變量參數值,來確定輸出數據。

程序第一版:

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <io.h>

void open_file(int a,int b,int c);
char str1[50];
char sym[100]={',','.','/','!','(',')','{','}',':','+','-','*','='};

int main ()
{
    char s[100];
    gets(s);

    while(1)
    {
        int s_ch=0,s_word=0,s_line=0;//記錄輸入的查詢數據
        int length_s=0;
        length_s=strlen(s);
        
        int i=0;
        
        for(i=7;i<length_s;++i)
        {
            if((s[i]=='-')&&(s[i+1]=='c'))
            {
                s_ch=1;
            }
            if((s[i]=='-')&&(s[i+1]=='w'))
            {
                s_word=2;
            }
            if((s[i]=='-')&&(s[i+1]=='l'))
            {
                s_line=3;
            }
            if(s[i]=='F')
            {
                strncpy(str1,s+i,length_s-i);
                break;
            }//獲取文件名
        }
        open_file(s_ch,s_word,s_line);
        printf("\n");
        
        gets(s);
    }
    
    return 0;
}

void open_file(int a,int b,int c)
{
    FILE *fp;
    fp=fopen(str1,"r");
    if(fp==NULL)
    {
        printf("the file not found\n");
        exit(0);
    }//打開地址文件
    
    int word_N=0,line_N=0,ch_N=0;//記錄單詞數、行數、字符數
    char ch;
    while((ch=fgetc(fp))!=EOF)
    {
        ch_N++;
        
        int j=0,length_sy=0;//符號表的長度
        length_sy=strlen(sym);
        char s1[30];//保存單詞
        
        if(isalpha(ch))
        {
            s1[j++]=ch;
            ch=fgetc(fp);
            ch_N++;
            while(isalpha(ch))
            {
                s1[j]=ch;
                j++;
                ch=fgetc(fp);
                ch_N++;
            }
            word_N++;
        }//是字母型的單詞
        if(isdigit(ch))
        {
            s1[j++]=ch;
            ch=fgetc(fp);
            ch_N++;
            while(isdigit(ch))
            {
                s1[j]=ch;
                j++;
                ch=fgetc(fp);
                ch_N++;
            }
            word_N++;
        }
        if(ch=='\n')
        {
            line_N++;
        }//行數
        while(length_sy--)
        {
            if(ch==sym[length_sy])
            {
                word_N++;
            }
        }//符號表的單詞
    }
    if(a!=0)
        printf("字符數:%d\n",ch_N);
    if(b!=0)
        printf("單詞數:%d\n",word_N);
    if(c!=0)
        printf("總行數:%d\n",line_N+1);
}

程序第二版:

void open_file(int a,int b,int c,int d,char data[])
{
    word_N=0,line_N=0,ch_N=0,null_h=0,code_h=0,mess_h=0;
    int i=0,length_str3=0,j=0;
    length_str3=strlen(str3);
    char str2[50];
    memset(str1, 0, sizeof(str1));
    strcpy(str1,str3);
    for(i=0;i<length_str3;++i)
    {
        if(str3[0]==data[0])
        {
            strcpy(str2,str3);
            break;
        }//當未輸入指令-s時,直接打開str1
        else if(str3[i]=='*')
        {
            for(;i<length_str3;++i)
                str1[i]='\0';
        /*    while((data[j]!='\0'))
            {
                str1[i]=data[j];
                i++;
                j++;
            }//把符合條件的文件名鏈接到目錄下*/
            strcat(str1,data);
            printf("文件位置:%s\n", str1);
            strcpy(str2,str1);
            break;
        }
    }

    FILE *fp;
    fp=fopen(str2,"r");
    if(fp==NULL){
        printf("the file not found\n");
        exit(0);
    }//打開地址文件
    
    char ch;
    ch=fgetc(fp);
    ch_N++;
    if(ch==EOF)
        ch_N--;
    while(ch!=EOF)
    {
        switch(ch)
        {
        case ' ':fp=null(fp,ch);break;
        case '{':fp=null(fp,ch);break;
        case '}':fp=null(fp,ch);break;
        case '/': fp=message(fp,ch);break;
        default :fp=letter(fp,ch);break;
        }
        ch=fgetc(fp);
        ch_N++;
        if(ch==EOF)
            ch_N--;
    }
    
    if(a==1)
        printf("字符數:%d\n",ch_N);
    if(b==2)
        printf("單詞數:%d\n",word_N);
    if(c==3)
        printf("總行數:%d\n",line_N);
    if(d==4)
    {
        printf("代碼行:%d\n",code_h);
        printf("注釋行:%d\n",mess_h);
        printf("空行:%d\n",null_h);
    }
}

5:緊接着的就是注釋處理函數。當連續讀取出兩個'/'字符時,標明此行是注釋行,讓程序一直運行讀取單個字符,直到此行結束,並判斷是否文件結束符,若是對字符數減一,跳出子函數,若不是,則返回文件指針並且回退一個字節。說到文件結束符和返回文件指針並回退,又是一個一個的坑,這里就先說說文件結束符吧。因為最初我的設計的在文件函數

open_file()中讀取字符並判斷,但我在子函數又對文件進行了操作,所以只好認了。在其他的函數我都想到了文件結束符的情況,但是就在這個注釋行這里給栽了,然后就很悲劇的在運行程序的時候,一個一個的去數文件中我放了多少字符,每類數據是多少,然后確定程序輸出。單步調試程序,查找漏洞,找到一個補一個,,,誰讓自己程序設計的不好,並且編程實踐少呢。。。
FILE *message(FILE *fp1,char ch2)
{
    char ch_2;
    ch_2=fgetc(fp1);
    ch_N++;
    if(ch_2=='/')
    {
        ch_2=fgetc(fp1);
        ch_N++;
        while((ch_2!='\n')&&(ch_2!=EOF))
        {
            ch_2=fgetc(fp1);
            ch_N++;
        }//換行時輸出
        ch_2=fgetc(fp1);
        ch_N++;
        mess_h++;
        line_N++;//注釋行+1,總行數+1,獲取\n之后的下一個字符;
        if(ch_2==EOF)
        {
            ch_N--;
            return fp1;
        }
    }
    fseek(fp1,-1,1);
    ch_N--;
    
    return fp1;
}//注釋函數

6:空行函數與注釋函數類似,就不細細解讀了。

FILE *null(FILE *fp1,char ch3)
{
    char ch_3;
    ch_3=ch3;

    if((ch_3=='{')||(ch_3=='}'))
    {
        ch_3=fgetc(fp1);
        ch_N++;
        if(ch_3=='\n')
        {
            null_h++;
            line_N++;
            ch_3=fgetc(fp1);
            ch_N++;
        }
    }
    if(ch_3==EOF)
    {
        ch_N--;
        null_h++;
        line_N++;
        return fp1;
    }

    while((ch_3==' '))
    {
        ch_3=fgetc(fp1);
        ch_N++;
    }
    if(ch_3=='\n')
    {
        null_h++;
        line_N++;
        ch_3=fgetc(fp1);
        ch_N++;
    }
    fseek(fp1,-1,1);
    ch_N--;

    return fp1;
}//空行函數

7:現在是本程序的最后一章,也是本程序最重要的第二章,對於單詞的分析。上面說到文件指針作為返回值以及回退的問題,這里就接着來吧。因為是子函數,而文件打開是在其他子函數中進行的,所以第一需要把文件指針傳進來,然后在子函數里面對文件指針進行了操作,需要返回文件指針,對就是返回,然后我就樂呵呵的返回了,等到我高高興興的運行程序的時候,完了,咋不對啊,怎么這么多字符數啊,然后就單步啊,運行運行在運行,發現每次從子函數返回到另外的函數時,文件指針的指向的是當前字符的下一個字符,然后就回唄。。我就想了各種方法啊,好像fseek能回退吧,一試是返回了,但是返回的不正確啊。。。總之后來我是又問大神了,大神也說fseek(fp,-1,1)沒錯,然后我心里那個翻滾啊,為什么沒錯,程序就是不出來呢,然后我那個備受挫折啊,,,果斷不寫了。但是一想算了,不跟它一般見識,又重新打開了,然后抱着試試的態度,一運行,,,,,,結果,NM的居然運行對了,,,,,,,我的小心肝啊,都要崩了,,,,

FILE *letter(FILE *fp1,char ch1)
{
    char ch_1;
    ch_1=ch1;
    
    if(isalpha(ch_1))
    {
        ch_1=fgetc(fp1);
        ch_N++;
        while((isalpha(ch_1))||(isdigit(ch_1)))
        {
            ch_1=fgetc(fp1);
            ch_N++;
        }
        word_N++;
    }//字母型單詞
    else if(isdigit(ch_1))
    {
        ch_1=fgetc(fp1);
        ch_N++;
        while((isdigit(ch_1))||(isalpha(ch_1)))
        {
            ch_1=fgetc(fp1);
            ch_N++;
        }
        word_N++;
    }//數字型單詞
    else if(ch_1=='\n')
    {
        code_h++;
        line_N++;
        ch_1=fgetc(fp1);
        ch_N++;
    }
    else if(ch_1==EOF)
    {
        ch_N--;
        return fp1;
    }
    else
    {
        ch_1=fgetc(fp1);
        ch_N++;
    }
    fseek(fp1,-1,1);
    ch_N--;

    return fp1;
} 

  對了,差點都忘了輸出結果了,

 

 第三周已經過去了,而我們的軟件工程課才剛剛開始,不知道到最后我們是否還會如現在這般編程累贅式,總之未來是不可預測的,一切皆有可能。

  

  好吧,正事說完了,該說說這周的發展了,從上周開始我就一直在找有關VS2015單元測試的信息,並且安裝了VS的好幾個版本來進行測試,這個裝了試試然后卸載,再試試那一個。周一,我們休息了一天,但是因為考研,所以基本也沒怎么休息。周二的時候周筠老師給我推薦了兩本書,然后下午就到了,周二就在一天看書中過去,說到看書,目前已經斷斷續續的看了上冊的一半,等過幾天在寫感觸吧。周三上午上課,下午晚上考研課,周四一天都在准備第二天上課給同學們將單元測試的內容,周五下午開始了這次的編程之旅。中途斷斷續續的進行,有時候某個想法突然就冒出來了,然后就在程序里進行實踐,然后不成功,就給刪了,加上中途遇到的各種小問題,耽擱了不少時間,再加上自己基礎的薄弱,有時候需要上網查詢一些函數的使用或者是某種想法需要使用到的函數,看了N篇博客和文檔,終於在周天晚上完成了這項工作。由於周六上一天考研課的疲憊,導致周六進行的不是很順利,周天上了半天考研課,中午休息了一個多小時,起來繼續未完成的事業。。這一周過得很充實,也很累,接連打破自己的“世界紀錄”,但同樣收獲也是頗豐的,盡管除了平時上課的時間,幾乎所有時間都獻給了我的軟件工程,但同樣也找到了很大的差距,如果以前堅持做自己的ACM,堅持刷題,如今的自己又會現在編個程序都費勁嗎?看到別人用c#,用Android,用HTML等,總覺得自己大學似乎還缺少點什么,盡管已經決定考研,但是自己就真的那么堅持不懈的考研嗎,會堅持到最后嗎?答案是需要時間來檢驗的。在接下來的幾個月里,我將踏上征程,踏上開始考研復習的征程,盡管之前幾周已經落后了很多,但是天道酬勤,總有一天會趕上來的;踏上軟件工程的學習征途,這學期的改革,給我帶來了一些壓力,有正向的,有反向的,但最終起決定作用的還是心,,心若向陽,何懼憂傷。我也一直再問自己,這次的改革我們學院會成功嗎?最終我們又能達到什么樣的水平,和預期的有多少出入?我有點杞人憂天了吧,畢竟這不是我應該考慮的問題,但是我相信不僅是我,身邊應該還有很大一部分同學的心里都是沒底的,畢竟以前欠的帳太多,這樣一下補起來可能有點困難。

  總之,現在我應該做的就是,積極配合老師們的行動,相信有那么多大神老師來指導我們肯定會提高,相信一切皆有可能,好好的把考研與上機實戰分配開來,走出屬於自己的道路,也許之前已經有過學長學姐們走過,不論我們是不是同一所學校,同一個城市,同一個省,同處一個地區........我們都會走出自己的精彩,see you today。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM