zoukankan      html  css  js  c++  java
  • 《C++之那些年踩过的坑(三)》

    C++之那些年踩过的(三)

    作者:刘俊延(Alinshans)

    本系列文章针对我在写C++代码的过程中,尤其是做自己的项目时,踩过的各种坑。以此作为给自己的警惕。


    【版权声明】转载请注明原文来自:http://www.cnblogs.com/GodA/p/6569254.html

    前言:

      如果你看了我的上一篇博客:《C++之那些年踩过的坑(二)》 我推荐你再看一遍,因为我对内容和排版做了一些修改,尤其是对说的不当的地方进行了修正。

      最近挺忙,这篇存了好久,还没发出去,讲多了一下写不完。所以今天就随便聊点简单的东西——unsigned type

    一、unsigned type 会有什么坑?

      你看到这篇的开头,就可能会想,无符号类型能有什么坑呀!那我们就直接了当一些吧!

    1、小心可能陷入的死循环

      其实单独的 unsigned type 你还是比较容易想明白的,可怕的就是它跟一些其它东西配合(auto),而你忽略了那就是 unsigned type 的时候,例如:

    #include <iostream>
    #include <cstdlib>
    
    int main()
    {
      char sz[] = "Hello world!";
      for (auto i = strlen(sz) - 1; i >= 0; --i)
      {
        std::cout << sz[i];
      }
    }

      上述代码逆序输出字符串,有什么问题?一部分人一眼看出了问题,相当一部分人一眼没看出问题,多看两遍终于看出了问题,还有一小部分人到现在还没看出问题。

      问题就在于 strlen 返回的类型是 size_t ,它是一个无符号类型,而一个无符号类型永远大于等于 0!是的没错,死循环了。如果你用的是 vs 这种有 intelliSense 的IDE,那还好,鼠标移过去,你能够看到函数的声明。那万一没有这东西呢?又或者,有一些你不认识的函数,那些类型已经被 typedef 到你认不出了,你又如何辨别出来呢?上述的例子非常非常简单,但难免在实际中有可能会出现更复杂的例子,然后你头晕眼花就写出了这样的代码。所以,当你用一个数值作为循环条件,你就要警惕一下,数值表示的范围是什么,循环终止的条件,边界等等。当然,unsigned type 是更容易被忽视的,所以出错的概率更大。

    2、小心可能的访问越界

      其实上面的例子也是属于访问越界,但C++的数组兼容C,C的数组是不作边界检查的,所以你写出 sz[-1] 这样的代码也不一定会错。所以还是推荐使用C++标准库的容器,对于日常使用,绰绰有余。C++的容器,如果越界了,他就会有提示(Debug模式),比如用 vs 的话,在Debug模式下,越界了他就会有弹出一个对话框显示“xxx subscript out of range”之类的信息。

    这个的错法跟上面的其实也差不多,也是写出了类似这样的代码:

    std::vector<int> v{ 1,2,3,4,5 };
    for (auto i = v.size() - 1; i >= 0; --i)
    {
      std::cout << v[i] << " ";
    }

      之前我写一个 BigInteger 类的时候,也有很多用到这样的倒序输出,一开始我也是这样写,然后一直错。然后我就想肯定是循环下标出了问题的,然后就按 Ctrl+F 查找 for,还好也不是很多,一个一个看,最终才醒悟过来。真是屡踩坑不改~~所以才要记下来让自己印象深刻,下次即使出错也能很快反应过来是错在了什么地方。

    二、解决方案

      这个 unsigned type 的坑比较简单,主要在 0 这个点容易出错,有这个意识就好了。解决方案(至少)有以下几种:

    1、显式指定可以容纳范围的 signed 类型

      如果你知道数值的确切范围了,如果可以有一个 signed 类型能容纳它,那么就显式指定出来,比如我知道 vector 最大大小不会超过一百万个,那么我就可以用:

    for (int32_t i = v.size() - 1; i >= 0; --i)

    这样来指定类型。

    2、在循环内部做检查

      如果你不能确定大小,而且不确定 signed type 能否容纳下那个 unsigned type 的全部范围,那就在循环内部做一个检查:

     

      if (v.size() != 0)
      {
        for (auto i = v.size() - 1; i >= 0; --i)
        {
          std::cout << v[i] << " ";
          if (i == 0)
            break;
        }
      }

     

    3、把边界情况移到循环外部

      其实是第2种方法的变形,如果你觉得检查下标浪费了很多性能,那么就可以这样做:

     

      if (v.size() != 0)
      {
        for (auto i = v.size() - 1; i > 0; --i)
        {
          std::cout << v[i] << " ";
        }
        std::cout << v[0];
      }

     

    4、使用迭代器

      既然用了C++的容器那么更好的写法当然是这样啦:

      for (auto i = v.rbegin(); i != v.rend(); ++i)
      {
        std::cout << *i << " ";
      }

      这样就不需要你去考虑下标范围啦,什么检查啦,多方便!如果想写出更泛型,更 C++ Style 的代码,还可以这样:

      std::for_each(std::rbegin(v), std::rend(v), [&](int i) {cout << i << " "; });

    最终选择哪种就看实际情况和个人爱好咯!

    三、其它杂谈

    我在网上看到不少关于什么“代码优化技巧”等等文章,即使是最近出的,还是这样写,不知道是不是抄的。比如我看到其中一篇就说到:

    有些处理器处理无符号unsigned 整形数的效率远远高于有符号signed整形数

    正确与否我先不说了,错别字我也不去说了。留给读者先自己实验一下,我会在下一篇中用一篇来讲这类问题。

     

    四、总结

    标题即总结。

  • 相关阅读:
    Maven--反应堆(Reactor)
    Maven--超级 POM
    Maven--插件管理
    解决非模态对话框第二次创建失败问题
    【转】VerQueryValue失败的解决办法
    【转】SYSTEM_HANDLE_INFORMATION
    安全版字符串操作函数
    int转string的3种方法
    PE格式详细讲解3
    PE格式详细讲解2
  • 原文地址:https://www.cnblogs.com/GodA/p/6569254.html
Copyright © 2011-2022 走看看