第一次個人作業【四】(代碼編寫、調試、debug相關)
代碼編寫過程中的重要知識點
VS調試命令行參數的輸入
在VS中調試,無法直接輸入命令行參數,但是可以通過一下方法配置命令行參數:
- 點擊菜單欄的 項目>>屬性
- 出現屬性對話框之後,選擇 配置屬性>>調試>>命令參數:
- 在裏面設置main的參數即可,多個參數用空格隔開。
String相關
erase操作
在對字符串的處理過程中需要除去字符串最後的數字,這裏利用了erase來進行操作:
erase操作有三種:
- 指定pos和len,其中pos為為起始位置,pos以及後面len-1個字符串都刪除
- 叠代器,刪除叠代器指向的字符
- 叠代器範圍,刪除這一範圍的字符串,範圍左閉右開
//代碼來自cpp官網 int main () { std::string str ("This is an example sentence."); std::cout << str << ‘\n‘; // "This is an example sentence." str.erase (10,8); // ^^^^^^^^ //直接指定刪除的字符串位置第十個後面的8個字符 std::cout << str << ‘\n‘;// "This is an sentence." str.erase (str.begin()+9);// ^ //刪除叠代器指向的字符 std::cout << str << ‘\n‘; // "This is a sentence." // ^^^^^ str.erase (str.begin()+5, str.end()-9); //刪除叠代器範圍的字符 std::cout << str << ‘\n‘; // "This sentence." return 0; }
在我的代碼中,在 Handlestring() 函數中,通過從後往前遍歷得到最後一個字母的位置i,利用第三種方法完成刪除操作:
s.erase(s.begin() + i + 1, s.end());
怎麽將一個 char 添加到 std::string 後面?
直接用append方法的話需要先將char轉換成string,比較麻煩;實際上可以通過直接使用 += 運算符來完成這個功能:
s += c;
大小寫轉換
C++中沒有string直接轉換大小寫的函數,需要自己實現。一般來講,可以用stl的algorithm實現:
#include <string> #include <algorithm> int main() { string s = "ddkfjsldjl"; transform(s.begin(), s.end(), s.begin(), tolower); // or transform(s.begin(), s.end(), s.begin(), toupper); if you want to transform to Upper cout<<s<<endl; return 0; }
但是在使用g++編譯時會報錯:
對‘transform(__gnu_cxx::__normal_iterator<char*, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, __gnu_cxx::__normal_iterator<char*, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, __gnu_cxx::__normal_iterator<char*, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, <unresolved overloaded function type>)’ 的調用沒有匹配的函數。
這裏出現錯誤的原因是Linux將toupper實現為一個宏而不是函數,這裏由兩種解決方案:
-
transform(str.begin(), str.end(), str.begin(), (int (*)(int))toupper);
這裏(int (*)(int))toupper是將toupper轉換為一個返回值為int,參數只有一個int的函數指針。
- 自己實現ToUpper函數:
int ToUpper(int c) { return toupper(c); } transform(str.begin(), str.end(), str.begin(), ToUpper);
Map相關
unordered_map
最初我的代碼使用的是普通的map,後來改成了unordered_map,主要有以下原因:
unordered_map和map類似,都是存儲的key-value的值,可以通過key快速索引到value。
但不同的是unordered_map不會根據key的大小進行排序,存儲時是根據key的hash值判斷元素是否相同,即unordered_map內部元素是無序的,而map中的元素是按照二叉搜索樹存儲,進行中序遍歷會得到有序遍歷。
而對我們的任務來說,使用map的主要原因是方便根據key來找到相應的value,(這裏的key就是標準化後的word,而value根據不同的map有差別,什麽是標準化在前一篇博客中有寫),對順序沒有要求,所以使用unordered_map可以免去排序的麻煩,實踐證明這也大大提高了程序的運行速度。
查看當前是否包含key
這裏我只想完成一個功能,就是:我得到一個特定的key,先查看在map中是否有該key的key-value對了,如果有我根據當前的value來做相應操作,如果沒有則給他一個默認的value並加入map中。
map提供了兩種方式,來完成這個功能,查看是否包含key,m.count(key),m.find(key)。
m.count(key):由於map不包含重復的key,因此m.count(key)取值為0,或者1,表示是否包含。
m.find(key):返回叠代器,判斷是否存在。
這裏使用了第一個,因為比起第二個要快一些
一個key對應多個value
其實我最初的設想就是利用一個map,來完成次數統計和表達式更新的,但是奈何默認的 map[key]++ 方法只對 map<string, int>型的map適用,所以不得已將一個map分為了兩個,一個存儲頻率數,一個存儲最小表達式,所幸對性能影響不大。
unordered_map<string, int> words_map; //用來記錄word出現次數的hash表 unordered_map<string, int> phrase_map; //用來記錄詞組出現次數的hash表 unordered_map<string, string> exp_map; //用來記錄單詞對應的最小表達式的hash表
vector相關
查找一個vector中的最大值、最小值與index
利用max_element,min_element,distance
int main() { std::vector<double> v {1.0, 2.0, 3.0, 4.0, 5.0, 1.0, 2.0, 3.0, 4.0, 5.0}; std::vector<double>::iterator biggest = std::max_element(std::begin(v), std::end(v)); std::cout << "Max element is " << *biggest<< " at position " << std::distance(std::begin(v), biggest) << std::endl; auto smallest = std::min_element(std::begin(v), std::end(v)); std::cout << "min element is " << *smallest<< " at position " << std::distance(std::begin(v), smallest) << std::endl; }
輸出:
Max element is 5 at position 4 min element is 1 at position 0
最初想用它來算TOP10,但是後來想了想還是自己寫了個算法
debug總結
這裏大致總結了一些遇到的一些比較關鍵的bug及其排查解決過程
低級錯誤
在寫代碼調試的過程中犯了不少低級錯誤,還好最後都基本排查出來了,但是真的讓人感到不快,例如:
在判斷是否是字母這個邏輯中:
c >= ‘a‘ && c <= ‘z‘
竟然將 ‘z‘ 手誤寫成了 ‘b‘,導致了進一步的例如指針越界之類的錯誤,又因為這個錯誤很小導致排查了半天。
遞歸讀取文件問題
之前寫的遞歸讀取文件夾下所有文件在測試集上運行良好,但是這些都是我以文件加路徑作為輸入完成的。在一次測試過程中我只輸入了一個文件的路徑,結果竟然不能識別,於是排查那個遍歷文件的函數,發現當初寫的時候沒能考慮用戶輸入是單個文件的情況,於是及時對該函數做了更改。
void GetAllFiles(string path, vector<string>& files) { long hFile = 0; struct _finddata_t fileinfo; string p; files.push_back(path); //加上了這一句 hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo); if (hFile != -1) { do { if ((fileinfo.attrib & _A_SUBDIR)) { if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0) { files.push_back(p.assign(path).append("\\").append(fileinfo.name)); GetAllFiles(p.assign(path).append("\\").append(fileinfo.name), files); } } else { files.push_back(p.assign(path).append("\\").append(fileinfo.name)); } } while (_findnext(hFile, &fileinfo) == 0); _findclose(hFile); } }
移植問題
這個問題在我之前的一篇博客中做了詳細介紹:
第一次個人作業【二】(代碼跨平臺的簡單解決方法,附代碼!!)
中文字符串亂碼
早先在做測試時,並沒有使用命令行傳遞參數,而是將路徑直接寫在了程序中,但是出現了一個問題:只要路徑中含有中文字符,文件就無法讀取。
最開始我以為是選用的庫文件不支持中文字符集。網上搜索,將所有的辦法試了一遍都不太行,當時差點崩潰。後來還是老老實實設置斷點debug才發現寫在程序中的中文路徑編譯後產生了亂碼,自然就無法定位文件。之後順藤摸瓜才發現是VS2015編輯器的鍋,VS2015使用的編譯器是Roslyn,而這個問題與Roslyn檢測文件編碼的處理方式有關。真是坑別的版本都沒有的問題,偏偏被我遇到了。
發現問題後趕緊換成了用命令行傳參,問題得到解決。
單詞個數統計誤差太大
之前統計的單詞個數與助教答案相差有10萬之多,怎麽debug都無法找到原因,後來更改了字符串處理的邏輯,將誤差減小到1萬左右。
文件的二進制方式讀取與文本方式讀取
這裏也是個天坑,最初我讀取文件是用的二進制方式,但結果與助教結果差別太大,後來明白是由於在Windows下文件編碼格式的問題,在Linux下就不存在了,例如 \n -> \r\n 這個問題。
後來改成文本方式讀取,算是成功解決。
第一次個人作業【四】(代碼編寫、調試、debug相關)