2017秋-软件工程第二次作业
本周因为个人缘故,参加社团活动作业没能及时完成。对此我表示,做过就不后悔,至少我觉得生活是丰富多彩的,错过的时间就应该努力赶上!夜深人静的时候总是可以让人反省自己。本次作业我只实现了第一个功能和第二个功能的部分。对此我表示很不满,但是时间紧迫、个人能力有限,以至于自己没能让自己的软件看起来完美。
第二次作业的内容非常有趣,这也是我一直想做的一件事情,统计一篇文章里的字词。我知道自己的编程能力较差、距离完成提交时间很近,自己手写全部是不能及时按照约定提交的,于是就尝试借鉴前人的代码。第一晚的努力各种报错的冲击下化为无用功,因为第一晚所修改的代码不可读取大文件。我对其中一些语句的理解还有所不足,所以第二天就没再使用。第二天我从51网站上下载了一份有关“词频统计”功能的代码来使用。老师提到希望我们使用c#语言,因为前一周及周末都没有花时间学习,对新鲜语言感到陌生和抗拒,又因为不想安装jave环境,所以只好选用c++来开发。第一晚安装了visualstadio,新建项目运行代码。
任务一,主要有2部分:1读取示例文本文件中的文字,2对该文本中的文字进行数量统计。但是我觉得本部分难点在于如何在控制台,使用命令行语句,操作程序。先上图,查看完成结果。
因为在命令行执行,这个是我不熟悉的地方。开始的时候我是不明白的。包括那个“type”的功能也不知道。第一项比较简单。
第二项作业我遇到了很多难题:包括自己手打的txt就出错,老师示例样本中的却不出错(开始的时候我没有下载到老师的测试用例,而是复制全部拷贝)、我还修改了txt的字符格式。大文件的读取也是一个问题,在第一晚尝试的程序中不能很好的读取全部内容(所以舍弃不用),之后就是一些小问题,我一一解决。如下展示部分关键代码。
该部分代码用于显示输出内容,格式控制的主要部分。
1 void Display_for_softwareclass(list<Word> &lWord) 2 { 3 list<Word>::iterator iteBegin = lWord.begin(); 4 list<Word>::iterator iteEnd = lWord.end(); 5 list<string> word; 6 for (; iteBegin != iteEnd; iteBegin++) 7 { 8 word.push_back((*iteBegin).GetWordInfor()); 9 } 10 word.sort(); 11 word.reverse(); 12 int icount = 0; 13 string sTemp; 14 list<string>::iterator wordBegin = word.begin(); 15 list<string>::iterator wordEnd = word.end(); 16 sTemp = *wordBegin; 17 int total = 1;//用于记录不同的单词的数目 18 19 for (; wordBegin != wordEnd; wordBegin++) 20 { 21 if (sTemp == *wordBegin) 22 { 23 icount++; 24 continue; 25 } 26 total++; 27 if (sTemp != *wordBegin) 28 { 29 sTemp = *wordBegin; 30 icount = 1; 31 } 32 } 33 cout <<"total "<< total << endl << endl; 34 word.sort(); 35 word.reverse(); 36 icount = 0; 37 wordBegin = word.begin(); 38 sTemp = *wordBegin; 39 40 cout << left << setw(15) << sTemp; 41 for (; wordBegin != wordEnd; wordBegin++) 42 { 43 if (sTemp == *wordBegin) 44 { 45 icount++; 46 continue; 47 } 48 cout << right << setw(3) << icount << endl; 49 //total++; 50 if (sTemp != *wordBegin) 51 { 52 sTemp = *wordBegin; 53 cout << left << setw(15) << sTemp ; 54 icount = 1; 55 } 56 } 57 cout<< right << setw(3) << icount << endl; 58 cout << endl << endl; 59}
这段代码展示的是消耗我时间最长的一部分代码。其中主要的问题来自数据类型!!!目前来讲,通过命令行控制程序这个功能我知道怎么实现,但在本程序中没有体现。
1 /* 2 inline char* UnicodeToAnsi(const wchar_t* szStr) 3 { 4 int nLen = WideCharToMultiByte(CP_ACP, 0, szStr, -1, NULL, 0, NULL, NULL); 5 if (nLen == 0) 6 { 7 return NULL; 8 } 9 char* pResult = new char[nLen]; 10 WideCharToMultiByte(CP_ACP, 0, szStr, -1, pResult, nLen, NULL, NULL); 11 return pResult; 12 } 13 char TcharToChar(const TCHAR * tchar) 14 { 15 int iLength; 16 char res; 17 char * _char; 18 //获取字节长度 19 iLength = WideCharToMultiByte(CP_ACP, 0, tchar, -1, NULL, 0, NULL, NULL); 20 //将tchar值赋给_char 21 res = WideCharToMultiByte(CP_ACP, 0, tchar, -1, _char, iLength, NULL, NULL); 22 return res; 23 } 24 */ 25 int _tmain(int argc, _TCHAR* argv[]) 26 { 27 list<Word> lWord; 28 string fileName; 29 cout << ">wf -s "; //这两行完全是为了凑格式写的 30 cin >> fileName; 31 OpenFile(fileName, lWord); 32 Display_for_softwareclass(lWord); 33 /* 34 wcout << argc << endl; 35 for (int i = 0; i < argc; i++) 36 { 37 wcout << "argv[" << i << "]=" << argv[i] << endl; 38 } 39 char argv_char[100] = ""; 40 char argv_char_result[100] = ""; 41 argv_char_result[100] = TcharToChar(argv[2]); 42 string fileName; 43 fileName = argv_char_result[100]; 44 cout << "文件名字是:"<<fileName; 45 OpenFile(fileName, lWord); 46 Display_for_softwareclass(lWord); 47 */ 48 return 0; 49 }
在命令行中,运行“wf”文件,传递的第一个参数是“-s”,第二个参数是“test.txt”。“-s”是参数?还是什么?使用不同语言的同学给了我各种各样的回答,简单说几个:某个参数的形参、无意义的参数,空过去不用就好、也许是运行程序中预期的某种功能s,lunix系统下的某种用法。至此,我想听一下老师的解答。其实不是我“不想问”,而是我“不会问”。我不知道怎么描述这个问题,不知道这个是有关那一部分的知识,怎么提问。
在一段时间的学习后,我大概知道了其中的含义,在使用控制台运行时,main()函数是可以接收参数的!它不仅仅是一个函数的名称了。开始的时候我是高兴的,因为通过尝试小例子,我可以愉快的读写main()函数中传入的参数。但是当具体写入我的程序的时候,我就很麻烦:各种错误。最大的问题来自这里:
int _tmain(int argc, _TCHAR* argv[]){}
_tmain和main的区别,char和_TCHAR*的区别?我希望使用_TCHAR* 这样的数据类型的字符,将它转化为string类型的即可当作文件名使用,但是强制转换过程中出现了问题。我还尝试使用main和char代替他们,也出现了问题。出现的问题对我来说,无法理解,感觉不好解决,经过百度,我得到了很多目前我觉得很闹心的答案,好几次出现“类似的”问题,但是百度得到的答案却不同:内存出错、内存冲突、空指针、内存空间不足、叫我们调用堆栈查看内部获取值。我也看到了获取了非法的数值,应该出现1个单词的地方出现了一行,但是我却不知道怎么找到对应解决办法。
如下,我想问老师一些问题:
1我们是应该先仔细看别人的代码,再模仿写自己?还是从头开始写自己的,不会哪里找哪里?
2遇到那些,不知道怎么解决的问题怎么问?我们应该怎样描述所遇到的问题?去哪里问?我们怎样较快的查找到相关的解答?
3抄袭可耻,但是复制代码属于抄袭么?怎么算这个软件是我自己写的,还是抄别人的?
4怎样看待那些莫名其妙的问题?比如自己手写输入的测试用例就不好用,反而下载的好用?那我怎么确定是样本不好,还是我的程序不好呢?
任务一小结:完成了读取文本文件内容并输出的功能,完成了对一些符号的识别和区分。未完成大小写的统一、未完成从命令台输入指令传递参数的功能。
任务二
任务二是任务一的升级,要求读入大量的数据并统计单词量。我完成了统计每个单词数量、单词总个数的功能,完成了排序功能,如下是效果展示图和代码。
采用的是《战争与和平》大概3.5MB.很多程序不能运行大文件,本程序可以读取大文件。如下代码是打开文件读取数据部分代码。
void OpenFile(const string fileName,list<Word> &lWord) { //list<Word> lWord; int paragraphNum = 1; int sentenceNum = 1 ; int wordNum = 1; ifstream fin(fileName.c_str()); if(!fin) { cout<<"File open error!\n"; return; } char ch; while(fin.get(ch)) { if(ch=='\n') { paragraphNum++; sentenceNum=1; wordNum=1; } else { char temp[50]; int icount=0; while((ch !='\n')&&(ch !='.')&&(ch !='!')&&(ch !='?')&&(ch !=' ')&&(ch !=',')&&(ch !='、')) { temp[icount]=ch; icount++; fin.get(ch); } temp[icount]='\0'; if(icount>=1) { lWord.push_back(Word(temp,paragraphNum,sentenceNum,wordNum)); } if((ch=='.')||(ch=='!')||(ch=='?')) { sentenceNum++; wordNum=0; } if((ch==' ')||(ch==';')||(ch=='、')) { wordNum++; } } }
如下是我的排序功能,我将单词和它出现的次数组成一个结构体,在第一次打印的时候进行存储。使用了结构体排序方法进行结构体排序,最后输出结构体中数量最高的几个单词,代码展示如下:
1 typedef struct 2 { 3 string danci;//储存单词 4 int count;//记录单词个数,后面出现几次 5 } sq;
排序部分功能如下:
bool compare(sq a, sq b) { return (a.count < b.count); } //调用时的语句 sort(word_count, word_count + total-1 , compare); cout << "total " << total << " words" << endl; for (int i = total; i >total-10; i--) cout << left << setw(10) << word_count[i].danci << right << setw(5) << ' ' << word_count[i].count << endl;
下图展示为《war and peace》部分章节内容统计。具体因为什么内容报错不可排序全部单词目前还不明确。
总结:
1三号和四号任务没有完成,其实大部分时间消耗在了学习新知识上面。
2课余活动少参加,现在已经是研究生了,就少玩一点。
3对于以前欠下的债,从现在开始补总比不补强太多。
4会提问才是会学习,我总是没有周围的同学会问,他们总能描述清楚想问什么,而我总是不知道如何张嘴。
5尽量别累计到一起完成任务,不然太多。
软件PSP分析
时间分析:我觉得可以自己可以接受找到代码,然后成功运行(而不是全部重写)这样的情况。所以自己找到的原始代码的好坏,或原始版本的好坏就很大程度上影响了进度。哪怕是参考也会有很大影像。如果使用第一晚的,那可能一直都不能使用大文件,而目前的版本中,排序这部分内容就得新添加。但是目前版本又遇到了不可读参数的问题。我觉得这个软件只要吧词语找到,后续的功能越做越快。具体原因呢,就是有些没有遇到的情况,会花大量的时间去修改。
代码及版本控制
git地址:https://git.coding.net/Rio56/wf.git
(该版本生成的exe可在根目录下读取相应的txt文件。)
补交作业部分:第二项任务和第三项任务
第一项任务的完善
这次是真真实实的从命令台中读取了数据,并且进行词汇统计,消除了大小写的问题。
代码如下。因为使用了“int _tmain(int argc, _TCHAR* argv[])”所以消耗了更多的时间。其中我突然想到了“wstring”这种数据格式。让我迅速的完成了从_TCHAR*到wchar,wchar到wstring,再从wstring到string的转换。
感谢高远博同学的帮助,虽然我没使用main(),但是你提示了我很重要的一点:找bug就是找不同。
wchar的使用是上周就遇到的问题,我因为作业中没有用到就没有记录。而这个没有记录的小知识点正是本次解决问题的起始点。这让我知道:每一次错误的尝试都有被记录的资格!
int _tmain(int argc, _TCHAR* argv[]) { //打开某一目录下的txt文件,对其进行读操作 list<Word> lWord; string fileName; wstring w_fileName; w_fileName = argv[2]; fileName = WStringToString(w_fileName); OpenFile(fileName, lWord); Display_for_softwareclass(lWord); system("PAUSE"); return 0; }
第二项任务的截图:
第三项任务截图:
测试用例中第一篇文章采用网络上一篇英语作文,实现了读取文件夹下所有txt文件,并统计其中词频的工作。
如下粘贴部分有关如何读取文件夹中txt文本文件的代码。这部分代码中,我首先获取文件夹内所有文件名称,再获取文件名后四位的字符,与“.txt”作比较,从而获取txt文件。
1 void getFiles(string path, vector<string>& files) 2 { 3 //文件句柄 4 long hFile = 0; 5 //文件信息 6 struct _finddata_t fileinfo; 7 string p;//定义了一个字符串 8 char *q; 9 string last_4; 10 _finddata_t sFind; 11 long lResult = 0; 12 if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1) 13 { 14 do 15 { 16 if ((fileinfo.attrib & _A_SUBDIR))//_A_SUBDIR(文件夹)如果是文件夹的话 17 { 18 //strcmp字符串比较 19 //.表示当前目录 ..表示当前目录的父目录。 20 if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0){ 21 getFiles(p.assign(path).append("\\").append(fileinfo.name), files); 22 } 23 else 24 { 25 files.push_back(p.assign(path).append("\\").append(fileinfo.name)); 26 } 27 } 28 if ((fileinfo.attrib & _A_SUBDIR)) 29 { 30 if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0) 31 getFiles(p.assign(path).append("\\").append(fileinfo.name), files); 32 } 33 else 34 { 35 last_4 = substring(fileinfo.name, 4); 36 if (strcmp(substring(fileinfo.name, 4), ".txt") == 0){ 37 files.push_back(p.assign(path).append("\\").append(fileinfo.name)); 38 } 39 } 40 } while (_findnext(hFile, &fileinfo) == 0); 41 42 _findclose(hFile); 43 } 44 }
如下代码用于截取后四位字符串。
1 char *substring(char str[], int n) { 2 char *strT = (char *)malloc(sizeof(char)* (n + 1)); 3 4 int len = strlen(str); 5 int i; 6 for (int i = 0; i < n; i++) { 7 strT[i] = str[len - n + i]; 8 } 9 strT[4] = '\0'; 10 return strT; 11 }
2017-9-26修改:
1完成了重定向功能!
2完成了直接命令行输入功能!
3完成了输入文件名字功能!
如下展示截图和代码情况。
此图展示直接粘贴部分段落统计词语截图
此图展示使用命令行输入“-s war_and_peace.txt”功能,读取文件中内容。(使用我的程序读取文件)
此图展示“重定向功能”通过“<test.txt”命令行读取test.txt文件中的内容。
如上,所有功能已经全部实现。感谢高远博同学多次对我的指点与教学 。
发现小的技巧:如果文件路径中包含“空格”会导致程序出现错误。相对路径中存在空格也是不可以的!!!