【问题标题】:What's the best way to do a backwards loop in C/C#/C++?在 C/C#/C++ 中进行反向循环的最佳方法是什么?
【发布时间】:2010-09-21 12:26:17
【问题描述】:

我需要向后移动一个数组,所以我有这样的代码:

for (int i = myArray.Length - 1; i >= 0; i--)
{
    // Do something
    myArray[i] = 42;
}

有更好的方法吗?

更新:我希望 C# 可能有一些内置机制,例如:

foreachbackwards (int i in myArray)
{
    // so easy
}

更新 2:有 更好的方法。 Rune 获奖:

for (int i = myArray.Length; i-- > 0; )
{    
    //do something
}
//or
for (int i = myArray.Length; i --> 0; )
{
    // do something
}

在常规 C 中看起来更好(感谢 Twotymz):

for (int i = lengthOfArray; i--; )
{    
    //do something
}

【问题讨论】:

  • 如果优点包括清晰性或可维护性,我不确定我是否明白为什么任何替代方案都更好。
  • 是的。我希望找到一种更合理的方法来做到这一点,因为我必须经常这样做。
  • 这个问题的大部分内容是对以下答案的回顾。建议大大缩短。
  • 请从标题和主题标签中删除 C 和 C++。

标签: c# c++ c


【解决方案1】:

我这样做

if (list.Count > 0)
    for (size_t i = list.Count - 1; i >= 0; i--)
    {
        //do your thing
    
        if (i == 0) //for preventing unsigned wrap
            break;
    }

但由于某种原因,Visual Studio 2019 生气并警告我“定义不明确的循环”之类的......它不信任我

