要求0:作業要求位址:https://edu.cnblogs.com/campus/nenu/2016CS/homework/2110
要求1:GIT倉庫位址:https://git.coding.net/wudb527/wf.git (master2為中間的過程,V1.0為最終送出的版本,随後對V1.0和主分支master進行了合并)
要求2:PSP階段表格
SP2.1 | 預估時間600(min) | 實際開發時間1043(min) |
計劃 | 20 | 40 |
·明确需求和其他相關因素,估計每段時間成本 | ||
開發 | 490 | 903 |
·需求分析 | 60 | |
·生成設計文檔 | 30 | |
·設計複審(和同學稽核設計文檔) | ||
·代碼規範(為目前的開發制定合适的規範) | 10 | |
·具體設計 | 50 | |
·具體編碼 | 300 | 673 |
·代碼複審 | ||
·測試(自測、修改代碼、送出修改) | ||
報告 | 90 | 100 |
·測試報告 | 35 | |
·計算工作量 | ||
·事後總結,并提出過程改進計劃 |
功能子產品 | 具體階段 | 預計時間(min) | 實際時間(min) |
功能1 | 具體設計 | 15 | |
具體編碼 | 130 | ||
測試完善 | 12 | ||
功能2 | |||
70 | 246 | ||
13 | |||
功能三 | |||
180 | 290 | ||
分析差距原因:
(1):沒有完全按照軟體開發的流程來,比如因為感覺是小程式,設計文檔就沒有做,雖然這一項我沒有花時間,但是實際中我卻在其他地方饒了彎路。
(2):我對C++的文法沒有足夠的熟悉,有些資料類型的内置函數不夠了解(比如string.c_str()),導緻在應用的時候還要上網查詢。
(3):在之前沒有對題目要求做足夠的了解,導緻,一邊寫代碼還要傳回去看題目要求,花費了不少時間。
要求3:
1.解題思路描述
我覺得這個程式的流程大概就是讀取檔案獲得字元串,然後對字元串進行操作獲得符合題目要要求的字元,而後進行字元串計數,然後按照題目要求輸出就可以了。當第一眼看到這個題目的時候我覺得難點在打開檔案、路徑輸入儲存以及進行檔案夾下的檔案查詢,而重點點我覺得有正規表達式的應用。我一開始就打算用C++裡STL裡面的map容器來實作字元串的計數。但是檔案的打開以及檔案夾下面的檔案讀取卡了不少時間,本來打算用C的fopen實作,但是fopen打開檔案後獲得是字元數組,我打算用string類型儲存的單詞的,是以還要把字元數組轉為字元串,有點繁瑣,後來我查找資料知道了C++的檔案流,這是一大突破,而在檔案夾内部的檔案周遊上我查資料知道了 _findfirst()函數可以實作問價的查詢。
2.代碼解釋
難點1:檔案的打開
用檔案流的話,我們使用流提取運算符( >> )從檔案讀取資訊,就像使用該運算符從鍵盤輸入資訊一樣。唯一不同的是,在這裡您使用的是 ifstream 或 fstream 對象,而不是 cin 對象。
下面是檔案流的使用方法,用于對某一個檔案進行讀取然後輸出。
1 #include<iostream>
2 #include<fstream>
3 using namespace std;
4 int main()
5 {
6 // 以讀模式打開檔案
7 ifstream infile;
8 string path = "D:\\codeblocks\\wf\\input.txt";
9 //打開檔案
10 infile.open(path.c_str());
11 //括号裡應該是字元串類型,是以要用到string.c_str()進行轉換
12 string str;
13 while(infile>>str)
14 {
15 cout<<str<<endl;
16 }
17 return 0;
18 }
難點2:路徑内檔案夾的周遊,檔案的查找
_findfirst是按照檔案名的字典序查找的,是以題目要求的檔案名排序問題直接解決了,隻要能找到檔案那就是符合要求的。
這兩個函數均在io.h裡面。
首先了解一下一個檔案結構體:
struct _finddata_t {
unsigned attrib;
time_t time_create;
time_t time_access;
time_t time_write;
_fsize_t size;
char name[260];
};
time_t,其實就是long
而_fsize_t,就是unsigned long
現在來解釋一下結構體的資料成員吧。
attrib,就是所查找檔案的屬性:_A_ARCH(存檔)、_A_HIDDEN(隐藏)、_A_NORMAL(正常)、_A_RDONLY(隻讀)、_A_SUBDIR(檔案夾)、_A_SYSTEM(系統)。
time_create、time_access和time_write分别是建立檔案的時間、最後一次通路檔案的時間和檔案最後被修改的時間。
size:檔案大小
name:檔案名。
再來看一下_findfirst函數:long _findfirst(const char *, struct _finddata_t *);
第一個參數為檔案名,可以用"*.*"來查找所有檔案,也可以用"*.cpp"來查找.cpp檔案。第二個參數是_finddata_t結構體指針。若查找成功,傳回檔案句柄,若失敗,傳回-1。
然後,_findnext函數:int _findnext(long, struct _finddata_t *);
第一個參數為檔案句柄,第二個參數同樣為_finddata_t結構體指針。若查找成功,傳回0,失敗傳回-1。
最後:_findclose()函數:int _findclose(long);
隻有一個參數,檔案句柄。若關閉成功傳回0,失敗傳回-1。
資料源自:http://blog.sina.com.cn/s/blog_67e046d10100jwdo.html
下面是小的使用方法:
1 #include<iostream>
2 #include<io.h>//_findfirst的頭檔案
3 using namespace std;
4 int main()
5 {
6 string path = "D:\\codeblocks\\wf\\";
7 path += "*.txt";//.txt是檔案類型
8 _finddata_t openfile;
9 long HANDLE;//_findfirst的傳回值,若找到為0,找不到為-1
10 HANDLE = _findfirst( path.c_str(), &openfile );
11 string file_name = openfile.name;//openfile的name成員是找到的檔案名
12 cout<<file_name<<endl;
13 return 0;
14 }
難點3:把一行字元串按照空格分割開來
在這裡我想到了stringstream來進行資料流的重定向。下面是我對stringstream的測試:
#include<iostream>
#include<sstream>//stringstream的頭檔案
using namespace std;
int main()
{
string str = "aaa bbb ccc ddd eee fff";
stringstream ss(str);
string t;
while(ss>>t)
cout<<t<<endl;
return 0;
}
輸出為:
可以看到實作了用空格分割字元串。
要點1:由于功能1在輸出時要求與輸入時的順序一緻,是以我用一個vector<string>依次儲存了輸入時的字元串。在輸出時隻要依次取出即可。
1 vector<string>Vstr;
2 map<string,int>Map;
3 int Max_wordsize = 0;
4 while(ss>>str)
5 {
6 if(str[0] >= '0' && str[0] <= '9') continue;
7 Max_wordsize = Max(Max_wordsize,str.size());//尋找最長單詞的長度
8 if(Map[str] == 0) Vstr.push_back(str);//把每一個單詞不重複的儲存起來
9 Map[str]++;
10 }
要點2:功能2在輸出時要求字元串按照字典序輸出,是以要對vector進行排序,我自定義了cmp函數。
1 bool cmp2(string a, string b)
2 {
3 return a < b;
4 }
sort(Vstr.begin(),Vstr.end(),cmp2);
要點3:在功能3輸出時,要求先按照單詞數量降序排序,對于數量一緻的再按照單詞的字典序升序排序,是以我建構了結構體,裡面有成員str和num,然後在功能3中把每個結構踢對象存入vector中,然後自定義排序函數。
struct Node
{
string str;
int num;
};
1 for(int i = 0; i < total;++i)
2 {
3 node.str = Vstr[i];
4 node.num = Map[Vstr[i]];
5 V_node.push_back(node);
6 }
vector<Node>V_node;
/////////////
bool cmp3(Node a, Node b)
{
if(a.num == b.num) return a.str < b.str;
return a.num > b.num;
}
/////////////////////////
sort(V_node.begin(),V_node.end(),cmp3);
(1)下面是我的完整代碼,對功能1,功能2,功能3的判别是根據控制台輸入的字元串個數,如果是5段那肯定是功能3,如果不是那就是功能1或功能2,然後查詢是有-c還是-f即可判斷。
1 #include<iostream>
2 #include<map>
3 #include<vector>
4 #include<sstream>
5 #include<string>
6 #include<fstream>
7 #include<cstring>
8 #include<cstdio>
9 #include<cstdlib>
10 #include<algorithm>
11 #include<io.h>
12 using namespace std;
13 int Max(int a,int b)
14 {
15 return a > b ? a : b;
16 }
17 int Min(int a,int b)
18 {
19 return a < b ? a : b;
20 }
21 struct Node
22 {
23 string str;
24 int num;
25 };
26 bool Get_FileName(string Path_Name,string &File_Name)
27 {
28 struct _finddata_t FileInfo;
29 long Handle;
30 Handle = _findfirst((Path_Name+"\\*.txt").c_str(),&FileInfo);//對檔案夾下的檔案篩選出.txt檔案
31 if(Handle == -1) return 0;
32 else
33 {
34 string temp = FileInfo.name;
35 File_Name = Path_Name + "\\" + temp;
36 return true;
37 }
38 }
39 bool cmp2(string a, string b)
40 {
41 return a < b;
42 }
43 bool cmp3(Node a, Node b)
44 {
45 if(a.num == b.num) return a.str < b.str;
46 return a.num > b.num;
47 }
48 void solve(string File_Name,int num)//實際執行檔案打開和輸出的函數,num = 5為功能2,num= -1 為功能1,num= -2為功能2
49 {
50 ifstream infile;
51 string file_txt = "",str;
52 infile.open(File_Name.c_str());//打開檔案
53 while(getline(infile,str))
54 {
55 file_txt = file_txt + str + ' ';
56 }
57 infile.close();
58 for(string::iterator it = file_txt.begin();it < file_txt.end();it++)//把大寫轉為小寫,并且把非字母數字轉為空格
59 {
60 if(*it >='A'&&*it<='Z') *it += 32;
61 else if(*it >='a' && *it <='z') continue;
62 else if(*it >='0' && *it <='9') continue;
63 else *it = ' ';
64 }
65 stringstream ss(file_txt);
66 vector<string>Vstr;
67 map<string,int>Map;
68 int Max_wordsize = 0;
69 while(ss>>str)
70 {
71 if(str[0] >= '0' && str[0] <= '9') continue;
72 Max_wordsize = Max(Max_wordsize,str.size());//尋找最長單詞的長度
73 if(Map[str] == 0) Vstr.push_back(str);//把每一個單詞不重複的儲存起來
74 Map[str]++;
75 }
76 int total = Vstr.size();
77 if(num == -1)//功能1
78 {
79 cout<<"total"<<" "<<total<<endl;
80 cout<<endl;
81 for(int i = 0;i < total;i++)
82 {
83 cout<<Vstr[i];
84 int wordsize = Vstr[i].size();
85 for(int j = 1; j <= Max_wordsize - wordsize;++j) cout<<" ";
86 cout<<" "<<Map[Vstr[i]]<<endl;
87 }
88 }
89 else if(num == -2)//功能2
90 {
91 cout<<"total"<<" "<<total<<" word";
92 if(total > 1) cout<<'s';
93 cout<<endl;
94 sort(Vstr.begin(),Vstr.end(),cmp2);
95 for(int i = 0;i < total;i++)
96 {
97 cout<<Vstr[i];
98 int wordsize = Vstr[i].size();
99 for(int j = 1; j <= Max_wordsize - wordsize;++j) cout<<" ";
100 cout<<" "<<Map[Vstr[i]]<<endl;
101 }
102 }
103 else//功能3
104 {
105 Max_wordsize = 0;
106 cout<<"Total word";
107 if(total > 1) cout<<'s';
108 cout<<" is "<<total<<endl;
109 cout<<"----------"<<endl;
110 vector<Node>V_node;
111 Node node;
112 for(int i = 0; i < total;++i)
113 {
114 node.str = Vstr[i];
115 node.num = Map[Vstr[i]];
116 V_node.push_back(node);
117 }
118 sort(V_node.begin(),V_node.end(),cmp3);
119 num = Min(num,total);
120 for(int i = 0; i < num;++i)
121 {
122 Max_wordsize = Max(V_node[i].str.size(),Max_wordsize);
123 }
124 for(int i = 0;i < num;++i)
125 {
126 cout<<V_node[i].str;
127 int wordsize = V_node[i].str.size();
128 for(int j = 1; j <= Max_wordsize - wordsize;++j) cout<<" ";
129 cout<<" "<<V_node[i].num<<endl;
130 }
131 }
132
133 }
134 int main()
135 {
136 string str_input,Path_Name,File_Name;//控制台輸入的内容,實際路徑,實際檔案名
137 bool havePath = false,haveFile = false;//判斷是有路徑還是有檔案名
138 getline(cin,str_input);
139 vector<string>input;
140 stringstream ss(str_input);//對控制台輸入的文字按照空格分割
141 string str;
142 while(ss>>str)
143 {
144 if(str == "-c") haveFile = true;//判斷是否有檔案名
145 if(str == "-f") havePath = true;//判斷是否有判斷是否有路徑
146 input.push_back(str);
147 }
148 int len = input.size();
149 if(havePath)//如果存在的是路徑
150 {
151
152 for(int i = 0;i < len;i++)
153 {
154 if(input[i] == "-f") {Path_Name = input[i+1];break;}
155 }
156 string temp = "";
157 //因為C++'\'的轉義
158 for(string::iterator it = Path_Name.begin();it < Path_Name.end();it++)
159 {
160 if(*it == '\\') temp += "\\";
161 else temp+=*it;
162 }
163 Path_Name = temp;
164 bool is = Get_FileName(Path_Name,File_Name);//擷取檔案名
165 if(is == false) {cout<<"路徑下找不到.txt檔案!"<<endl;return 0;}//擷取失敗,輸出
166 }
167 else
168 {
169 for(int i = 0;i < len;i++)
170 {
171 if(input[i] == "-c") {File_Name = input[i+1];break;}
172 }
173 }
174 int num;
175 if(len == 5)
176 {
177 for(int i = 0;i < len ;i++)
178 if(input[i] == "-n")
179 num = atoi(input[i+1].c_str());
180 }
181 else
182 {
183 if(str_input.find("-c") != string::npos) num = -1;
184 else num = -2;
185 }
186 solve(File_Name,num);
187 return 0;
188 }
189 /*
190 wf -f D:\codeblocks\wf
191 wf -f D:\codeblocks\wf -n 3
192 */
(2)功能測試:注:功能3四種輸入均進行了測試
功能1:
功能2:
功能3:
對四種輸入均進行了測試,均能實作
(3)自認為題目中的小細節
1)在輸出時單詞 word 後面有沒有s,我注意到了這個問題,是以我對單詞進行了計數,若大于1就加s。
if(total > 1) cout<<'s';
2)對單詞數一列的對齊,我用一個變量維護了最大單詞長度,然後在輸出時對于其他單詞,長度比他小幾個我就補幾個空格,這樣就實作了對齊。
1 int Max_wordsize = 0;
2 while(ss>>str)
3 {
4 if(str[0] >= '0' && str[0] <= '9') continue;
5 Max_wordsize = Max(Max_wordsize,str.size());//尋找最長單詞的長度
6 if(Map[str] == 0) Vstr.push_back(str);//把每一個單詞不重複的儲存起來
7 Map[str]++;
8 }
輸出時
1 if(num == -1)//功能1
2 {
3 cout<<"total"<<" "<<total<<endl;
4 cout<<endl;
5 for(int i = 0;i < total;i++)
6 {
7 cout<<Vstr[i];
8 int wordsize = Vstr[i].size();
9 for(int j = 1; j <= Max_wordsize - wordsize;++j) cout<<" ";//補充空格
10 cout<<" "<<Map[Vstr[i]]<<endl;
11 }
12 }
(4)對于這次作業我自己的心得:
1)在這次作業的過程中我對自己比較滿意的是在正式編碼前将 檔案打開、指定路徑下檔案查詢、怎樣用空格分開字元串,其實在這裡面花費時間是最大的,我慶幸我這麼做了,如果一開始就正式編碼、遇到困難然後去搜尋,那麼可能你已經寫了很多了,然後如果對新的用法不熟悉,編譯會報錯,debug會很困難,而我在編碼前就通過小實驗熟悉了操作,是以在正式編碼時debug幾乎沒有花費多少時間,這算我的一個收獲吧!雖然這次我沒有犯這個錯誤但我還是要打好預防針。
2)而不足就是編碼前沒有足夠了解問題,導緻頻繁去看問題,浪費了不少時間。就建構之法中的軟體開發流程而言就不夠合格,這次讓我意識到麻雀雖小但也五髒俱全,小的軟體項目也應該按照流程走,這樣表面上花費了時間,但是在總體來看是節省不少時間。
3)GIT并沒有那麼簡單,本以為隻要了解了那幾個語句就可以了,但是在push是發生了不少錯誤,查了不少資料才解決。其實C++等語言學習也是一樣的,那些文法學習其實是最基礎的,然後就是怎樣進行組合會什麼功能,在更新就是對于各種報錯的解決,這一點是沒有捷徑的,隻有長時間的練習才能解決,沒有學習是一蹴而就的。