“人向猿进阶”之软件工程第三课----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。

posted @ 2016-03-21 20:47  wuxiaoyong  阅读(781)  评论(10编辑  收藏  举报