【讨论】:

    【解决方案2】:

    对于 C++:

    正如其他人所提到的,如果可能(即一次只需要每个元素),强烈建议使用迭代器来明确并避免常见的陷阱。现代 C++ 有更简洁的语法,auto:

    std::vector<int> vec = {1,2,3,4};
    for (auto it = vec.rbegin(); it != vec.rend(); ++it) {
        std::cout<<*it<<" ";
    }
    

    打印4 3 2 1 。

    你也可以在循环中修改值:

    std::vector<int> vec = {1,2,3,4};
    for (auto it = vec.rbegin(); it != vec.rend(); ++it) {
        *it = *it + 10;
        std::cout<<*it<<" ";
    }
    

    导致14 13 12 11 被打印出来,{11, 12, 13, 14} 在之后出现在std::vector 中。

    如果您不打算在循环期间修改该值,则应确保在尝试执行此操作时会遇到错误,类似于人们可能会写 for(const auto&amp; element : vec) 的方式。这可能是这样的:

    std::vector<int> vec = {1,2,3,4};
    for (auto it = vec.crbegin(); it != vec.crend(); ++it) { // used crbegin()/crend() here...
        *it = *it + 10; // ... so that this is a compile-time error
        std::cout<<*it<<" ";
    }
    

    对我来说,这种情况下的编译器错误是:

    /tmp/main.cpp:20:9: error: assignment of read-only location ‘it.std::reverse_iterator<__gnu_cxx::__normal_iterator<const int*, std::vector<int> > >::operator*()’
       20 |     *it = *it + 10;
          |     ~~~~^~~~~~~~~~
    

    另外请注意,您应该确保不要同时使用不同的迭代器类型:

    std::vector<int> vec = {1,2,3,4};
    for (auto it = vec.rbegin(); it != vec.end(); ++it) { // mixed rbegin() and end()
        std::cout<<*it<<" ";
    }
    

    导致详细错误:

    /tmp/main.cpp: In function ‘int main()’:
    /tmp/main.cpp:19:33: error: no match for ‘operator!=’ (operand types are ‘std::reverse_iterator<__gnu_cxx::__normal_iterator<int*, std::vector<int> > >’ and ‘std::vector<int>::iterator’ {aka ‘__gnu_cxx::__normal_iterator<int*, std::vector<int> >’})
       19 | for (auto it = vec.rbegin(); it != vec.end(); ++it) {
          |                              ~~ ^~ ~~~~~~~~~
          |                              |            |
          |                              |            std::vector<int>::iterator {aka __gnu_cxx::__normal_iterator<int*, std::vector<int> >}
          |                              std::reverse_iterator<__gnu_cxx::__normal_iterator<int*, std::vector<int> > >
    

    如果堆栈上有 C 样式的数组,则可以执行以下操作:

    int vec[] = {1,2,3,4};
    for (auto it = std::crbegin(vec); it != std::crend(vec); ++it) {
        std::cout<<*it<<" ";
    }
    

    如果您确实需要索引,请考虑以下选项:

    • 检查范围,然后使用有符号值,例如:
    void loop_reverse(std::vector<int>& vec) {
        if (vec.size() > static_cast<size_t>(std::numeric_limits<int>::max())) {
            throw std::invalid_argument("Input too large");
        }
        const int sz = static_cast<int>(vec.size());
        for(int i=sz-1; i >= 0; --i) {
            // do something with i
        }
    }
    
    • 使用无符号值,小心并添加 cmets,例如:
    void loop_reverse2(std::vector<int>& vec) {
        for(size_t i=vec.size(); i-- > 0;) { // reverse indices from N-1 to 0
            // do something with i
        }
    }
    
    • 单独计算实际索引,例如:
    void loop_reverse3(std::vector<int>& vec) {
        for(size_t offset=0; offset < vec.size(); ++offset) {
            const size_t i = vec.size()-1-offset; // reverse indices from N-1 to 0
            // do something with i
        }
    }
    

    【讨论】:

      【解决方案3】:

      我更喜欢 while 循环。这对我来说比在 for 循环的条件下减少 i 更清楚

      int i = arrayLength;
      while(i)
      {
          i--;
          //do something with array[i]
      }
      

      【讨论】:

        【解决方案4】:

        在 C++ 中,您基本上可以选择使用迭代器或索引进行迭代。 根据您使用的是普通数组还是std::vector,您可以使用不同的技术。

        使用 std::vector

        使用迭代器

        C++ 允许您使用 std::reverse_iterator: 来执行此操作

        for(std::vector<T>::reverse_iterator it = v.rbegin(); it != v.rend(); ++it) {
            /* std::cout << *it; ... */
        }
        

        使用索引

        std::vector&lt;T&gt;::size 返回的无符号整数类型不是总是std::size_t。它可以更大或更小。这对于循环工作至关重要。

        for(std::vector<int>::size_type i = someVector.size() - 1; 
            i != (std::vector<int>::size_type) -1; i--) {
            /* std::cout << someVector[i]; ... */
        }
        

        它可以工作,因为无符号整数类型的值是通过对它们的位数取模来定义的。因此,如果你设置-N,你最终会得到(2 ^ BIT_SIZE) -N

        使用数组

        使用迭代器

        我们正在使用std::reverse_iterator 进行迭代。

        for(std::reverse_iterator<element_type*> it(a + sizeof a / sizeof *a), itb(a); 
            it != itb; 
            ++it) {
            /* std::cout << *it; .... */
        }
        

        使用索引

        我们可以在这里安全地使用std::size_t,而不是上面的,因为sizeof总是根据定义返回std::size_t。

        for(std::size_t i = (sizeof a / sizeof *a) - 1; i != (std::size_t) -1; i--) {
           /* std::cout << a[i]; ... */
        }
        

        避免将 sizeof 应用于指针的陷阱

        实际上,上述确定数组大小的方法很糟糕。如果 a 实际上是一个指针而不是一个数组(这种情况经常发生,初学者会混淆它),它会默默地失败。更好的方法是使用以下方法,如果给定指针,它将在编译时失败:

        template<typename T, std::size_t N> char (& array_size(T(&)[N]) )[N];
        

        它的工作原理是首先获取传递数组的大小,然后声明返回对相同大小的 char 类型数组的引用。 char 被定义为具有 sizeof 的:1。因此返回的数组将具有 sizeof 的:N * 1,这就是我们正在寻找的,只有编译时评估和零运行时开销。

        而不是做

        (sizeof a / sizeof *a)
        

        更改您的代码,使其现在可以使用

        (sizeof array_size(a))
        

        【讨论】:

        • 另外,如果你的容器没有反向迭代器,你可以使用 boost 的 reverse_iterator 实现:boost.org/doc/libs/1_36_0/libs/iterator/doc/…
        • 您的 array_size 似乎适用于静态分配的数组,但例如当 a 为 'new int[7]' 时对我来说失败了。
        • 是的,这就是它的意图:) new int[7] 返回一个指针。所以 sizeof(new int[7]) 不返回 7 * sizeof(int) ,而是返回 sizeof(int*) 。 array_size 在这种情况下会导致编译错误,而不是静默工作。
        • 也可以尝试 array_size(+statically_allocated_array) ,这也失败了,因为 + 运算符使数组成为指向其第一个元素的指针。使用普通的 sizeof 会再次给你一个指针的大小
        • 首先——为什么不把end和begin倒序使用?
        【解决方案5】:

        我会总是更喜欢清晰的代码而不是“排版令人愉悦”的代码。 因此,我会一直使用:

        for (int i = myArray.Length - 1; i >= 0; i--)  
        {  
            // Do something ...  
        }    
        

        您可以将其视为向后循环的标准方式。
        就我的两分钱...

        【讨论】:

        • 工作。尚未在 C# 中完成此操作,但谢谢。
        • 那么如果数组真的很大(所以索引必须是无符号类型)怎么办? size_t 实际上是无符号的,不是吗?
        • 您可以将 i 声明为 uint。参见 Marc Gravell 的帖子。
        • 由于换行而无法用于无符号类型,这与印刷上令人愉悦的类型不同。不好。
        • @lalala 你可以使用long long。这至少有 64 位,所以你的数组可以有多达 2^63 个条目。这应该会做一段时间......
        【解决方案6】:
        // this is how I always do it
        for (i = n; --i >= 0;){
           ...
        }
        

        【讨论】:

          【解决方案7】:

          注意:这篇文章最终要详细得多,因此偏离主题,我深表歉意。

          话虽如此,我的同龄人读过它并相信它在“某处”很有价值。这个线程不是地方。感谢您对这应该去哪里提供反馈(我是该网站的新手)。


          无论如何,这是 .NET 3.5 中的 C# 版本,令人惊奇的是,它可以使用定义的语义在任何集合类型上工作。这是一种默认度量(重用!),而不是大多数常见开发场景中的性能或 CPU 周期最小化,尽管这似乎从未在现实世界中发生过(过早优化)。

          *** 扩展方法适用于任何集合类型,并采取一个期望该类型的单个值的动作委托,所有这些都在每个项目上反向执行**

          要求 3.5:

          public static void PerformOverReversed<T>(this IEnumerable<T> sequenceToReverse, Action<T> doForEachReversed)
                {
                    foreach (var contextItem in sequenceToReverse.Reverse())
                        doForEachReversed(contextItem);
                }
          

          较早的 .NET 版本还是您想更好地了解 Linq 内部?继续阅读.. 或者不..

          假设:在 .NET 类型系统中,Array 类型继承自 IEnumerable 接口(不是泛型 IEnumerable,只有 IEnumerable)。

          这就是您从头到尾迭代所需的全部内容,但是您想朝相反的方向移动。由于 IEnumerable 适用于“对象”类型的数组,因此任何类型都有效,

          关键措施:我们假设您是否可以以“更好”的相反顺序处理任何序列,然后只能处理整数。

          .NET CLR 2.0-3.0 的解决方案 a:

          描述:我们将接受任何 IEnumerable 实现实例,并要求它包含的每个实例都属于同一类型。因此,如果我们收到一个数组,则整个数组包含 X 类型的实例。如果任何其他实例的类型为 !=X,则会引发异常:

          单例服务:

          公共类 ReverserService { 私有 ReverserService() { }

              /// <summary>
              /// Most importantly uses yield command for efficiency
              /// </summary>
              /// <param name="enumerableInstance"></param>
              /// <returns></returns>
              public static IEnumerable ToReveresed(IEnumerable enumerableInstance)
              {
                  if (enumerableInstance == null)
                  {
                      throw new ArgumentNullException("enumerableInstance");
                  }
          
                  // First we need to move forwarad and create a temp
                  // copy of a type that allows us to move backwards
                  // We can use ArrayList for this as the concrete
                  // type
          
                  IList reversedEnumerable = new ArrayList();
                  IEnumerator tempEnumerator = enumerableInstance.GetEnumerator();
          
                  while (tempEnumerator.MoveNext())
                  {
                      reversedEnumerable.Add(tempEnumerator.Current);
                  }
          
                  // Now we do the standard reverse over this using yield to return
                  // the result
                  // NOTE: This is an immutable result by design. That is 
                  // a design goal for this simple question as well as most other set related 
                  // requirements, which is why Linq results are immutable for example
                  // In fact this is foundational code to understand Linq
          
                  for (var i = reversedEnumerable.Count - 1; i >= 0; i--)
                  {
                      yield return reversedEnumerable[i];
                  }
              }
          }
          
          
          
          public static class ExtensionMethods
          {
          
                public static IEnumerable ToReveresed(this IEnumerable enumerableInstance)
                {
                    return ReverserService.ToReveresed(enumerableInstance);
                }
           }
          

          [测试夹具] 公开课Testing123 {

              /// <summary>
              /// .NET 1.1 CLR
              /// </summary>
              [Test]
              public void Tester_fornet_1_dot_1()
              {
                  const int initialSize = 1000;
          
                  // Create the baseline data
                  int[] myArray = new int[initialSize];
          
                  for (var i = 0; i < initialSize; i++)
                  {
                      myArray[i] = i + 1;
                  }
          
                  IEnumerable _revered = ReverserService.ToReveresed(myArray);
          
                  Assert.IsTrue(TestAndGetResult(_revered).Equals(1000));
              }
          
              [Test]
              public void tester_why_this_is_good()
              {
          
                  ArrayList names = new ArrayList();
                  names.Add("Jim");
                  names.Add("Bob");
                  names.Add("Eric");
                  names.Add("Sam");
          
                  IEnumerable _revered = ReverserService.ToReveresed(names);
          
                  Assert.IsTrue(TestAndGetResult(_revered).Equals("Sam"));
          
          
              }
          
              [Test]
              public void tester_extension_method()
            {
          
                  // Extension Methods No Linq (Linq does this for you as I will show)
                  var enumerableOfInt = Enumerable.Range(1, 1000);
          
                  // Use Extension Method - which simply wraps older clr code
                  IEnumerable _revered = enumerableOfInt.ToReveresed();
          
                  Assert.IsTrue(TestAndGetResult(_revered).Equals(1000));
          
          
              }
          
          
              [Test]
              public void tester_linq_3_dot_5_clr()
              {
          
                  // Extension Methods No Linq (Linq does this for you as I will show)
                  IEnumerable enumerableOfInt = Enumerable.Range(1, 1000);
          
                  // Reverse is Linq (which is are extension methods off IEnumerable<T>
                  // Note you must case IEnumerable (non generic) using OfType or Cast
                  IEnumerable _revered = enumerableOfInt.Cast<int>().Reverse();
          
                  Assert.IsTrue(TestAndGetResult(_revered).Equals(1000));
          
          
              }
          
          
          
              [Test]
              public void tester_final_and_recommended_colution()
              {
          
                  var enumerableOfInt = Enumerable.Range(1, 1000);
                  enumerableOfInt.PerformOverReversed(i => Debug.WriteLine(i));
          
              }
          
          
          
              private static object TestAndGetResult(IEnumerable enumerableIn)
              {
                //  IEnumerable x = ReverserService.ToReveresed(names);
          
                  Assert.IsTrue(enumerableIn != null);
                  IEnumerator _test = enumerableIn.GetEnumerator();
          
                  // Move to first
                  Assert.IsTrue(_test.MoveNext());
                  return _test.Current;
              }
          }
          

          【讨论】:

          • Dude or dudette:我只是在寻找一种不那么笨拙的书写方式“for (int i = myArray.Length - 1; i >= 0; i--)”。
          • 这个回复毫无价值
          【解决方案8】:

          虽然有点晦涩难懂,但我想说的是,最令人愉悦的排版方式是

          for (int i = myArray.Length; i --> 0; )
          {
              //do something
          }
          

          【讨论】:

          • 当我第一次阅读你的答案时,它看起来甚至无法编译,所以我认为你是一个疯狂的人。但这正是我一直在寻找的:一种编写反向 for 循环的更好方法。
          • 我认为我 --> 0;是故意的。这就是他所说的“令人愉悦的排版”
          • 我自己发现它“在排版上令人困惑”。对我来说,“--”看起来不正确,除非它与它所影响的变量相邻。
          • 这太晦涩难懂了。我永远不会在生产代码中写这样的东西......
          • 啊,goes to operator (-->) 成功了!
          【解决方案9】:

          在 C 中我喜欢这样做:

          int i = myArray.Length; while (i--) { myArray[i] = 42; }

          MusiGenesis 添加的 C# 示例:

          {int i = myArray.Length; while (i-- > 0)
          {
              myArray[i] = 42;
          }}
          

          【讨论】:

          • 我喜欢这个。我花了一两秒钟才意识到你的方法有效。希望您不介意添加的 C# 版本(额外的大括号使索引的范围与 for 循环中的范围相同)。
          • 我不确定我是否会在生产代码中使用它,因为它会引发一个普遍的“WTF 这是?”必须维护它的人的反应,但实际上它比普通的 for 循环更容易输入,并且没有减号或 >= 符号。
          • 可惜 C# 版本需要额外的 " > 0 "。
          • 我不确定它是什么语言。但是您的第一个代码 sn-p 肯定不是 C :) 只是为了告诉您显而易见的事情。也许你想写 C# 并且 html 无法解析它或其他什么?
          • @litb:我猜“myArray.Length”不是有效的 C(?),但是 while 循环部分可以工作并且看起来很棒。
          【解决方案10】:

          在 C# 中,使用 Visual Studio 2005 或更高版本,键入“forr”并点击 [TAB] [TAB]。这将扩展为一个 for 循环,该循环向后遍历一个集合。

          很容易出错(至少对我来说),我认为把这个 sn-p 放进去是个好主意。

          也就是说,我喜欢 Array.Reverse() / Enumerable.Reverse() 然后更好地迭代转发 - 他们更清楚地表明了意图。

          【讨论】:

            【解决方案11】:

            在 C++ 中做到这一点的最佳方法可能是使用迭代器(或更好的范围)适配器,它会在遍历序列时延迟转换。

            基本上,

            vector<value_type> range;
            foreach(value_type v, range | reversed)
                cout << v;
            

            以相反的顺序显示范围“范围”(这里是空的,但我相当确定您可以自己添加元素)。 当然,简单地迭代范围并没有多大用处,但是将新范围传递给算法和其他东西非常酷。

            这种机制还可以用于更强大的用途:

            range | transformed(f) | filtered(p) | reversed
            

            将懒惰地计算范围“范围”,其中函数“f”应用于所有元素,删除“p”不为真的元素,最后将结果范围反转。

            管道语法是最易读的 IMO,因为它是中缀的。 Boost.Range 库更新待审核实现了这一点,但自己也很简单。使用 lambda DSEL 生成函数 f 和内联谓词 p 更酷。

            【讨论】:

            • 你能告诉我们你的“foreach”来自哪里吗?它是 BOOST_FOREACH 的 #define 吗?一路看起来很可爱。
            【解决方案12】:

            在 C# 中使用 Linq:

            foreach(var item in myArray.Reverse())
            {
                // do something
            }
            

            【讨论】:

            • 这肯定是最简单的,但请注意,在当前版本的 CLR 中,反转列表始终是 O(n) 操作,而不是 O(1) 操作,如果它是一个 IList;见connect.microsoft.com/VisualStudio/feedback/…
            • 看起来 .Reverse() 制作了数组的本地副本,然后向后迭代。复制一个数组似乎有点低效,以便您可以遍历它。我想任何带有“Linq”的帖子都值得投票。 :)
            • 有一个可能的折衷方案,但随后您会遇到“对答案投票”(i --> 0) 可能会导致代码变慢的问题,因为 (i) 必须与每次在索引中使用时数组的大小。
            • 虽然我同意 Linq 很棒,但这种方法的问题是迭代器不允许您有选择地挑选出数组中的项目 - 考虑一下您想要浏览一部分的情况一个数组,但不是全部...
            【解决方案13】:

            我会使用原始问题中的代码,但如果你真的想使用 foreach 并在 C# 中有一个整数索引:

            foreach (int i in Enumerable.Range(0, myArray.Length).Reverse())
            {
                myArray[i] = 42; 
            }
            

            【讨论】:

              【解决方案14】:

              我觉得不错。如果索引器未签名(uint 等),您可能必须考虑到这一点。叫我懒惰,但在那种(无符号)情况下,我可能只使用一个反变量:

              uint pos = arr.Length;
              for(uint i = 0; i < arr.Length ; i++)
              {
                  arr[--pos] = 42;
              }
              

              (实际上,即使在这里,您也需要小心诸如 arr.Length = uint.MaxValue 之类的情况......也许是 != 某处......当然,这种情况不太可能发生!)

              【讨论】:

              • 不过,“--pos”这个东西很棘手。过去我的同事喜欢随机“纠正”代码中看起来不太正确的内容。
              • 然后使用 .arr.Length-1 和 pos--
              【解决方案15】:

              我将尝试在这里回答我自己的问题,但我也不喜欢这样:

              for (int i = 0; i < myArray.Length; i++)
              {
                  int iBackwards = myArray.Length - 1 - i; // ugh
                  myArray[iBackwards] = 666;
              }
              

              【讨论】:

              • 而不是 .Length - 1 - i 每次,也许考虑第二个变量?请参阅我的 [更新] 帖子。
              • 对我自己的问题投了反对票。残酷的。它并不比原版笨重。
              【解决方案16】:

              对于任何长度为有符号整数类型的数组来说,这绝对是最好的方法。对于长度为无符号整数类型的数组(例如 C++ 中的 std::vector),则需要稍微修改结束条件:

              for(size_t i = myArray.size() - 1; i != (size_t)-1; i--)
                  // blah
              

              如果你刚才说i &gt;= 0,对于无符号整数总是如此,因此循环将是一个无限循环。

              【讨论】:

              • 在 C++ 中,如果 i 为 0,“i--”会自动回绕到最大值吗?抱歉,我是 C++ 障碍。
              • 太棒了。你们只是帮助我了解了我 12 年前写的东西中硬盘填充错误的原因。好在我不使用 C++。
              • 终止条件应该是:i
              • 我认为对我来说更好的解决方案是留在我不会伤害任何人的 C# 世界中。
              猜你喜欢
              • 1970-01-01
              • 2012-08-16
              • 1970-01-01
              • 2018-08-05
              • 1970-01-01
              • 1970-01-01
              • 2010-09-07
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多