递归函数或过程是再次调用自己的过程或函数。递归编程的一个优点是程序代码变得更短、更优雅。此外,在许多情况下,递归调用是对其进行编程的唯一方法。在本文中,将用VBA的几个例子来解释递归。VBA没有内置函数来计算数字的阶乘。你可以使用VBA创建计算阶乘的自定义函数,如下:Function FactorialLoop(x As Byte) As Double
Dim i As Byte
FactorialLoop = 1
For i = 1 To x
FactorialLoop = FactorialLoop * i
Next i
End Function
该函数是通过循环来进行计算的。然而,如果使用递归,则代码如下:Function FactorialRecursive(x As Byte) As Double
FactorialRecursive = 1
If x > 1 Then FactorialRecursive = x * FactorialRecursive(x - 1)
End Function
可以看到函数调用自身。该函数使用性质:n=n*(n-1)!下面是一个计算文本字符串/句子中空格的函数示例。用递归函数来实现有点麻烦,事实上,该函数只需要一行代码:Function CountSpaceShort(TextString As String)
CountSpaceShort = Len(TextString) - Len(Replace(TextString, " ", ""))
End Function
但是下面的递归变体很好地展示了如何将可选变量与递归函数结合使用。代码如下:Function CountSpaces(TextString As String, Optional Start As Integer, Optional SpaceCount As Integer) As Integer
Dim i As Long
'首次调用设置函数的Start值为1
If Start = 0 Then Start = 1
i = InStr(Start, TextString, " ")
If i > 0 Then
SpaceCount = SpaceCount + 1
'递归调用该函数
CountSpaces TextString, i + 1, SpaceCount
End If
CountSpaces = SpaceCount
End Function
在这个函数中,使用了2个可选变量。变量Start设置应从单词或句子中的哪个位置搜索下一个空格。递归调用时,该值设置为最近一次找到的空格后的一个位置。可选的SpaceCount变量跟踪空格计数。下面是一个经常使用递归编程的示例。假设要读取现有文件夹中的所有文件名,包括其子文件夹中的文件名。因为事先不知道有多少子文件夹,所以可以通过递归调用该过程来解决这个问题:Sub ReadFilesRecursive(MapName As String)
Dim FileName As String, PathName As String
Dim Subfolders() As String, SubFolderCount As Integer
Dim i As Integer
'确保文件夹名后面总是带有后缀 \
If Right(MapName, 1) <> "\" Then MapName = MapName & "\"
FileName = Dir(MapName & "*.*", vbDirectory)
While Len(FileName) <> 0
If Left(FileName, 1) <> "." Then '当前文件夹
PathName = MapName & FileName
If GetAttr(PathName) = vbDirectory Then
'在数组中保存找到的子文件夹
ReDim Preserve Subfolders(0 To SubFolderCount)
Subfolders(SubFolderCount) = PathName
SubFolderCount = SubFolderCount + 1
Else
Debug.Print MapName & FileName
End If
End If
FileName = Dir()
Wend
'读取具有子文件夹的数组并递归调用过程
For i = 0 To SubFolderCount - 1
ReadFilesRecursive Subfolders(i)
Next i
End Sub
ReadFilesSecure(“D:\完美Excel”)
在此过程中,首先从指定文件夹读取所有文件和子文件夹。如果这是一个子文件夹,则此子文件夹将写入数组。如果是文件名,则使用Debug.Print在立即窗口中打印。读取此文件夹后,将使用存储的子文件夹名称数组作为该函数的参数递归调用该过程。来自这些子文件夹的数据与来自指定文件夹的数据处理方式相同。因此,子文件夹的子文件夹被添加到数组中,文件名显示在立即窗口中。一直到读取了整个数组,因此指定文件夹中的所有文件(包括所有子文件夹)都已写入立即窗口。但是,也可以在不使用递归过程的情况下从文件夹和其子文件夹中读取文件。例如:Sub ReadFilesNOTRecursive()
Dim arr() As String
arr = Split(CreateObject("wscript.shell").exec("cmd /c Dir ""D:\Data\"" /b /s").stdout.readall, vbCrLf)
Range("A1").Resize(UBound(arr) + 1) = Application.Transpose(arr)
End Sub
这个过程使用了DIR函数的两个属性,即/b和/s。属性/b确保不生成摘要信息和列标题,属性/s确保显示所选文件夹中的所有信息,包括所有子文件夹。在此过程中,文件信息显示在活动工作表的A列中。此过程的缺点是文件夹名称也包含在文件名之间,可以通过只读取具有特定扩展名的文件来防止这种情形,例如,将D:\Data\替换为D:\Data\*.xlsx,将显示所有标准的Excel文件,文件夹名称也不会出现在它们之间。如果要显示所有文件名而不显示文件夹名,则可以通过递归过程完成。在某些情况下,递归是一种不可或缺的工具,尤其是在事先不知道必须多久执行一次程序的情况下。例如,在分析树结构时,事先不知道有多少分支。递归中最大的陷阱是它陷入无休止的死循环,这就是为什么认真考虑结束过程非常重要。注:本文整理自worksheetsvba.com,供参考。欢迎在下面留言,完善本文内容,让更多的人学到更完美的知识。欢迎到知识星球:完美Excel社群,进行技术交流和提问,获取更多电子资料,并通过社群加入专门的微信讨论群,更方便交流。