这一篇会讲一个很小的点,但又经常容易犯错 ¯_(ツ)_/¯ ———— unsigned type
。
一、unsigned type 的坑
看到这篇的开头,你可能就会想,unsigned type
能有什么坑的呀!那我们就直接了当一些吧!
1. 小心可能陷入的死循环
其实单独的 unsigned type
你还是比较容易想明白的,怕的就是它跟一些其它东西搭配(比如 auto
),而你又忽略了那就是 unsigned type
的时候,例如:
对于上述的代码,会有什么问题吗?当然啦,有些经验的人还是一眼就能看出来的,还有一些人需要多看两眼才能看出来。
问题就在于 std::strlen
的返回值类型是 std::size_t
,这是一个 unsigned type
,而一个 unsigned type
的数值永远大于等于 0,是的没错,你很快就反应过来,死循环了。当然这个例子比较简单,而且如果你用 VS 这种有 intelliSense
的 IDE,你敲出函数,或者鼠标移到函数名字上,就能看到函数的声明。那万一不是用这种 IDE 的呢?又或者,一些你不认识的函数,而且类型也被 typedef
到你认不出来了,你又如何分辨呢?虽然这是简单的例子,但确实存在这种风险。
2. 小心可能的访问越界
其实上面的例子也是属于访问越界,但 C++ 的数组兼容 C,而 C 的数组是不作边界检查的,所以实际上,上述代码运行起来不会崩溃,甚至还不一定会出错。为什么说不一定呢,因为 sz[-1]
的内容是不知道的…所以输出也有可能让你撞对了,但是!这个代码一定是错误的。所以我会推荐使用 C++ 的容器,对于日常使用,已经绰绰有余了。C++ 的容器,在 Debug
模式下,是会做边界检查的。比如用 VS,在 Debug
下,容器访问越界了,它会有弹出一个 MessageBox
显示 xxx subscript out of range
之类的信息。
好了回归正题,使用容器时,要是一不小心,写出了类似代码:
就会导致了访问越界了,原因也是 size()
的返回类型是 std::size_t
。
之前我写一个 BigInteger 类的时候,就有很多用到了这样的倒序输出的地方。一开始没注意,也是出现了访问越界的错误,后来看到 vector subscript out of range
才反应过来。
解决方案
unsigned type
的坑其实也不算很深,主要在于 0 这个点,要注意以下。解决的方案(至少)有以下几种:
1. 显示指定可容纳范围内的 signed type
如果你知道你数据的确切范围,比如不会超过 100w,那么就可以用一个能够放得下 100w 的一个 signed type
去指定它:
2. 手动做边界检查
如果你不能确定大小,而且不确定 signed type 能否容纳下那个 unsigned type 的全部范围,那就自己在循环的周围做一个边界检查:
3. 把边界检查移到循环外部
其实是第2种方法的变形,如果你唯恐在循环内检查损失了很多性能,那么就可以这样做:
4. 使用 C++ 迭代器
既然用了 C++ 的容器,那么更好的写法当然是这样啦:
这样就不需要去考虑下标范围啦,什么检查啦,多方便!如果想写出更泛型,更有 C++ Style 的代码,还可以这样:
最终选择什么样的方式还是看实际情况跟个人爱好咯!
三、其它杂谈
我在网上看到不少关于什么“代码优化技巧”等等文章,即使是最近出的,还是这样写,也不知道是不是到处抄的。比如我看到其中一篇就说到:
有些处理器处理无符号unsigned 整形数的效率远远高于有符号signed整形数
正确与否我就先不说了,错别字我也不去说了。留给读者先自己实验一下,我会在下一篇中用一篇来讨论一下这类问题。
四、总结
小标题即总结。