wordcount2--realized by c

wc.exe 编写说明

先给出:码云地址 github地址

说明:给出git地址是因为码云不支持TOC标签,我直接写锚定代码他也不支持,但是github却可以,所以加上一个github地址

博文目录

git不支持TOC标签生成目录(当场翻白眼)(gitee和github都不支持)

但是好的是,github还是可以实现锚定功能的,但是gitee却不知道为什么不行,所以我也给了一个github的地址。

正式提交后的更新说明

  1. 用例10没有通过,但是我也没有修改,昨天失眠,想了想算法,有点小想法,所以修改了代码;
  2. 昨天在想如果后面要完成别的需求,那么在我的代码上加东西似乎我的代码不是一个易重构的代码(我把这点加入了问题中);
  3. 再次阅读作业的说明,发现我的输出不合要求,已改正;
  4. 看到模板博客有生成的目录,所以加上了目录;
  5. 看到老师给一个同学说可以把测试写成脚本,这样就可以自动化测试,我觉得用shell写个小脚本这样最方便了,所以加上这个东东;

项目代码目录

.
├── bin
│   ├── linux_amd64
│   │   ├── input
│   │   ├── my.ini
│   │   ├── result.log
│   │   ├── result.txt
│   │   └── wc
│   └── windows
│       ├── wc32.exe
│       └── wc64.exe
├── image
│   └── whole_flow_chart.png
├── readme.md
└── src
    ├── function.c
    ├── wc.c
    └── wc.h
  • bin:存放编译后的文件
    • linux_amd64文件夹:在64位linux下编译的可执行文件
    • windows:在windows下编译的可执行文件
  • image文件夹:存放的是readme文件中需要的图片说明文件(cnblog中直接把图片拖到富文本编辑器中即可自动上传)
  • readme.md:项目说明文件,记载着和项目相关的各项文字(我感觉是我唠嗑的地方)
  • src文件夹:存放源文件

1 需要说明

1.1 概要

wc接收一个文本文件,并统计这个文本文件中的信息(行数、字数等)

1.2 基本功能

wc.exe -c file.c //返回文件 file.c 的字符数

wc.exe -w file.c //返回文件 file.c 的单词总数

wc.exe -l file.c //返回文件 file.c 的总行数

wc.exe -o outputFile.txt file.c //将结果输出到指定文件outputFile.txt

我在原来的基本上加了一个帮助选项,因为觉得这样更适用:

wc.exe -h //显示帮助信息

注意:

  • 空格,水平制表符,换行符,均算字符。
  • 由空格或逗号分割开的都视为单词,且不做单词的有效性校验,例如:thi#,that视为用逗号隔开的2个单词。
  • -c, -w, -l参数可以共用同一个输入文件,形如:wc.exe --w --c file.c
    。>
  • -o
    必须与文件名同时使用,且输出文件必须紧跟在-o参数后面,不允许单独使用-o参数

2 基本思路

2.1 接受参数

参数的形式有两种:长选项、短选项,选项间可以组合,长选项用空格隔开各个参数

例: wc.exe --word --charater file.c

短选项可以多个直接叠加或者,也像长选项一样分开写

例: wc.exe -wc file.c wc.exe -w -c file.c

对于一个命令行程序,它可以接受来自命令行的参数。

c语言的main函数中有两个参数:int main (int argc, char
*argv[])
,这两个参数就是用于这个参数的输入。

argc 一个整数,代表有多少个命令行参数,在此注意两个点

​ 1、 参数间是用空格格开的;

​ 2、程序名是第一个参数。

argv[i]是一个指针,指向第i个参数的首地址

理解了以上的两个参数就简单了,只需要做一些基本的字符串处理就可以了。

2.2.1 -h参数

这个参数单独说是因为这个参数不能和别的参数混用,所以我在程序里面是单独写的,一开始就判断是否用户需要的是help帮助,如果是的话,那么完全不必要再运行程序(打开文件),直接exit中止进程

2.2.2 -w -c -l 参数

这三个参数都是一个路数:

1、打开文件;

