14

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

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

...当我编译时,我收到这个警告:

warning: comparison between signed and unsigned integer expressions 

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

4

4 回答 4

11

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

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

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

于 2012-10-16T00:23:07.080 回答
11

已经回答得很好,但我会添加我的 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<MyObject>的 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,或向量。如果你总是在最后推并在前面弹出,它确实适用于双端队列,如果你在广度优先步行中使用双端队列作为队列,这很方便;我不确定你是否可以在后面弹出双端队列。

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

于 2012-10-16T00:46:54.667 回答
3

在大多数情况下,直到您的向量包含的元素多于有符号的 int 可以表示的元素,这并不重要。

于 2012-10-16T00:23:19.013 回答
2

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

#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 风格的演员表:摆脱他们!;-)

于 2012-10-16T00:41:36.187 回答