1. 程式人生 > >第一次個人作業【四】(代碼編寫、調試、debug相關)

第一次個人作業【四】(代碼編寫、調試、debug相關)

pre 運行速度 快速 hfile 配置 osi 命令 字符 最大值

代碼編寫過程中的重要知識點

VS調試命令行參數的輸入

在VS中調試,無法直接輸入命令行參數,但是可以通過一下方法配置命令行參數:

  1. 點擊菜單欄的 項目>>屬性
  2. 出現屬性對話框之後,選擇 配置屬性>>調試>>命令參數:
  3. 在裏面設置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實現為一個宏而不是函數,這裏由兩種解決方案:

  1. transform(str.begin(), str.end(), str.begin(), (int (*)(int))toupper);

    這裏(int (*)(int))toupper是將toupper轉換為一個返回值為int,參數只有一個int的函數指針。

  2. 自己實現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相關)