2、判断要做的操作;

3、执行操作。

它们间只有步骤3是不同的,所以有理由把3写成不同的函数,再由2判断执行哪个。

有一些细节问题是可以考虑的。

比如,因为单复数的关系,有一行/个 单词/字母,应该是不同的表达(是否有s)

额外就是判断一个单词的算法也是值得考虑的问题,我的想法是,如果一个字符,它自己是一个字母,它前面是一个非字母,那么这就是一个单词的标致。

2.2.3 -o 参数

这个参数比较特殊,因为它后面跟了一个文件,要做的事情是把输出的内容存到的文件换成用户自定义的名字。

总的来说是两件事情

  • 捕获用户输入的文件的名字,并创建这个文件;
  • 把输出的信息存进去。

3 程序流程图

1487200-20180927163014379-364757773.png

4 关键代码

4.1 搜索行数

void
lines_searching(FILE *fp, bool out_to_files){
    int line = 0;
    int ch = 0;

    while(!feof(fp)){
        ch = fgetc(fp);
        //putchar(ch);
        if(ch == '\n') line++;
        else continue;
    }//of while loop

    //printf("line is %d\n",line);
    
    output_to_sdo(line, "line", out_to_files);
}//of character_searching function

4.2 查找词数

void
words_searching(FILE *fp, bool out_to_files){
    int letter =0;
    int word = 0;
    bool is_word = FALSE;

    while(!feof(fp)){
        letter = fgetc(fp);

        if((letter >= 'a' && letter <= 'z') || (letter >= 'A' && letter <= 'Z' ) || (letter == '\'')){
            if(!is_word)
                //if if_word is false, it means this is a brand new word
                is_word = TRUE;
        }//of if
        else{
            if(is_word){
                word ++;
                is_word = FALSE;
            }//of if
            else continue;
        }//of else
    }//of while loop

    output_to_sdo(word, "word", out_to_files);
}//of words_searching function

这个里面有一些问题要考虑:

  • 前面提到的-w选项里面对于单词的介定是什么?
    • 一个新单词的标致:当前字符是非字母,并且上一个字符是字母。
  • 进一步考虑
    • that1but算一个单词还是两个单词?th3at算一个单词还是两个单词?
    • I don't like you算几个单词?
    • "do you like me?"he asked 算这个词?
    • 原题中说只要是空格和逗号分开的都算两个词,如果出实换行和不可显特殊字符(没能从键盘键入的字符)怎么处理?
    • 可显的特殊字符单独出现怎么处理?
      • 例1: He died. ? No!
      • 例2: He died. . Ah-ha?
  • 简单考虑这个问题:只有换行(\n)、制表(\t)、空格(
    )可分以分开单词,把上面的判断字符代码改成:
letter != '\n' || letter != '\t' || letter != ' '

4.3 输出函数(包括输出到标准输入输出和文件)

void
output_to_sdo(int number, char* name, bool out_to_files){
        char result_fp_name[50];//the name of the result files

        char result[50];
        //printf("%s\n",specific_file);
        if(number == 0) sprintf(result, "No %s in the file\n", name);
        else if(number == 1) sprintf(result, "1 %s in the file\n", name);
        else sprintf(result, "%d %ss in the file\n",number,name);

        printf("%s", result);

        if(out_to_files){
            strcpy(result_fp_name, specific_file);
        }//of if
        else{
            strcpy(result_fp_name, "result.txt");
        }//of else

        FILE *result_fp = fopen(result_fp_name,"w");

        fputs(result, result_fp);
        fclose(result_fp);
}//of function output_to_fdo

5 测试

主要是做简单的系统测试,等价类和边界值

等价类、边界值

