Word Count作业
一.个人Gitee地址:https://gitee.com/Changyu-Guo
二.项目简介
该项目主要是模拟Linux上面的wc命令,基本要求如下:
命令格式:
wc.exe [para] <filename> [para] <filename> ... -o <filename>
功能:
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
:将结果输出到指定文件
要求:
-o
后面必须跟一个文件
-c -w -l
可以同时出现
-c -w -l
可以合并成-wcl
,即命令可以连写如果不指定输出文件,则将结果默认保存在result.txt里面
三.PSP2.1表格
PSP2.1 | PSP阶段 | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 5 | 5 |
· Estimate | · 估计这个任务需要多少时间 | 5 | 5 |
Development | 开发 | 340 | 635 |
· Analysis | · 需求分析(包括学习新技术) | 20 | 30 |
· Design Spec | · 生成设计文档 | 30 | 30 |
· Design Review | · 设计复审(和同事审核设计文档) | 10 | 15 |
· Coding Standard | · 代码规范(为目前的开发制定合适的规范) | 5 | 5 |
· Design | · 具体设计 | 15 | 20 |
· Coding | · 具体编码 | 200 | 400 |
· Code Review | · 代码复审 | 40 | 30 |
· Test | · 测试(自我测试,修改代码,提交修改) | 20 | 30 |
Reporting | 报告 | 60 | 50 |
· Test Report | · 测试报告 | 20 | 15 |
· Size Measurement | · 计算工作量 | 10 | 5 |
· Postmortem & Process improvement Plan | · 事后总结,并提出过程改进计划 | 30 | 30 |
合计 | 405 | 690 |
四.解题思路
由于自己对C语言比较熟悉(主要是C语言编译过后就是exe,其他语言还要打包,就直接用C语言写了),因此选择用C语言来实现这个项目。刚拿到题的时候仔细分析了一下,发现在功能上的要求不高,甚至不用校验单词的有效性,凡是以空格和逗号隔开的都算是单词,因此第一次作业的难点应该在于命令行参数的解析上面。
接下来我用C语言写了一个简单的demo,尝试着梳理一下程序构建思路,应该如何设计,模块怎样划分。demo中所有的功能都在main函数里面,没有上传到码云。
写好demo后,大致整理了一下解题思路:
1.程序执行流程分析
根据项目的要求,该程序执行的大体流程为:首先用户执行程序并附带各种参数,程序首先要分析处理各种选项,校验选项的有效性,并将各种参数和对应的文件联系在一起,然后对不同的文件执行该文件对用的各种操作,然后将最终的结果一并保存在输出文件中。
2.数据结构设计
根据对程序执行流程的分析,由于不同的文件对应着不同的操作,因此需要将文件名和其对应的操作绑定在一起,由此想到了用结构体保存一个文件的相关信息,然后使用链表将各个文件连起来。待命令处理完毕后,只需遍历链表,即可对各个文件执行相应的操作。文件的结构体如下:
// 命令结构体 // 解析命令时存储相关信息 struct Node {bool _c;bool _w;bool _l;bool _hasFile;char inFile[100];int row;int character;int words;struct Node *next; };
3.模块划分
根据程序的执行流程,可以将程序划分为以下几个模块:
(1).主函数
主函数中主要是一些基本的处理和一些简单的逻辑的处理,负责调用其他函数
(2).命令处理模块
对于用户输入的命令的处理,有很多种办法,其中最常用的就是遍历数组,或者将输入的命令编程字符串,然后解析字符串,我选择的是将用户输入的各种选项和命令拼接成一个字符串,然后遍历整个字符串,并做相应的分析。
(3).统计模块
统计模块主要就是对每个文件做相应的统计操作,包括对行数的统计,对单词数的统计,对字符数的统计,每个功能写在一个单独的函数里面。统计完字符后顺便将数据写入文件。
五.关键代码分析
1.命令处理函数
1 // 对用户输入的命令进行分析 2 // 传入的用户输入的命令的字符串,中间用空格隔开 3 // 如果是-开头的,则认为是选项 4 // 如果检测到-o,就立即读取后面紧跟的输出文件 5 // 如果不是-开头的,就认为是输入文件 6 7 // 第二个参数是一串文件的头结点 8 void analyseCommand(char commandStr[], struct Node *Head) 9 { 10 // 遍历整个字符串 11 initFileNode(Head); 12 struct Node *cur; 13 cur = Head; 14 for (int i = 0;; i++) 15 { 16 // 读出当前字符 17 char c = commandStr[i]; 18 // 如果遍历到了\0,说明字符串结束,则退出函数 19 if (c == 0) 20 return; 21 // 如果c是-,则应该是一个选项 22 if (c == '-') 23 { 24 i++; 25 // 读取出-后面的字符,并做判断 26 read: 27 c = commandStr[i]; 28 // 如果-后面是c,就将_c置为true 29 if (c == 'c') 30 { 31 cur->_c = true; 32 if (commandStr[++i] != ' ') 33 { 34 goto read; 35 } 36 continue; 37 } 38 // 如果-后面是w,就将_w置为true 39 else if (c == 'w') 40 { 41 cur->_w = true; 42 if (commandStr[++i] != ' ') 43 { 44 goto read; 45 } 46 continue; 47 } 48 // 如果-后面是l,就将_l置为true 49 else if (c == 'l') 50 { 51 cur->_l = true; 52 if (commandStr[++i] != ' ') 53 { 54 goto read; 55 } 56 continue; 57 } 58 // 如果-后面是o,则后面紧跟的一个参数一定是filePath 59 // 首先判断后面是否有文件,如果有,就添加 60 // 如果没有,就报错 61 // 此时i的index是在选项上的 62 else if (c == 'o') 63 { 64 i += 2; // 将i移动到 65 char next = commandStr[i]; 66 if (next == '-' || next == '0') 67 { 68 printf("after -o must a para\n"); 69 exit(-1); 70 } 71 char path[100] = ""; // 用来存放输出路径 72 for (int j = 0;; j++) 73 { 74 // 读取出命令中的文件名中的每一个字符 75 char ch = commandStr[i++]; 76 77 // 如果读取到了0,就说明文件名读取结束,就退出 78 if (ch == ' ') 79 { 80 break; 81 } 82 path[j] = ch; 83 } 84 memset(outFile, 0, sizeof(outFile)); 85 strcpy(outFile, path); 86 } 87 else 88 { 89 // 如果-后面什么都没有,就判定为错误 90 printf("after - must a para\n"); 91 exit(-1); 92 } 93 } 94 else 95 { 96 // 如果不是-,则判定为输入文件 97 // 此时i定位在输入文件的第一个字符上 98 char path[100] = ""; 99 for (int j = 0;; j++) 100 { 101 char ch = commandStr[i++]; 102 if (ch == ' ') 103 { 104 break; 105 } 106 path[j] = ch; 107 } 108 strcpy(cur->inFile, path); 109 cur->_hasFile = true; 110 struct Node *fileNode; 111 fileNode = (struct Node *)malloc(sizeof(struct Node)); 112 initFileNode(fileNode); 113 cur->next = fileNode; 114 cur = fileNode; 115 i--; 116 } 117 } 118 // 检测是否有输入文件 119 // if (strlen(cur->inFile) == 0) 120 // { 121 // printf("you do not have input file"); 122 // exit(-1); 123 // } 124 }
代码分析:该函数是这次作业中最重要的一个函数,因此单独拿出来说一下。
要点说明:
1.使用for循环遍历整个字符串
2.遇到-之后就认为是一个选项,就紧接着读取他的后一个字符,如果是有效参数,就记录在当前文件的结构体中,否则报错
3.如果是-o,则认为后面紧跟着一个输出文件,不做文件名有效性检验,不做权限检查
4.如果是普通字符开头,则认为是输入文件,不做文件名有效性检查,不做权限检查
5.根据规则,输出文件应该放在该文件对应参数的后面
6.遍历完毕之后,就将相关数据都保存在了文件的结构体中,并连接成了链表,返回后可进行后期相关操作。
六.测试设计
根据要求,根据如下条件设计测试:
是否有输入
是否输入-
-后是否有参数
是否统计行数
是否统计字符数
是否统计单词数
是否支持命令连写
是否支持多文件统计
是否有-o
-o后是否跟文件
根据以上条件,设计了如下批处理文件:
1 .\wc.exe 2 .\wc.exe - 3 .\wc.exe -l 4 .\wc.exe -c 5 .\wc.exe -w 6 .\wc.exe -lc 7 .\wc.exe -lw 8 .\wc.exe -cw 9 .\wc.exe -lcw 10 .\wc.exe -lcw -o 11 .\wc.exe -lcw -o res.txt 12 .\wc.exe -lcw file1.c 13 .\wc.exe -lcw file1.c -o 14 .\wc.exe -lcw file1.c -o res.txt 15 .\wc.exe -lcw file1.c file2.c -o res.txt 16 .\wc.exe -lcw file1.c -lcw file2.c -o res.txt 17 .\wc.exe -lcw file1.c -o -lcw file2.c -o res.txt 18 PAUSE
测试结果如下:
文件输出结果:
七.参考文献
《构建之法--现代软件工程》 --邹新 [第三版]
博客园把我的格式变成了这个样子
(哇的一声就哭出来了)