【问题标题】:Signed vs. unsigned values for counting in a loop循环计数的有符号值与无符号值
【发布时间】:2012-10-06 00:43:12
【问题描述】:

所以我在程序中有一个普通的 for 循环,通过对象向量(我定义的类型的对象,如果相关的话):

for(int k = 0; k < objects.size(); k++){ ... }

...当我编译时,我收到以下警告:

warning: comparison between signed and unsigned integer expressions 

这是有道理的,因为我认为向量的size() 返回size_t。但是这有什么关系呢?一定数量的元素(甚至内存块)不是你可以计算的整数吗?更重要的是,由于我的程序有多个这样的循环并且碰巧有很多段错误,这可能是它的一部分吗?

【问题讨论】:

  • 另外一点警告是,如果有符号值(理所当然地)为负数,当它转换为无符号类型时,结果往往会很大,所以比较可能没有结果程序员所期望的。

标签: c++ vector int size-t


【解决方案1】:

已经回答得很好,但我将添加我的 S/0.02:执行此操作的“正确”方法是:

for (typename std::vector<MyObject>::size_type i = 0; i < object.size(); ++i) { ... }

只有有抱负的语言律师才会这样写,甚至他们也可能在读到好东西之前就停止阅读。

使用 C++11,您可以利用 decltype

for (decltype(object.size()) i = 0; i < object.size(); ++i) { ... }

或者你可以利用auto:

for (auto i = object.size() - object.size(); i < object.size(); ++i) { ... }

或者你可以只使用size_t,但你仍然可能对溢出有疑问,因为vector&lt;MyObject&gt; 的size_type 可能大于size_t。 (不是,但不能保证):

for (size_t i = 0; i < object.size(); ++i) { ... }

那么,一个诚实的程序员应该做什么呢?

最简单的解决方案是 STL 从一开始就一直在推广的解决方案。除了一开始写起来也很痛苦:

for (typename std::vector<MyObject>::iterator_type it = object.begin(); it != object.end(); ++it) { ... }

现在,C++11 确实可以帮助您。你有一些非常好的选择,从简单的开始:

for (auto it = object.begin(); it != object.end(); ++it) { ... }

但它会变得更好(请打鼓)......:

for (auto& val : object) { ... }

这就是我要使用的那个。


编辑添加:

Cory Nelson 在评论中指出,还可以通过以下方式缓存 object.end() 的结果:

for (auto it = object.begin(), end = object.end(); it != end; ++it) { ... }

事实证明,for (var : object) 语法生成的代码与 Cory Nelson 提出的代码非常相似。 (所以我鼓励他和你只使用后者。)

但是,它的语义与其他语义略有不同,包括作为原始帖子主题的迭代。如果您在迭代期间以改变其大小的方式修改容器,那么您必须非常仔细地考虑问题。很有可能发生灾难。

迭代可能在迭代期间被修改的向量的唯一方法是使用整数索引,如原始帖子中所述。其他容器更宽容。您可以使用在每次迭代时调用 object.end() 的循环来迭代 STL 映射,并且(据我所知)即使面对插入和删除它也可以工作,但不要尝试使用 unordered_map,或向量。如果你总是在最后推并在前面弹出,它确实适用于双端队列,如果你在广度优先步行中使用双端队列作为队列,这很方便;我不确定你是否可以在后面弹出双端队列。

确实应该在某处简单总结容器类型的容器修改对迭代器和元素指针(并不总是与迭代器相同)的影响,因为这都是由标准指定的,但我从来没有在任何地方遇到它。如果你找到了,请告诉我。

【讨论】:

  • 你错过了for(auto it = object.begin(), end = object.end(); it != end; ++it) { ... },以防你不想每次迭代都调用 end(),这对不支持 range for 的编译器很有好处:)
  • @CoryNelson,是的,这就是for (auto&amp; val : object) 的意思。然而,对于大多数 STL 容器,也许是所有容器,执行 object.end() 基本上没有任何成本。 (特别是对于向量,迭代器只是指针,而 end() 是一个普通的访问器。)
  • 作为一个有趣的说明,至少对于 VC++,vector 的迭代器在很长一段时间内在任何情况下都不是指针。不过,它们当然会优化到等效!
  • @CoryNelson:很有趣。那么它们不是唯一数据成员是指针的类吗?这有什么好处? (在 gnu libstdc++ 中,如果存在适当的 Alloc 特征,则它们不是指针,这对于非标准分配器可能是必需的,但通常它们只是通过一长串 typedef 到 T* 的 typedef。)
  • @rici:在你的最后一个建议中,是否有可能获得一个通用的整数计数器来显示轮数,或者那是不可能的(除非创建一个单独的循环计数器)?
【解决方案2】:

关于循环变量的有符号/无符号比较的警告流中可能会丢失重要警告。甚至一些有符号/无符号的比较警告也很重要!因此,通过定义大小函数来消除不重要的警告,如下所示:

#include <stddef.h>    // ptrdiff_t
#include <utility>     // std::begin, std::end

typedef ptrdiff_t Size;
typedef Size Index;

template< class Type >
Size nElements( Type const& c )
{
    using std::begin;  using std::end;
    return end( c ) - begin( c );
}

然后你可以写例如

for( int i = 0;  i < nElements( v );  ++i ) { ... }

或者,使用迭代器,例如

for( auto it = begin( v );  it != end( v );  ++it ) { ... }

和/或使用基于 C++11 范围的 for 循环,

for( auto const& elem : v ) { ... }

无论如何,在最高实际警告级别进行干净编译对于消除这些段错误和其他错误非常重要。

您应该关注的另一个领域是 C 风格的演员表:摆脱他们! ;-)

【讨论】:

    【解决方案3】:

    在大多数情况下,直到您的向量包含的元素比有符号的 int 可以表示的多。

    【讨论】:

      【解决方案4】:

      object.size() 返回的值大于k 的最大可表示值时,就会出现问题。由于k 已签名,因此与size_t相比,它只有最大值的一半1

      现在,这可能不会在您的特定应用程序中发生(在典型的 32 位系统上,您的集合中将有超过 20 亿个对象),但使用正确的类型总是一个好主意。

      1.先发制人的反驳:是的,这仅适用于使用典型二进制补码算法的机器,以及 intsize_t 使用相同位数表示的机器。

      【讨论】:

        猜你喜欢
        • 2019-03-22
        • 2021-10-04
        • 2015-10-31
        • 2019-07-11
        • 1970-01-01
        • 2012-04-19
        • 1970-01-01
        • 2011-10-11
        • 2010-09-19
        相关资源
        最近更新 更多