从输入的数据入用进行分析,本程序的输入信息就是从命令行接受的一些数据,这些个数据可以分为两人类

  1. 选项:控制程序运行的方式和输出的方式,这类选项可以分为三类

    ​ 1.1 -h
    :这个选项不能和别人一用,而且这个选项不带参数,它也不允许程序再带参数;

    ​ 1.1 -o
    :这个选项需要和别人组合在一起用,而且不能单用,在这个选项后面需要带一个参数,而且也需要程序带一个参数;

    ​ 1.2
    -w/-c/-l:它们都是是执行特点的查找任务的,这几个选项不带参数,但是需要程序带一个参数。

  2. 参数:在本程序中也就是需要查找的文件,分成这文件存在、不存在。

    输入条件有效等价类无效等价类
    选项-h (1)/ -w(2) / -l(3) / -c(4) / -o out(5) / -w -c(6)-a(7) / -p(8) / -h -w(9)
    参数文件(10)没有文件(11)

自制的待统计文本

I loved you.
But you didn't love me.
I felt sad.
But you seemed to feel happy.
So I begun to hate you.

5.1 user-case 1

seven@mylab:~/wordcount/bin/linux_amd64$ cat result.in 
Usage: wc.exe [OPTIONS]... [FILE]

--character    -c     calculate the numbers of characters in the file
--word         -w     calculate the numbers of words in the file
--line         -l     calculate the numbers of lines in the file
--outtofile=x  -o x   transfer the result to the specific file

5.2 user-case 2

seven@mylab:~/wordcount/bin/linux_amd64$ ./wc -h input
Wrong Parameter!

5.3 user-case 3

seven@mylab:~/wordcount/bin/linux_amd64$ ./wc -w input
23 words in the file
seven@mylab:~/wordcount/bin/linux_amd64$ wc -w input
23 input

与linux下自带的wordcount(wc)的结果对比了下

5.4 user-case 4

seven@mylab:~/wordcount/bin/linux_amd64$ ./wc -w nothing
Fail to Open the File!
seven@mylab:~/wordcount/bin/linux_amd64$ wc -w nothing
wc: nothing: No such file or directory

5.5 user-case 5

seven@mylab:~/wordcount/bin/linux_amd64$ ./wc -l input 
5 lines in the file
seven@mylab:~/wordcount/bin/linux_amd64$ wc -l input 
5 input

5.6 user-case 6

seven@mylab:~/wordcount/bin/linux_amd64$ ./wc -l nothing
Fail to Open the File!
seven@mylab:~/wordcount/bin/linux_amd64$ wc -l nothing
wc: nothing: No such file or directory

5.7 user-case 7

seven@mylab:~/wordcount/bin/linux_amd64$ ./wc -c input 
104 characters in the file
seven@mylab:~/wordcount/bin/linux_amd64$ wc -c input 
103 input

出现了不同

5.8 user-case 8

seven@mylab:~/wordcount/bin/linux_amd64$ ./wc -c nothing
Fail to Open the File!
seven@mylab:~/wordcount/bin/linux_amd64$ wc -c nothing
wc: nothing: No such file or directory

5.9 user-case 9

seven@mylab:~/wordcount/bin/linux_amd64$ ./wc -w -o result.log input 
23 words in the file
seven@mylab:~/wordcount/bin/linux_amd64$ cat result.log 
23 words in the file
seven@mylab:~/wordcount/bin/linux_amd64$ wc -w input > result.log 
seven@mylab:~/wordcount/bin/linux_amd64$ cat result.log 
23 input

5.10 user-case 10

seven@mylab:~/wordcount/bin/linux_amd64$ ./wc -w -h
Fail to Open the File!
seven@mylab:~/wordcount/bin/linux_amd64$ ./wc -w -h nohting
Fail to Open the File!
seven@mylab:~/wordcount/bin/linux_amd64$ ./wc -w -h input 
Wrong Parameter!
23 words in the file

这个用例没有通过

  • 修改了代码,在检测参数的时候,如果查到了-h,则应该报错
for(int i = 1; i < argc-1; i++){
    if(argv[i][0] == '-'){
        switch(argv[i][1]){
            case 'c':
                print_characters = TRUE;
                break;
            case 'l':
                print_lines = TRUE;
                break;
            case 'w':
                print_words = TRUE;
                break;
            case 'o':
                out_to_files = TRUE;
                strcpy(specific_file,argv[i+1]);
                //printf("%s\n",specific_file);
                break;
            case 'h'://add a case to detect -h
                printf("-h parameter should work alone!\n");
                exit(0);
            default:
                printf("Wrong Parameter!\n");
        }//of switch
    }//of if
}//of for loop
seven@mylab:~/wordcount/src$ ./a.out -h input 
-h parameter should work alone!

