在std :: vector上进行迭代:无符号vs有符号索引变量


469

在C ++中迭代向量的正确方法是什么?

考虑下面的两个代码片段,这一段效果很好:

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

我是C ++领域的新手,所以unsigned变量对我来说有点令人恐惧,我知道unsigned如果使用不正确,变量可能很危险,所以-这正​​确吗?


9
无符号的是正确的,因为polygon.size()是无符号的类型。无符号表示始终为正或0。仅此而已。因此,如果变量的用法始终仅用于计数,那么unsigned是正确的选择。
亚当·布鲁斯

2
@AdamBruss .size()不是unsignedaka 类型unsigned int。它是类型的std::size_t
underscore_d

1
@underscore_d size_t是未签名的别名。
亚当·布鲁斯

1
@AdamBruss号std::size_t是_implementation定义的typedef。参见标准。std::size_t可能与unsigned您当前的实现等效,但这无关紧要。假装它会导致不可移植的代码和不确定的行为。
underscore_d

2
@LF ...确定,这可能std::size_t在实践中。您认为我们已经在6年多的漫长评论中涵盖了所有内容吗?
underscore_d

Answers:


816

对于向后迭代,请参见此答案

向前迭代几乎是相同的。只需按增量更改迭代器/交换递减量即可。您应该更喜欢迭代器。有人告诉您将其std::size_t用作索引变量类型。但是,这不是便携式的。始终使用size_type容器的typedef(虽然在向前迭代的情况下只能进行一次转换,但是在使用时std::size_t,如果向后迭代的情况下它可能会出错,以防万一std::size_t大于typedef的情况size_type) :


使用std :: vector

使用迭代器

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

重要的是,对于您不知道其定义的迭代器,请始终使用前缀增量形式。这将确保您的代码尽可能地通用。

使用范围C ++ 11

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方法可以解决什么问题。


指针的大小类型:使用difference_type可能更便于携带。尝试iterator_traits <element_type *> :: difference_type。这只是一份声明,但更便于携带……
wilhelmtell

wilhelmtell,我应该使用different_type吗?sizeof被定义为返回size_t :)我不了解您。如果我要相互减去指针,difference_type将是正确的选择。
Johannes Schaub-litb

如果在传递给该函数的数组上的函数中执行迭代,则使用本文中提到的技术对数组进行迭代将不起作用。因为sizeof数组只会返回sizeof指针。
systemsfault 2011年

1
@Nils我同意使用无符号循环计数器是一个坏主意。但是由于标准库使用无符号整数类型来定义索引和大小,因此我更喜欢标准库使用无符号索引类型。因此,其他库仅使用带符号的类型,例如Qt库。
Johannes Schaub-litb 2012年

32
C ++ 11更新:基于范围的循环。for (auto p : polygon){sum += p;}
任思远

170

四年过去了,谷歌给了我这个答案。使用标准C ++ 11(aka C ++ 0x),实际上有一种新的令人愉悦的方式(以破坏向后兼容性为代价):new auto关键字。它可以省去必须明确指定要使用的迭代器的类型(再次重复向量类型)的麻烦(对于编译器而言),即使用哪种类型。随着v做你的vector,你可以这样做:

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 ++编译器,该程序应独立于平台(和编译器)。


7
C ++ 11带给您for (auto& val: vec)
Flexo

@flexo谢谢,我不知道该如何忘记。我猜没有做足够的C ++。无法相信有什么实用的东西(实际上是JavaScript语法)。我更改了答案以包括该内容。
克拉滕科

你的回答很好。令人遗憾的是,各种OS devkit中的g ++默认版本低于4.3,这使其无法正常工作。
Ratata Tata

您需要使用来初始化向量std::vector<int> v = std::vector<int>();,还是可以简单地使用它std::vector<int> v;呢?
Bill Cheatham

@BillCheatham好吧-我只是在没有初始化的情况下对其进行了尝试,并且它确实起作用了,因此似乎没有它也可以工作。
克拉滕科

44

在您的示例的特定情况下,我将使用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,如诊断所指示的那样,必须在其前面加上关键字。
恢复莫妮卡2014年

17

的调用vector<T>::size()返回类型为std::vector<T>::size_type,not 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是您存储在向量中的数据类型。

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


迭代器通常是一个好主意,尽管我怀疑是否需要将“ end”存储在单独的变量中,并且都可以在for(;;)语句中完成。
SauliusŽemaitaitis09年

1
我知道begin()和end()是固定时间摊销的,但是与将所有内容都塞进一行相比,我通常发现它更具可读性。
贾斯珀·贝克斯

3
您可以将for分成不同的行以提高可读性。在循环外部声明迭代器意味着在不同类型的容器上进行的每个循环都需要一个不同的迭代器名称。
杰·康罗德

我知道所有的差异,基本上可以归结为个人喜好。这通常是我最终做事的方式。
贾斯珀·贝克斯

2
@pihentagy我想这将是在for循环的第一部分中进行设置。例如。for(auto i = polygon.begin(),end = polygon.end(); i!= end; i ++)
Jasper Bekkers 2014年

