在 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
变量会很危险,所以 - 这是正确的吗?
.size()
不是 unsigned
又名 unsigned int
类型。它的类型为 std::size_t
。
std::size_t
是 _implementation-defined typedef。见标准。 std::size_t
在您当前的实现中可能等同于 unsigned
,但这并不相关。假装它是可能导致不可移植的代码和未定义的行为。
对于向后迭代,请参见 this answer。
向前迭代几乎是相同的。只需按增量更改迭代器/交换减量。您应该更喜欢迭代器。有人告诉您使用 std::size_t
作为索引变量类型。但是,这不是便携式的。始终使用容器的 size_type
类型定义(虽然您可以在前向迭代情况下只进行一次转换,但在使用 std::size_t
时,在反向迭代情况下它实际上可能一直出错,以防 std::size_t
比 size_type
的 typedef 宽):
使用 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
方法可以解决的问题。
四年过去了,Google 给了我这个答案。使用 standard C++11(又名 C++0x)实际上有一种新的令人愉快的方法(以破坏向后兼容性为代价):新的 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++ 编译器的,程序应该是平台(和编译器)独立的。
for (auto& val: vec)
std::vector<int> v = std::vector<int>();
初始化向量,或者您可以简单地使用 std::vector<int> v;
来代替?
在您的示例中的特定情况下,我将使用 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 );
关于约翰内斯·绍布的回答:
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) {
...
T
是模板参数的情况,因此表达式 std::vector<T*>::iterator
是从属名称。要将依赖名称解析为类型,需要在其前面加上 typename
关键字,如诊断所示。
对 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::transform
、std::copy
、std::fill
、std::for_each
等)。
使用 size_t
:
for (size_t i=0; i < polygon.size(); i++)
引用Wikipedia:
stdlib.h 和 stddef.h 头文件定义了一个名为 size_t 的数据类型,用于表示对象的大小。采用大小的库函数期望它们的类型为 size_t,并且 sizeof 运算符的计算结果为 size_t。 size_t 的实际类型取决于平台;一个常见的错误是假设 size_t 与 unsigned int 相同,这可能导致编程错误,尤其是在 64 位架构变得越来越普遍的情况下。
#include <cstddef>
而不是 <stddef.h>
或更糟糕的是整个 [c]stdlib
,并使用 std::size_t
而不是不合格的版本 - 对于您可以在 <cheader>
和<header.h>
。
一点历史:
要表示一个数字是否为负数,计算机使用“符号”位。 int
是有符号数据类型,意味着它可以保存正值和负值(大约 -20 亿到 20 亿)。 Unsigned
只能存储正数(由于它不会在元数据上浪费一点点,它可以存储更多:0 到大约 40 亿)。
std::vector::size()
返回一个 unsigned
,因为向量怎么会有负长度?
警告告诉您,不等式语句的右操作数可以比左操作数容纳更多的数据。
本质上,如果您有一个包含超过 20 亿个条目的向量,并且您使用一个整数来索引,您将遇到溢出问题(int 将返回到负 20 亿)。
我通常使用 BOOST_FOREACH:
#include <boost/foreach.hpp>
BOOST_FOREACH( vector_type::value_type& value, v ) {
// do something with 'value'
}
它适用于 STL 容器、数组、C 风格的字符串等。
完整地说,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
}
在 C++11 中
我会使用像 for_each
这样的通用算法来避免搜索正确类型的迭代器和 lambda 表达式,以避免额外的命名函数/对象。
针对您的特定情况的简短“漂亮”示例(假设多边形是整数向量):
for_each(polygon.begin(), polygon.end(), [&sum](int i){ sum += i; });
不要忘记包括:算法,当然还有向量 :)
微软实际上也有一个很好的例子:
来源: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;
}
for (vector<int>::iterator it = polygon.begin(); it != polygon.end(); it++)
sum += *it;
首先是类型正确,并且在某种严格意义上是正确的。 (如果您考虑一下,大小永远不会小于零。)不过,这个警告让我觉得是被忽略的好人选之一。
i == INT_MAX
的迭代中,i++
会导致未定义的行为。在这一点上,任何事情都可能发生。
考虑是否需要迭代
<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]
要修改向量的元素,需要将迭代器定义为引用:
for(auto &it : v)
如果您的编译器支持它,您可以使用基于范围的 for 来访问向量元素:
vector<float> vertices{ 1.0, 2.0, 3.0 };
for(float vertex: vertices){
std::cout << vertex << " ";
}
打印:1 2 3 。请注意,您不能使用此技术来更改向量的元素。
添加这个,因为我在任何答案中都找不到它:对于基于索引的迭代,我们可以使用 decltype(vec_name.size())
,它的计算结果为 std::vector<T>::size_type
例子
for(decltype(v.size()) i{ 0 }; i < v.size(); i++) {
/* std::cout << v[i]; ... */
}
这两个代码段的工作方式相同。但是,无符号整数“路线是正确的。使用无符号整数类型将更好地与您使用它的实例中的向量一起使用。在向量上调用 size() 成员函数会返回一个无符号整数值,因此您希望比较变量"i" 为其自身类型的值。
此外,如果您对代码中“unsigned int”的外观仍然有些不安,请尝试“uint”。这基本上是“unsigned int”的缩短版本,它的工作原理完全相同。您也不需要包含其他标题来使用它。
auto polygonsize = polygon.size(), i=polygonsize;
for (i=0; i < polygonsize; i++) {
sum += polygon[i];
}
这个
使用 auto 来避免我们担心类型。
它需要任何函数调用,例如循环中的 size() 函数调用,以避免不必要的重复函数调用。
它使循环计数器可用。纯粹主义者会想在不知道 n 值的情况下使用第 n 个元素,并认为这很糟糕。
它似乎有一个不必要的语句 i=polygonsize 在声明循环变量时对其进行初始化,但是如果有一个不错的代码优化器,这应该会消失,并且只是为了确保 i 具有正确的类型。
我并不是说任何人都应该像我刚才那样编写任何东西。
我只是将它作为另一种替代方案来提供,它可以避免担心类型,将函数调用排除在循环之外,并使循环计数器可用于实际操作,例如在更复杂的场景中调试信息。
不定期副业成功案例分享
for (auto p : polygon){sum += p;}