通过测试!

5.11 user-case 11

检查用例7

  • 手动数了一下可见字符是98个,再加上五行有六个段断的控制符,似乎我是真的错了。
  • 再写了一个简单的文本
seven@mylab:~/wordcount/bin/linux_amd64$ printf "I hate you\nYou hate me\n"
I hate you
You hate me
seven@mylab:~/wordcount/bin/linux_amd64$ printf "I hate you\nYou hate me\n" > my.ini
seven@mylab:~/wordcount/bin/linux_amd64$ ./wc -c my.ini 
24 characters in the file
seven@mylab:~/wordcount/bin/linux_amd64$ wc -c my.ini 
23 my.ini
  • 不信邪,再来一次
seven@mylab:~/wordcount/bin/linux_amd64$ echo "I really made a mistake??" > my.ini 
seven@mylab:~/wordcount/bin/linux_amd64$ ./wc -c my.ini 
27 characters in the file
seven@mylab:~/wordcount/bin/linux_amd64$ wc -c my.ini 
26 my.ini
  • 三次的测试我的结果稳定的比正确结果少一个,有理由相信这是一个系统误差,是程序的错误。
  • 查程序发现的原因,以下是代码
while(!feof(fp)){
    fgetc(fp);
    character ++;
}//of while loop

这样把最后一个结尾字符也记上了,所以应该把结果减去1.

5.12 user-case 12

  • 重新编译后再测试一下这个功能
seven@mylab:~/wordcount/bin/linux_amd64$ ./wc -w my.ini 
5 words in the file
seven@mylab:~/wordcount/bin/linux_amd64$ wc -w my.ini 
5 my.ini
seven@mylab:~/wordcount/bin/linux_amd64$ ./wc -w input 
23 words in the file
seven@mylab:~/wordcount/bin/linux_amd64$ wc -w input 
23 input

5.13 user-case 13

  • 输出格式不对,所以改了一下格式(害怕中文出问题所以我全用了英文)
seven@mylab:~/wordcount/src$ ./a.out -c input 
input, character number:23

5.14 自动测试脚本

#!/bin/sh

#Author: seven
#date: 2018/09/27
#aim: an automatic script to test the program

#a function to print test result
#function will receive 2 parameters,
#first is the type of the test,
#second is whether the test tested as expected
test_result_print(){
    if [ $2 -eq 1 ]
    then
        echo `date "+%Y-%m-%d %H:%M:%S"` " $1 test succeeded!" | tee -a test_log.txt
    else
        echo `date "+%Y-%m-%d %H:%M:%S"` " $1 test met with failure!" | tee -a test_log.txt
    fi
}

compare_result_with_standard(){
    if [ "$1" = "$2" ]
        then 
            if_test_success=1
        else 
            if_test_success=0
    fi
}

test_file_name="input"

#Here is the test for ./wc -h
#the ideal result is the usage of the program
standard_result="Usage: wc.exe [OPTIONS]... [FILE]

--character    -c     calculate the numbers of characters in the file
--word         -w     calculate the numbers of words in the file
--line         -l     calculate the numbers of lines in the file
--outtofile=x  -o x   transfer the result to the specific file"
result=`./wc -h`

compare_result_with_standard "$result" "$standard_result"
test_result_print "wc -h" $if_test_success

#Here is the test for ./wc -h input
#the ideal result is to print "-h parameter should work alone!"
standard_result="-h parameter should work alone!"
result=`./wc -h input`

compare_result_with_standard "$result" "$standard_result"
test_result_print "wc -h print" $if_test_success

#Here is the test for wc -w input
standard_result=`wc -w $test_file_name | sed 's/[^0-9]//g'`
result=`./wc -w $test_file_name | sed 's/[^0-9]//g'`

