wc.exe 编写说明
说明:给出git地址是因为码云不支持TOC标签,我直接写锚定代码他也不支持,但是github却可以,所以加上一个github地址
博文目录
git不支持TOC标签生成目录(当场翻白眼)(gitee和github都不支持)
但是好的是,github还是可以实现锚定功能的,但是gitee却不知道为什么不行,所以我也给了一个github的地址。
正式提交后的更新说明
- 用例10没有通过,但是我也没有修改,昨天失眠,想了想算法,有点小想法,所以修改了代码;
- 昨天在想如果后面要完成别的需求,那么在我的代码上加东西似乎我的代码不是一个易重构的代码(我把这点加入了问题中);
- 再次阅读作业的说明,发现我的输出不合要求,已改正;
- 看到模板博客有生成的目录,所以加上了目录;
- 看到老师给一个同学说可以把测试写成脚本,这样就可以自动化测试,我觉得用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 程序流程图
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 -h
:这个选项不能和别人一用,而且这个选项不带参数,它也不允许程序再带参数; 1.1 -o
:这个选项需要和别人组合在一起用,而且不能单用,在这个选项后面需要带一个参数,而且也需要程序带一个参数; 1.2
-w/-c/-l:它们都是是执行特点的查找任务的,这几个选项不带参数,但是需要程序带一个参数。参数:在本程序中也就是需要查找的文件,分成这文件存在、不存在。
输入条件 有效等价类 无效等价类 选项 -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)对一个学生而言并非易事,参考了很多(其实也不多),见下:
- GNU Linux对wc的实现:主要参照了一下格式,因为代码看不懂。
- 参考了一下这位同学对于-w选项的实现,博文地址
- 关于git的使用和码云的使用参考:廖雪峰的博客
- 流程图绘制软件
- 一个跨平台的开源流程图绘图软件dia(不推荐,太难用了)
- markdown有点不熟,参考一篇文章:献给写作者的 Markdown
新手指南 - TOC(Table of Contents)(也就是目录)自动生成软件:GISerDaiShaoqing's Blog