ChatGPT解决这个技术问题 Extra ChatGPT

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

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

无符号是正确的,因为 polygon.size() 是无符号类型。无符号表示始终为正或 0。这就是它的全部含义。因此,如果变量的使用始终仅用于计数,那么 unsigned 是正确的选择。
@AdamBruss .size() 不是 unsigned 又名 unsigned int 类型。它的类型为 std::size_t
@underscore_d size_t 是无符号的别名。
@AdamBruss No. std::size_t 是 _implementation-defined typedef。见标准。 std::size_t 在您当前的实现中可能等同于 unsigned,但这并不相关。假装它是可能导致不可移植的代码和未定义的行为。
@underscore_d 我说无符号等于 size_t 是错误的。正如您所指出的,size_t 在 64 位构建下是 8 个字节。在 microsoft visual c++ 中也是如此。但是,如果 size_t 在两个编译器之间实际上有所不同,正如您所推断的那样,您只需使用 size_t 就会拥有不可移植的代码。

M
Maarten Bodewes

对于向后迭代,请参见 this answer

向前迭代几乎是相同的。只需按增量更改迭代器/交换减量。您应该更喜欢迭代器。有人告诉您使用 std::size_t 作为索引变量类型。但是,这不是便携式的。始终使用容器的 size_type 类型定义(虽然您可以在前向迭代情况下只进行一次转换,但在使用 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(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::difference_type。这是一口宣言,但更便携……
wilhelmtell,我应该使用differ_type做什么? sizeof 被定义为返回 size_t :) 我不明白你。如果我要相互减去指针,diff_type 将是正确的选择。
如果在传递给该函数的数组上的函数中执行迭代,则使用您在本文中提到的技术对数组进行迭代将不起作用。因为 sizeof 数组只会返回 sizeof 指针。
@Nils 我同意使用无符号循环计数器是一个坏主意。但是因为标准库使用无符号整数类型来表示索引和大小,所以我更喜欢标准库的无符号索引类型。因此,其他库仅使用签名类型,例如 Qt 库。
C++11 更新:基于范围的 for 循环。 for (auto p : polygon){sum += p;}
Z
Ziezi

四年过去了,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++ 编译器的,程序应该是平台(和编译器)独立的。


C++11 为您提供 for (auto& val: vec)
@flexo 谢谢,我不知道我怎么能忘记这一点。我猜C++做得不够。不敢相信有什么实用的东西(实际上以为是 JavaScript 语法)。我更改了答案以包含它。
你的回答非常好。令人不快的是,各种 OS devkits 中 g++ 的默认版本低于 4.3,这使其无法正常工作。
您是否需要使用 std::vector<int> v = std::vector<int>(); 初始化向量,或者您可以简单地使用 std::vector<int> v; 来代替?
@BillCheatham 好吧-我只是在没有初始化的情况下尝试了它,它确实有效,所以它似乎没有。
p
paxos1977

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

P
Polat Tuzla

关于约翰内斯·绍布的回答:

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 关键字,如诊断所示。
J
Jasper Bekkers

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 等)。


迭代器通常是一个好主意,尽管我怀疑是否需要将“end”存储在一个单独的变量中,并且它都可以在 for(;;) 语句中完成。
我知道 begin() 和 end() 是摊销的常数时间,但我通常发现这比将所有内容都塞进一行更具可读性。
您可以将 for 拆分为单独的行以提高可读性。在循环之外声明迭代器意味着您需要为不同类型容器上的每个循环使用不同的迭代器名称。
我知道所有差异,基本上归结为个人喜好;这通常是我最终做事的方式。
@pihentagy 我猜那是在for循环的第一部分设置它。例如。 for(auto i = polygon.begin(), end = polygon.end(); i != end; i++)
C
Community

使用 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 位架构变得越来越普遍的情况下。