compare_result_with_standard "$result" "$standard_result"
test_result_print "wc -w input" $if_test_success

#Here is the test for wc -w nothing
standard_result="Fail to Open the File!"
result=`./wc -w nothing`

compare_result_with_standard "$result" "$standard_result"
test_result_print "wc -w nothing" $if_test_success

#Here is the test for wc -l nothing
standard_result="Fail to Open the File!"
result=`./wc -l nothing`

compare_result_with_standard "$result" "$standard_result"
test_result_print "wc -l nothing" $if_test_success

#Here is the test for wc -c input
standard_result=`wc -c $test_file_name | sed 's/[^0-9]//g'`
result=`./wc -c $test_file_name | sed 's/[^0-9]//g'`

compare_result_with_standard "$result" "$standard_result"
test_result_print "wc -c input" $if_test_success

#Here is the test for wc -c nothing
standard_result="Fail to Open the File!"
result=`./wc -c nothing`

compare_result_with_standard "$result" "$standard_result"
test_result_print "wc -c nothing" $if_test_success

#Here is the test for wc -w -o result.log input
standard_result=`wc -w $test_file_name | sed 's/[^0-9]//g'`
result=`./wc -w -o result.log $test_file_name | sed 's/[^0-9]//g'`

compare_result_with_standard "$result" "$standard_result"
test_result_print "wc -w -o result.log input in std output" $if_test_success

result=`cat result.log | sed 's/[^0-9]//g'`

compare_result_with_standard "$result" "$standard_result"
test_result_print "wc -w -o result.log input in outputfile" $if_test_success

测试结果:

seven@mylab:~/wordcount/bin/linux_amd64$ ./test.sh 
2018-09-27 16:11:58  wc -h test succeeded!
2018-09-27 16:11:58  wc -h print test succeeded!
2018-09-27 16:11:58  wc -w input test succeeded!
2018-09-27 16:11:58  wc -w nothing test succeeded!
2018-09-27 16:11:58  wc -l nothing test succeeded!
2018-09-27 16:11:58  wc -c input test succeeded!
2018-09-27 16:11:58  wc -c nothing test succeeded!
2018-09-27 16:11:58  wc -w -o result.log input in std output test succeeded!
2018-09-27 16:11:58  wc -w -o result.log input in outputfile test succeeded!

6 还存在的问题

6.1 选项组合问题

  • 本文前对短选项的定义中有一个这样的组合方式:
    -wl,但是实际上没去实现它(懒);
  • 长选项也没去实现,因为要字符串比较太麻烦了,而且我还要准备考研。

6.2 程序的实现的算法问题

  • 对于是否要把程序的结果输出到文件里面我选择了用一个控制变量进行操作,然后用全局的变量存储文件地址引用到函数中,似乎应该有更好的方法(我特别想看GNU的实现但是我,是在看不懂+找不到源函数);
  • 代码的重用性不太好;

7 总结

  • 开心的是有陈老师原因让我们去学习git,去学习写博客,让我一个大四的老学长也能学到一些东西(已经会用github写日记了);
  • 大学以来一直有个遗憾,就是没有遇到几个好老师认真布置课程设计,让又有上课的知识,又要自己去学习一部分的东西,此前我也没有上课前预习,下课后复习,上课中记笔记的习惯,陈老师的这次课让我挺开心的是,我开始做了,让我在大四的时候找到了点大学的感觉(愿我的研究生之路更好~);
  • 想把程序写得更好,但是我用c语言处理的效率比较低,本来想参考GNU
    LINUX对于WC的编写,无耐我看不懂他们的代码,于是乎,我只能自己先实现一些小功能,形式上和功能上没办做到还不错的样子;
  • 没想到自己写博客还是有很多废话可以说的。

8 参考、致谢

完成一个还算比较难的程序(因为我总想着GNU写过了的wc)对一个学生而言并非易事,参考了很多(其实也不多),见下:

转载于:https://www.cnblogs.com/yusaisai/p/9703612.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值