在std::vector上迭代:无符号索引变量与有符号索引变量

492

在C++中迭代遍历vector的正确方法是什么?

考虑这两个代码片段,这一个可以正常工作:

for (unsigned i=0; i < polygon.size(); i++) {
    sum += polygon[i];
}

还有这个:

for (int i=0; i < polygon.size(); i++) {
    sum += polygon[i];
}

这产生了warning: comparison between signed and unsigned integer expressions警告。

unsigned变量让我有点害怕,我知道如果使用不正确,unsigned变量可能会很危险。所以,这是正确的吗?


11
未签名的变量是正确的,因为 polygon.size() 的类型是 unsigned。Unsigned 的意思是始终为正或零,就是这个意思。所以,如果该变量的使用仅用于计数,则 unsigned 是正确的选择。 - Adam Bruss
3
@AdamBruss .size() 不是类型为 unsigned 或者 unsigned int,它的类型是 std::size_t - underscore_d
1
@underscore_d size_t 是 unsigned 的别名。 - Adam Bruss
5
不行。std::size_t是一个实现定义的typedef。请参阅标��。在当前实现中,std::size_t可能等同于unsigned,但这并不重要。假装它是可以导致非可移植代码和未定义的行为。 - underscore_d
1
@underscore_d,我之前说unsigned等同于size_t是错误的。正如你所指出的,在64位构建下,size_t是8个字节。这在微软的Visual C++中也是正确的。但是,如果size_t在两个编译器之间实际上有所不同,就像你所暗示的那样,那么仅仅使用size_t就会导致非可移植代码。 - Adam Bruss
显示剩余5条评论
18个回答

857

若要倒序迭代,请参考这个答案

正向迭代几乎相同。只需更改迭代器/交换递减为递增即可。您应该优先使用迭代器。有些人建议将std::size_t用作索引变量类型。然而,那并不具有可移植性。始终使用容器的size_type typedef(虽然在正向迭代情况下仅需要转换,但在使用std::size_t时,在倒序迭代情况下实际上可能完全出错,如果std::size_tsize_type的typedef更宽):


使用std :: vector

使用迭代器

for(std::vector<T>::iterator it = v.begin(); it != v.end(); ++it) {
    /* std::cout << *it; ... */
}

重要的是,对于那些定义未知的迭代器,始终使用前缀自增形式。这将确保您的代码尽可能地运行通用。

使用C++11的范围(for-range)循环

