【问题标题】:Reverse order of For Each loopFor Each 循环的逆序
【发布时间】:2014-08-13 00:52:21
【问题描述】:

VB 最强大的功能之一是能够在不引用索引的情况下循环遍历集合中的对象 - for each 循环。

我发现只想从集合中删除对象非常有用。

从预定义的对象(例如电子表格上的行)中删除对象时,如果我使用索引并从最大开始并返回到第一个,则代码会更简单。 (带迭代器的步骤-1)(否则需要一个偏移量,因为一旦删除活动对象,For each 会将枚举器指针移回前一个对象)

例如。

For intA = 10 to 1 step -1 
    ' ...
Next

使用 For Each | 时会怎样?下一个 例如。

For each rngCell in Selection.Cells
    ' ...
Next

如何使用for each 循环语法向后循环

【问题讨论】:

  • 您可以简单地反转集合并执行 ForEach :)
  • 我什至不知道你能做到这一点:stackoverflow.com/questions/4203329/…。很高兴知道。
  • @VBlades,这很有趣,但是对于这个问题,OP 不会仅仅使用新集合就面临同样的问题吗?我认为在像这样删除 VBA 集合中的对象时,最好的解决方案是只使用集合索引和计数器。
  • @DougGlancy:是的,也许是道格。 Tbh,我在工作,只是发现它很新颖,可能与 OP 的问题有关,所以我把它链接了。不过,我认为更大的原则是 For...Each 构造专门针对那些顺序不重要的情况(stackoverflow.com/questions/952136/…);有点像带有数据行的集合论——本质上是无序的。这意味着是的,如果您需要按特定顺序执行操作,请使用您正在使用的集合提供的计数器。
  • 问您为什么要这样做 - 所需的变通办法会使代码的效率低于 Step 替代方案?

标签: vba excel foreach


【解决方案1】:

使用 for each 循环语法无法向后循环。

作为替代方法,您可以使用For i = a To 1 Step -1 循环:

Sub reverseForEach()
    Dim i As Long, rng As Range

    Set rng = ActiveSheet.Range("A1:B2")

    For i = rng.Cells.Count To 1 Step -1

        Debug.Print rng.item(i).Address
        ' Or shorthand rng(i) as the Item property 
        ' is the default property for the Range object.
        ' Prints: $B$2, $A$2, $B$1, $A$1

    Next i

End Sub

这适用于所有具有 Item 属性的集合。例如工作表、区域或形状。

注意:在 Range 对象上使用时循环的顺序是从右到左,然后向上。

【讨论】:

    【解决方案2】:

    对于内置集合(例如Range),简短的回答是:你不能。对于用户定义的集合,@VBlades 链接的答案可能有用,尽管成本可能超过收益。

    一种解决方法是将要删除的项目的标识与实际删除分开。例如,对于一个范围,使用Union 建立一个新的范围变量,然后处理该变量,例如一次性删除所有行。对于Range 示例,您还可以利用Variant Array 方法进一步加快速度。

    这是否有用将取决于您的实际用例。

    【讨论】:

    • 这是答案“对于内置集合(例如范围),简短的回答是:你不能。对于用户定义的集合,@VBlades 链接的答案可能很有用,尽管成本可能利大于弊。”
    • 简而言之,即使每次删除时都无法使用集合反转,因为 For Each 将枚举器指针移回前一个对象,一旦删除活动对象。 IE。引入偏移错误删除的东西越多,这变得越大,因此偏移量需要与计数器一起使用。首先使用索引和反向计数器要简单得多。
    【解决方案3】:

    还有其他很好的答案,但这里有另一种在 Range 中“后退”的替代方法。


    将范围反转为数组的功能

    这个函数返回一个"backwards Range Array",可以和For..Each一起使用:

    Function ReverseRange(rg As Range) As Range()
        Dim arr() As Range, r As Long, c As Long, n As Long
        With rg
            ReDim arr(1 To .Cells.Count) 'resize Range Array
            For r = .Cells(.Rows.Count, 1).Row To .Cells(1, 1).Row Step -1
                For c = .Cells(1, .Columns.Count).Column To .Cells(1, 1).Column Step -1
                    n = n + 1
                    Set arr(n) = .Worksheet.Cells(r, c) 'set cell in Array
                Next c
            Next r
        End With
        ReverseRange = arr  'return Range Array as function result
    End Function
    

    示例用法:

    Sub test()
        Dim oCell
        For Each oCell In ReverseRange(ActiveSheet.Range("E5:A1"))
    
            Debug.Print oCell.Address 'do something here with each cell
    
        Next oCell
    End Sub
    

    【讨论】:

      【解决方案4】:

      使用设置为您想要的计数器的第二个变量并在您的代码中使用这个变量

      'ex: Loop from n = 19 to 16
      For i = 0 To 3
         n = 19 - i
         'your code here using n as the counter
      Next
      

      【讨论】:

        【解决方案5】:

        仅适用于 Range 集合。如果它们有超过 1 个区域,它们会更复杂。

        基本上有两个循环,第一个保持数组中所有单元格的索引,第二个从后到前创建范围的联合

        Option Explicit
        
        Private Sub Main()
            Dim InvertedRange As Range
            Set InvertedRange = InvertRange(Application.Union(ActiveSheet.Range("A1:A2"), _
              ActiveSheet.Range("F6:F7"), ActiveSheet.Range("E4:F5"), ActiveSheet.Range("E1")))
            Dim ActualRange As Range
            For Each ActualRange In InvertedRange
                Debug.Print (ActualRange.Address(False, False) & " : " & ActualRange.Value)
            Next ActualRange
        End Sub
        
        Public Function InvertRange(ByVal rngRange_I As Range) As Range
            Dim RangesArray() As Long
            ReDim RangesArray(1 To rngRange_I.Count, 1 To rngRange_I.Count)
            Dim ActualArea As Range
            Dim ActualRange As Range
            Dim ArrayIndex As Long
            For Each ActualArea In rngRange_I.Areas
                For Each ActualRange In ActualArea
                    ArrayIndex = ArrayIndex + 1
                    RangesArray(ArrayIndex, 1) = ActualRange.Row
                    RangesArray(ArrayIndex, 2) = ActualRange.Column
                Next ActualRange
            Next ActualArea
        
            Dim ActualRow As Long
            Dim ActualColumn As Long
            ActualRow = RangesArray(UBound(RangesArray, 1), 1)
            ActualColumn = RangesArray(UBound(RangesArray, 2), 2)
            With rngRange_I.Worksheet
                Dim InvertedRange As Range
                Set InvertedRange = .Cells(ActualRow, ActualColumn)
                For ArrayIndex = UBound(RangesArray, 1) To LBound(RangesArray, 1) Step -1
                    ActualRow = RangesArray(ArrayIndex, 1)
                    ActualColumn = RangesArray(ArrayIndex, 2)
                    Set InvertedRange = Application.Union(InvertedRange, _
                      .Cells(ActualRow, ActualColumn))
                Next ArrayIndex
            End With
        
            Set InvertRange = InvertedRange
        End Function
        

        【讨论】:

          猜你喜欢
          • 2015-11-12
          • 1970-01-01
          • 1970-01-01
          • 2012-10-11
          • 1970-01-01
          • 1970-01-01
          • 2015-11-10
          • 2014-10-19
          • 1970-01-01
          相关资源
          最近更新 更多