【问题标题】:Iteration over std::vector: unsigned vs signed index variable迭代 std::vector:无符号与有符号索引变量
【发布时间】:2010-09-29 09:21:23
【问题描述】:

在 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 是 unsigned 的别名。
  • @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 就会拥有不可移植的代码。

标签: c++ stl unsigned signed


【解决方案1】:

有关向后迭代,请参阅this answer

向前迭代几乎是相同的。只需按增量更改迭代器/交换减量。您应该更喜欢迭代器。有人告诉你使用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(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 :) 我不明白你。如果我要相互减去指针,difference_type 将是正确的选择。
  • 如果在传递给该函数的数组上的函数中执行迭代,则使用您在本文中提到的技术对数组进行迭代将不起作用。因为 sizeof 数组只会返回 sizeof 指针。
  • @Nils 我同意使用无符号循环计数器是个坏主意。但是因为标准库使用无符号整数类型来表示索引和大小,所以我更喜欢标准库的无符号索引类型。因此,其他库仅使用签名类型,例如 Qt 库。
  • C++11 更新:基于范围的 for 循环。 for (auto p : polygon){sum += p;}
【解决方案2】:

四年过去了,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&amp; val: vec)
  • @flexo 谢谢,我不知道我怎么能忘记这一点。我猜C++做得不够。不敢相信有什么实用的东西(实际上以为是 JavaScript 语法)。我更改了答案以包含它。
  • 你的回答很好。令人不快的是,各种 OS devkits 中 g++ 的默认版本低于 4.3,这使其无法正常工作。
  • 您需要用std::vector&lt;int&gt; v = std::vector&lt;int&gt;(); 初始化向量,还是直接使用std::vector&lt;int&gt; v; 来代替?
  • @BillCheatham 好吧 - 我只是在没有初始化的情况下尝试了它,它确实有效,所以它似乎没有。
【解决方案3】:

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

【讨论】:

    【解决方案4】:

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

    【讨论】:

    • 您应该详细说明这仅适用于 T 是模板参数的情况,因此表达式 std::vector&lt;T*&gt;::iterator 是从属名称。如诊断所示,要将依赖名称解析为类型,需要在其前面加上 typename 关键字。
    【解决方案5】:

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

    通常在 C++ 中对容器的迭代也是使用 iterators 完成的,就像这样。

    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++)
    【解决方案6】:

    使用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 &lt;cstddef&gt; 而不是&lt;stddef.h&gt;,或者更糟糕的是,整个[c]stdlib,并使用std::size_t 而不是不合格的版本——对于任何其他情况,你在&lt;cheader&gt;&lt;header.h&gt; 之间进行选择。
    【解决方案7】:

    一点历史:

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

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

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

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

    【讨论】:

      【解决方案8】:

      我通常使用 BOOST_FOREACH:

      #include <boost/foreach.hpp>
      
      BOOST_FOREACH( vector_type::value_type& value, v ) {
          // do something with 'value'
      }
      

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

      【讨论】:

      • 其他问题的好答案(我应该如何迭代向量?),但完全不是 OP 所要求的(关于无符号变量的警告是什么意思?)跨度>
      • 好吧,他问迭代向量的正确方法是什么。所以似乎足够相关。警告正是他对当前解决方案不满意的原因。
      【解决方案9】:

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

      【讨论】:

        【解决方案10】:

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

        【讨论】:

          【解决方案11】:
          for (vector<int>::iterator it = polygon.begin(); it != polygon.end(); it++)
              sum += *it; 
          

          【讨论】:

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

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

          【讨论】:

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

          考虑是否需要迭代

          &lt;algorithm&gt; 标准标头为我们提供了以下功能:

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

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

          【讨论】:

            【解决方案14】:

            晦涩但重要的细节:如果你说“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)
            

            【讨论】:

              【解决方案15】:

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

              vector<float> vertices{ 1.0, 2.0, 3.0 };
              
              for(float vertex: vertices){
                  std::cout << vertex << " ";
              }
              

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

              【讨论】:

                【解决方案16】:

                这两个代码段的工作方式相同。但是,无符号整数“路线是正确的。使用无符号整数类型将更好地与您使用它的实例中的向量一起使用。在向量上调用 size() 成员函数会返回一个无符号整数值,因此您希望比较变量"i" 为它自己类型的值。

                另外,如果您仍然对“unsigned int”在您的代码中的外观有些不安,请尝试“uint”。这基本上是“unsigned int”的缩短版本,它的工作原理完全相同。您也不需要包含其他标题来使用它。

                【讨论】:

                • 无符号整数 size() 不一定等于 C++ 术语中的“无符号整数”,在这种情况下,“无符号整数”通常是 64 位无符号整数,而“无符号整数”通常是 32 位。
                【解决方案17】:

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

                示例

                for(decltype(v.size()) i{ 0 }; i < v.size(); i++) {
                    /* std::cout << v[i]; ... */
                }
                

                【讨论】:

                  【解决方案18】:
                  auto polygonsize = polygon.size(), i=polygonsize;
                  for (i=0; i < polygonsize; i++) {
                      sum += polygon[i];
                  }
                  

                  这个

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

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

                  我只是将它作为另一种选择提供,它避免了担心类型,将函数调用从循环中取出,并使循环计数器可用于实际操作,例如在更复杂的场景中调试信息。

                  【讨论】:

                    猜你喜欢
                    • 2011-03-04
                    • 2012-05-07
                    • 2010-10-11
                    • 2015-04-21
                    • 2011-02-01
                    • 2015-12-28
                    • 2023-04-05
                    • 2016-10-09
                    • 1970-01-01
                    相关资源
                    最近更新 更多