第一次个人作业(代码编写调试debug相关)

Posted franzkfk

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第一次个人作业(代码编写调试debug相关)相关的知识,希望对你有一定的参考价值。

代码编写过程中的重要知识点

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相关)的主要内容,如果未能解决你的问题,请参考以下文章

第一次个人作业(心得经验)

python debug怎么用

pycharm _debug单步调试

个人作业3——个人总结(Alpha阶段)

个人作业2————英语学习APP的案例分析

闽江学院2015-2016学年下学期《软件测试》课程-第二次作业(个人作业)第一题