迭代std::vector: unsigned vs signed索引变量

在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变量可能是危险的,所以-这是正确的吗?

863951 次浏览

使用size_t:

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

引用维基百科:

stdlib.h和stddef.h头文件定义了一个名为size_t的数据类型,用于表示对象的大小。接受大小的标准库函数期望它们的类型为size_t, sizeof操作符的计算结果为size_t

size_t的实际类型是平台相关的;一个常见的错误是假定size_t与unsigned int相同,这可能导致编程错误,特别是在64位架构变得越来越普遍的情况下。

调用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是存储在向量中的数据类型。

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

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

讲一点历史:

为了表示一个数字是否为负,计算机使用“符号”位。int是一个带符号的数据类型,这意味着它可以保存正数和负数(大约- 20亿到20亿)。Unsigned只能存储正数(由于它不浪费元数据,它可以存储更多:0到大约40亿)。

std::vector::size()返回unsigned,因为向量的长度怎么可能是负的呢?

警告告诉您,不等式语句的右操作数可以比左操作数保存更多的数据。

从本质上讲,如果你有一个超过20亿项的向量,你使用一个整数来索引,你会遇到溢出问题(int将返回到- 20亿)。

第一个是类型正确,严格意义上的正确。(如果你想的是,size永远不可能小于零。)不过,这个警告在我看来是一个很好的被忽视的警告。

要向后迭代,请参见这个答案

向前迭代几乎是一样的。只需要改变迭代器/ swap就可以了。你应该更喜欢迭代器。有些人告诉你使用std::size_t作为索引变量类型。然而,这是不可移植的。始终使用容器的size_type类型定义(虽然在向前迭代的情况下可以只使用转换,但在向后迭代的情况下使用std::size_t时,实际上可能会一直出错,如果std::size_tsize_type的类型定义宽的话):


使用std::向量

使用迭代器

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

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

使用Range 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; ... */
}

使用Range 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方法可以产生的问题。

我通常使用BOOST_FOREACH:

#include <boost/foreach.hpp>


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

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

在您示例中的特定情况下,我将使用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 );

关于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) {
...

四年过去了,谷歌给了我这个答案。对于c++ 11标准< em > < / em >(又名c++ 0 x),实际上有一种新的愉快的方式来做到这一点(以打破向后兼容性为代价):新的auto关键字。它为您省去了必须显式指定要使用的迭代器类型(再次重复vector类型)的痛苦,而(对编译器来说)显然应该使用哪种类型。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 + +编译器的请注意,程序应该是平台(和编译器)独立的。

为了完整,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
}

在c++中11

我会使用像for_each这样的通用算法来避免搜索正确类型的迭代器和lambda表达式,以避免额外命名的函数/对象。

短的“漂亮”;例如你的特定情况(假设多边形是一个整数向量):

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

测试上:http://ideone.com/i6Ethd

别忘了包括:算法,当然还有vector:)

微软实际上也有一个很好的例子:
来源: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;
}

考虑是否需要迭代

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

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

模糊但重要的细节:如果你像下面这样说“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]

要修改vector的元素,需要将迭代器定义为引用:

for(auto &it : v)

如果你的编译器支持它,你可以使用一个基于范围的for来访问vector元素:

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


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

指纹:1 2 3。注意,您不能使用此技术来更改向量的元素。

这两个代码段的工作原理相同。然而,unsigned int"路由是正确的。使用unsigned int类型将更好地用于使用它的实例中的vector。在vector对象上调用size()成员函数将返回一个无符号整数值,因此您希望将变量“i”与其自身类型的值进行比较。

此外,如果你仍然对“unsigned int”在代码中的表现感到不安,可以试试“uint”。这基本上是“unsigned int”的缩写版本,它的工作原理完全相同。您也不需要包含其他头文件来使用它。

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

例子

for(decltype(v.size()) i{ 0 }; i < v.size(); i++) {
/* std::cout << v[i]; ... */
}
auto polygonsize = polygon.size(), i=polygonsize;
for (i=0; i < polygonsize; i++) {
sum += polygon[i];
}

  • 使用auto来避免我们担心类型。
  • 它将任何函数调用(例如size()函数调用)从循环中取出,以避免不必要的重复函数调用。
  • 它使循环计数器可用。纯粹主义者想要在不知道n值的情况下处理第n个元素,并认为这很糟糕。
  • 在声明循环变量时,它似乎有一个不必要的语句i=polygonsize初始化循环变量,但如果有一个像样的代码优化器,这应该会消失,而仅仅是为了确保i具有正确的类型。

我并不是说任何人都应该像我刚才那样编写代码。

我只是将它作为另一种替代方法提供,以避免担心类型,将函数调用从循环中取出,并使循环计数器可用于更复杂场景中的调试信息等实际事情。