11

用途size_t

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

引用维基百科

stdlib.h和stddef.h头文件定义了一个称为的数据类型size_t,该数据类型用于表示对象的大小。接受大小的库函数希望它们是type size_t,并且sizeof运算符的计算结果为size_t

的实际类型size_t取决于平台;一个常见的错误是假定size_t它与unsigned int相同,这可能导致编程错误,尤其是在64位体系结构变得越来越普遍时。


size_t适用于矢量,因为它必须将所有对象存储在一个数组中(本身也是一个对象),但是std :: list可能包含多个size_t元素!
MSalters,2009年

1
通常,size_t足以枚举进程地址空间中的所有字节。虽然我可以看到某些异国情调的架构可能并非如此,但我宁愿不用担心。

在AFAIK中,建议#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亿个条目,并且使用整数索引将遇到溢出问题(int将回绕为负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,2009年

3
好吧,他问向量迭代的正确方法是什么。因此似乎足够相关。警告只是为什么他对当前的解决方案不满意。
jalf

5

为了完整起见,C ++ 11语法仅为迭代器(ref)启用了另一个版本:

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表达式类型,以避免额外的命名函数/对象。

针对特定情况的简短“漂亮”示例(假设多边形是整数的向量):

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;
}

4
for (vector<int>::iterator it = polygon.begin(); it != polygon.end(); it++)
    sum += *it; 

2
对于vector来说,这很好,但是通常情况下,最好使用++ it而不是++,以防迭代器本身不平凡。
史蒂夫·杰索普

就个人而言,我习惯于使用++ i,但我认为大多数人都喜欢i ++样式(“ for”的默认VS代码段为i ++)。只是一个想法
Mehrdad Afshari

@MehrdadAfshari谁在乎“大多数人”做什么?“大多数人”在很多事情上都是错误的。至少从理论上讲,永远不使用pre值的in-inc / decrement是错误且效率低下的-不管在各处的次等示例代码中盲目地使用它。您不应该鼓励不良做法,以使那些还不了解的人看起来更熟悉。
underscore_d

2

第一个是类型正确的,并且在某种意义上是正确的。(如果您考虑的是,大小永远不能小于零。)但是,警告让我震惊,因为它是被忽略的很好的候选者之一。


2
我认为这是一个可怕的候选人,很容易被忽略-易于修复,并且由于错误地比较了有符号/无符号值,有时会出现真正的错误。例如,在这种情况下,如果大小大于INT_MAX,则循环永远不会终止。
史蒂夫·杰索普

...或者也许立即终止。两者之一。取决于将有符号的值转换为无符号以进行比较,还是将无符号的值转换为有符号。但是,在具有32位int的64位平台上,例如win64,该int将被提升为size_t,并且循环永远不会结束。
史蒂夫·杰索普

@SteveJessop:您不能肯定地说循环永远不会结束。在上的迭代时i == INT_MAX,则i++导致未定义的行为。此时任何事情都可能发生。
Ben Voigt

@BenVoigt:是的,仍然没有提供忽略警告的理由:-)
Steve Jessop 2014年

2

考虑是否需要迭代

<algorithm>标准的头为我们提供了这样的设施:

using std::begin;  // allows argument-dependent lookup even
using std::end;    // if the container type is unknown here
auto sum = std::accumulate(begin(polygon), end(polygon), 0);

算法库中的其他函数执行常见任务-如果您想节省精力,请确保知道可用的功能。


1

晦涩但很重要的细节:如果您按如下方式说“ for(auto it)”,您将得到对象的副本,而不是实际的元素:

struct Xs{int i} x;
x.i = 0;
vector <Xs> v;
v.push_back(x);
for(auto it : v)
    it.i = 1;         // doesn't change the element v[0]

要修改向量的元素,您需要定义迭代器作为参考:

for(auto &it : v)

1

如果编译器支持,则可以使用基于的范围来访问矢量元素:

vector<float> vertices{ 1.0, 2.0, 3.0 };

for(float vertex: vertices){
    std::cout << vertex << " ";
}

打印:1 2 3。请注意,您不能使用此技术来更改矢量的元素。


0

这两个代码段工作相同。但是,unsigned int“路由是正确的。在使用过的实例中,使用unsigned int类型将更适合矢量。在vector上调用size()成员函数将返回一个无符号整数值,因此您想比较变量“ i”为其自身类型的值。

另外,如果您对代码中“ unsigned int”的外观仍然不太满意,请尝试“ uint”。这基本上是“ unsigned int”的简化版本,其工作原理完全相同。您也不需要包含其他标题即可使用它。


用C ++术语来说,size()的无符号整数不一定等于“ unsigned int”,在这种情况下,“无符号整数”通常是64位无符号整数,而“ unsigned int”通常是32位。
Medran

0

添加此内容是因为我在任何答案中都找不到它:对于基于索引的迭代,我们可以使用decltype(vec_name.size())它将评估为std::vector<T>::size_type

for(decltype(v.size()) i{ 0 }; i < v.size(); i++) {
    /* std::cout << v[i]; ... */
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.