for(auto const& value: a) {
     /* std::cout << value; ... */

使用索引

for(std::vector<int>::size_type i = 0; i != v.size(); i++) {
    /* std::cout << v[i]; ... */
}

使用数组

使用迭代器

for(element_type* it = a; it != (a + (sizeof a / sizeof *a)); it++) {
    /* std::cout << *it; ... */
}

使用C++11中的范围

for(auto const& value: a) {
     /* std::cout << value; ... */

使用索引

for(std::size_t i = 0; i != (sizeof a / sizeof *a); i++) {
    /* std::cout << a[i]; ... */
}

阅读倒序迭代的答案,了解sizeof方法可能会导致什么问题。


使用您在此帖子中提到的技术迭代数组,在函数中对传递给该函数的数组执行迭代将无法正常工作。因为 sizeof 数组只会返回指针的大小。 - systemsfault
1
@Nils 我同意使用无符号循环计数器是个坏主意。但是因为标准库使用无符号整数类型作为索引和大小,我更喜欢在标准库中使用无符号索引类型。其他库因此只使用有符号类型,比如Qt库。 - Johannes Schaub - litb
33
C++11更新:基于范围的for循环。for (auto p : polygon){sum += p;} 可以理解为“对于polygon中的每个p,将p加到sum中”。 - Siyuan Ren
@C.R. 还要更新到 C++11:使用 std::begin()std::end() 使数组和向量版本相等。 - Manu343726
如果你需要使用循环索引,但又懒得输入容器的完整 size_type 类型,可以考虑使用 C++11 的另一个特性 decltype,例如 for (decltype(vec.size()) i=0; i<vec.size(); ++i) - Yibo Yang
显示剩余5条评论

180
Four years later, Google gave me this answer. With the standard C++11 (also known as C++0x), there is now a new and convenient way to achieve this (at the cost of breaking backward compatibility): using the new auto keyword. It spares you the trouble of explicitly specifying the type of the iterator to be used (repeating the vector type again), when it is already clear (to the compiler) which type to use. Assuming that v is your vector, you can do something like this:
for ( auto i = v.begin(); i != v.end(); i++ ) {
    std::cout << *i << std::endl;
}

C++11更进一步,为您提供了一种特殊的语法,用于迭代像向量这样的集合。它消除了编写始终相同的内容的必要性:

for ( auto &i : v ) {
    std::cout << i << std::endl;
}

要在工作程序中查看它,请构建一个名为auto.cpp的文件:
#include <vector>
#include <iostream>

int main(void) {
    std::vector<int> v = std::vector<int>();
    v.push_back(17);
    v.push_back(12);
    v.push_back(23);
    v.push_back(42);
    for ( auto &i : v ) {
        std::cout << i << std::endl;
    }
    return 0;
}

在编写本文时,当您使用 g++ 进行编译时,通常需要通过添加额外的标志来使其与新标准兼容:

g++ -std=c++0x -o auto auto.cpp

现在你可以运行这个例子:
$ ./auto
17
12
23
42

请注意,编译和运行的说明仅适用于Linux上的GNU C++编译器,该程序应该是平台(和编译器)无关的。

9
C++11 提供了 for (auto& val: vec) - Flexo
1
@flexo 谢谢,我不知道我怎么会忘记那个。我想是因为我没有做足够的C++吧。我简直不敢相信有这样实用的东西(其实我以为那是JavaScript语法)。我改变了答案来包括它。 - kratenko
你的答案非常好。令人不满意的是,各种操作系统开发工具包中默认版本的g ++低于4.3,这使其无法正常工作。 - Ratata Tata
你需要使用 std::vector<int> v = std::vector<int>(); 来初始化向量吗?或者你可以直接使用 std::vector<int> v; 吗? - Bill Cheatham
你不需要使用&来进行只读访问,我个人认为在这里使用int比auto更好:我会使用:for (int i: v)。 - Will
显示剩余4条评论

43
在您的示例中,我会使用STL算法来实现这一点。
#include <numeric> 

sum = std::accumulate( polygon.begin(), polygon.end(), 0 );

对于一个更普遍但仍相当简单的情况,我会选择:

#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>

using namespace boost::lambda;
std::for_each( polygon.begin(), polygon.end(), sum += _1 );

38
关于 Johannes Schaub 的回答:
for(std::vector<T*>::iterator it = v.begin(); it != v.end(); ++it) { 
...
}

这在某些编译器上可能有效,但在gcc中却不行。问题在于std::vector::iterator是否是一种类型、变量(成员)还是函数(方法)。我们在使用gcc时会遇到以下错误:

In member function ‘void MyClass<T>::myMethod()’:
error: expected `;' before ‘it’
error: ‘it’ was not declared in this scope
In member function ‘void MyClass<T>::sort() [with T = MyClass]’:
instantiated from ‘void MyClass<T>::run() [with T = MyClass]’
instantiated from here
dependent-name ‘std::vector<T*,std::allocator<T*> >::iterator’ is parsed as a non-type, but instantiation yields a type
note: say ‘typename std::vector<T*,std::allocator<T*> >::iterator’ if a type is meant

解决方案是使用关键字“typename”,如下所示:
typename std::vector<T*>::iterator it = v.begin();
for( ; it != v.end(); ++it) {
...

2
你应该详细说明,这仅适用于T是模板参数的情况,因此表达式std::vector<T*>::iterator是一个相关名称。为了将相关名称解析为类型,需要在其前面加上typename关键字,就像诊断信息所示。 - Kuba hasn't forgotten Monica

17

vector<T>::size() 的调用返回的是 std::vector<T>::size_type 类型的值,而不是 int、unsigned int 或其他类型。

在 C++ 中,通常使用迭代器来遍历容器,例如:

std::vector<T>::iterator i = polygon.begin();
std::vector<T>::iterator end = polygon.end();

for(; i != end; i++){
    sum += *i;
}

T代表您在vector中存储的数据类型。

或者使用不同的迭代算法(std::transformstd::copystd::fillstd::for_each等)。


1
我知道begin()和end()是摊还常数时间,但我通常认为这比将所有内容挤入一行更易读。 - Jasper Bekkers
3
为了提高可读性,你可以将for循环分成多行。在循环外声明迭代器意味着你需要为不同类型的容器进行每个循环命名一个不同的迭代器名称。 - Jay Conrod
通常建议将end()的结果存储在变量中,以避免重复调用函数。即使它是常数时间,仍然存在调用的开销。没有理由在循环外初始化,唯一的结果是混乱作用域。 - user44511
@user44511:那你有什么建议吗? - pihentagy
2
@pihentagy 我猜应该是将其设置在for循环的第一部分。例如,for(auto i = polygon.begin(), end = polygon.end(); i != end; i++) - Jasper Bekkers
显示剩余3条评论

13

使用 size_t

for (size_t i=0; i < polygon.size(); i++)

引用自维基百科

stdlib.hstddef.h头文件定义了一个称为size_t的数据类型,用于表示对象的大小。接受大小参数的库函数要求其类型为size_t,而sizeof运算符的结果也是size_t

size_t的实际类型取决于平台;一个常见的错误是假设size_t与无符号整数类型相同,这可能导致编程错误,特别是在64位架构越来越普遍的情况下。


size_t 对于 vector 是可行的,因为它必须将所有对象存储在一个数组中(本身也是一个对象),但 std::list 可能包含超过 size_t 元素! - MSalters
1
size_t 通常足以枚举进程地址空间中的所有字节。虽然我可以看出在一些奇特的架构上可能不是这种情况,但我宁愿不用担心它。 - user3458
据我所知,建议使用#include <cstddef>而不是<stddef.h>或更糟糕的是整个[c]stdlib,并且使用std::size_t而不是未经限定的版本 - 在任何其他情况下也是如此,其中您可以在<cheader><header.h>之间进行选择。 - underscore_d

7

历史背景:

计算机使用“符号位”来表示数字是否为负。int是一种有符号数据类型,意味着它可以保存正数和负数(约-20亿到20亿)。无符号unsigned只能存储正数(并且由于不浪费一个位于元数据上,它可以存储更多:从0到约40亿)。

std::vector::size()会返回一个unsigned,因为向量怎么可能具有负长度呢?

警告提示您,不等式语句的右操作数可以容纳比左操作数更多的数据。

本质上,如果您有一个包含超过20亿个条目的向量,并使用整数索引,则会遇到溢出问题(整数将回绕至负20亿)。


6

我通常使用BOOST_FOREACH:

#include <boost/foreach.hpp>

BOOST_FOREACH( vector_type::value_type& value, v ) {
    // do something with 'value'
}

它适用于STL容器、数组、C风格字符串等。


2
这是对另一个问题(如何迭代向量?)的很好的回答,但完全不是OP所问的(无符号变量警告的含义是什么?)。 - abelenky
3
他问什么是迭代向量的正确方式,所以似乎相关性很大。警告只是解释了为什么他对目前的解决方案不满意。 - jalf

5
为了完整,C++11 语法提供了迭代器的另一个版本(参考):
for(auto it=std::begin(polygon); it!=std::end(polygon); ++it) {
  // do something with *it
}

这也适用于反向迭代,使操作更加方便。

for(auto it=std::end(polygon)-1; it!=std::begin(polygon)-1; --it) {
  // do something with *it
}

5

在C++11中

我会使用一般算法,比如for_each,以避免寻找正确类型的迭代器,并使用 lambda 表达式来避免多余的命名函数/对象。

下面是一个简短的“漂亮”示例,适用于您的特定情况(假设 polygon 是一个整数向量):

for_each(polygon.begin(), polygon.end(), [&sum](int i){ sum += i; });

测试环境: http://ideone.com/i6Ethd

不要忘记包含:算法和向量 :)

微软实际上也有一个不错的例子:
来源: http://msdn.microsoft.com/en-us/library/dd293608.aspx

#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;

int main() 
{
   // Create a vector object that contains 10 elements.
   vector<int> v;
   for (int i = 1; i < 10; ++i) {
      v.push_back(i);
   }

   // Count the number of even numbers in the vector by 
   // using the for_each function and a lambda.
   int evenCount = 0;
   for_each(v.begin(), v.end(), [&evenCount] (int n) {
      cout << n;
      if (n % 2 == 0) {
         cout << " is even " << endl;
         ++evenCount;
      } else {
         cout << " is odd " << endl;
      }
   });

   // Print the count of even numbers to the console.
   cout << "There are " << evenCount 
        << " even numbers in the vector." << endl;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接