size_t 向量可以,因为它必须将所有对象存储在一个数组中(本身也是一个对象),但 std::list 可能包含超过 size_t 个元素!
size_t 通常足以枚举进程地址空间中的所有字节。虽然我可以看到在某些异国情调的架构上可能并非如此,但我宁愿不用担心。
AFAIK 建议使用 #include <cstddef> 而不是 <stddef.h> 或更糟糕的是整个 [c]stdlib,并使用 std::size_t 而不是不合格的版本 - 对于您可以在 <cheader><header.h>
s
sudo_coffee

一点历史:

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

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

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

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


M
Martin Cote

我通常使用 BOOST_FOREACH:

#include <boost/foreach.hpp>

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

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


对其他一些问题的好答案(我应该如何迭代向量?),但完全不是 OP 所要求的(关于无符号变量的警告的含义是什么?)
好吧,他问迭代向量的正确方法是什么。所以似乎足够相关。警告正是他对当前解决方案不满意的原因。
J
Jan Turoň

完整地说,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
Community

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

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

对于向量,这很好,但通常最好使用 ++it 而不是 it++,以防迭代器本身不重要。
就个人而言,我习惯使用 ++i,但我认为大多数人更喜欢 i++ 样式(“for”的默认 VS 代码片段是 i++)。只是一个想法
@MehrdadAfshari 谁在乎“大多数人”做什么? “大多数人”在很多事情上都是错误的。至少在理论上,从不使用 pre 值的 post-inc/decrement 是错误的和低效的 - 无论它在任何地方的低于标准的示例代码中被盲目使用的频率如何。您不应该仅仅为了让还不了解的人看起来更熟悉而鼓励不良做法。
C
Charlie Martin

首先是类型正确,并且在某种严格意义上是正确的。 (如果您考虑一下,大小永远不会小于零。)不过,这个警告让我觉得是被忽略的好人选之一。


我认为这是一个被忽略的可怕候选 - 它很容易修复,并且偶尔会由于错误地比较有符号/无符号值而发生真正的错误。例如,在这种情况下,如果大小大于 INT_MAX,则循环永远不会终止。
...或者它可能立即终止。两者之一。取决于是否将带符号的值转换为无符号进行比较,或者将无符号的值转换为有符号。但是,在具有 32 位 int 的 64 位平台上,就像 win64 一样,int 将被提升为 size_t,并且循环永远不会结束。
@SteveJessop:你不能肯定地说循环永远不会结束。在 i == INT_MAX 的迭代中,i++ 会导致未定义的行为。在这一点上,任何事情都可能发生。
@BenVoigt:是的,但仍然没有提供忽略警告的理由:-)
T
Toby Speight

考虑是否需要迭代

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

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


P
Pierre

晦涩但重要的细节:如果您按如下方式说“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)

B
Brett L

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

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

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

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


C
Community

添加这个,因为我在任何答案中都找不到它:对于基于索引的迭代,我们可以使用 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”的缩短版本,它的工作原理完全相同。您也不需要包含其他标题来使用它。


size() 的无符号整数不一定等于 C++ 术语中的“无符号整数”,在这种情况下,“无符号整数”通常是 64 位无符号整数,而“无符号整数”通常是 32 位。
I
Ivan
auto polygonsize = polygon.size(), i=polygonsize;
for (i=0; i < polygonsize; i++) {
    sum += polygon[i];
}

这个

使用 auto 来避免我们担心类型。

它需要任何函数调用,例如循环中的 size() 函数调用,以避免不必要的重复函数调用。

它使循环计数器可用。纯粹主义者会想在不知道 n 值的情况下使用第 n 个元素,并认为这很糟糕。

它似乎有一个不必要的语句 i=polygonsize 在声明循环变量时对其进行初始化,但是如果有一个不错的代码优化器,这应该会消失,并且只是为了确保 i 具有正确的类型。

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

我只是将它作为另一种替代方案来提供,它可以避免担心类型,将函数调用排除在循环之外,并使循环计数器可用于实际操作,例如在更复杂的场景中调试信息。