资讯详情

资讯详情

建站行业动态 · 设计趋势 · 数字化升级干货

VBA数组维度与组合:从内存模型到实战应用

VBA数组维度与组合:从内存模型到实战应用 1. 从一次“诡异”的数组赋值错误说起那天下午我正在用VBA处理一份销售数据报表。需求很简单我需要将几个不同区域的月度销售额合并到一个总表里。每个区域的数据都是一个一维数组里面按顺序存放着12个月的销售额。我的第一反应是这不就是要把几个一维数组“拼”成一个二维数组吗行是区域列是月份。于是我信心满满地写下了类似这样的代码Sub MergeDataNaive() Dim regionA() As Variant Dim regionB() As Variant Dim regionC() As Variant Dim combinedArray() As Variant Dim i As Long, j As Long 假设这是三个区域的数据每个数组有12个元素1月到12月 regionA Array(100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 210) regionB Array(90, 95, 100, 105, 110, 115, 120, 125, 130, 135, 140, 145) regionC Array(80, 85, 90, 95, 100, 105, 110, 115, 120, 125, 130, 135) 我“想当然”地认为combinedArray应该是一个3行12列的二维数组 ReDim combinedArray(1 To 3, 1 To 12) 尝试把regionA赋值给第一行 For j 1 To 12 combinedArray(1, j) regionA(j - 1) 注意VBA默认数组是0基我这里是1基 Next j 看起来没问题继续... 但当我试图用一行代码直接赋值时问题来了 combinedArray(2) regionB 这行代码会直接报错下标越界 End Sub代码在combinedArray(2) regionB这一行卡住了抛出一个“下标越界”的运行时错误。我当时就愣住了combinedArray(2)不就是指第二行吗为什么不能直接把一个一维数组regionB整个赋给它这个错误提示像一个路障直接把我挡在了“想当然”的思维捷径之外。它迫使我停下来重新思考在VBA的内存世界里数组到底是如何被组织和访问的“行”和“列”这种我们表格中的直观概念在数组的底层结构里究竟对应着什么这次踩坑让我意识到很多VBA开发者包括当时的我对数组的“维度”和“组合”存在普遍的误解。我们习惯于Excel的单元格网格很容易把二维数组想象成一个完美的、可以按行或按列整体操作的矩形。但VBA的数组机制要更原始、更底层。“一维数组的组合并非二维数组二维数组的组合并非三维数组”这个标题精准地戳中了这个认知盲区。它不是在玩文字游戏而是在描述VBA数组一个根本性的、不同于我们直觉的内存布局规则。理解这个规则是写出高效、稳定且不出错的VBA数组代码的关键一步。接下来我们就彻底拆解这个规则看看数组的“维度”究竟意味着什么以及如何正确地“组合”它们。2. 维度本质VBA数组在内存中如何“排队”要理解为什么不能简单地把几个一维数组拼成一个二维数组我们得先抛开Excel表格的网格视图深入到计算机内存这个最基础的层面来看数组。你可以把计算机的内存想象成一条非常非常长的、带编号的储物柜走廊。每个储物柜内存单元都有一个唯一的地址并且只能存放一个数据比如一个数字、一段文本的引用。VBA中的数组无论它有多少维在物理内存中都是被“拉直”成一条线依次存放在这些连续的储物柜里的。这就是数组的线性存储本质。那么“维度”在这个线性世界里扮演什么角色呢维度其实是一套寻址规则是VBA为了方便我们人类按“行、列、页”这样的多维概念去访问那条线性内存而设计出来的语法糖。一维数组是最简单的。它对应内存中的一段连续空间。声明Dim arr(1 To 5) As LongVBA会向系统申请5个连续的“储物柜”来存放5个Long型整数。我们通过一个下标索引arr(3)就能直接定位到第3个储物柜。它的寻址规则是线性的地址 起始地址 索引 * 元素大小。二维数组的声明例如Dim matrix(1 To 3, 1 To 4) As Long我们告诉VBA“我需要一个3行4列的结构”。但在内存中VBA并不会真的创建一个3x4的网格。它仍然只申请一段连续的内存空间长度是 3 * 4 12 个储物柜。那么当我们访问matrix(2, 3)第2行第3列时VBA是如何知道该去第几个储物柜找数据呢这里就涉及到行优先存储的概念。VBA以及大多数源自C的语言默认采用行优先存储。意思是它在把这12个储物柜分配给这个“逻辑上的”3x4网格时是先放完第一行的所有列再放第二行的所有列以此类推。所以内存中的排列顺序实际上是[第1行第1列], [第1行第2列], [第1行第3列], [第1行第4列], [第2行第1列], [第2行第2列], ... , [第3行第4列]访问matrix(2, 3)时VBA内部会用一个公式计算它在线性序列中的位置位置 (行索引 - 行下界) * 列数 (列索引 - 列下界)代入(2, 3)假设下界都是1列数为4位置 (2-1)*4 (3-1) 1*4 2 6。所以它实际上访问的是这段连续内存中的第6个元素。三维或更高维数组也是同样的原理。声明Dim cube(1 To 2, 1 To 3, 1 To 4) As Long表示一个2页、每页3行4列的结构。在内存中它被拉直成一个长度为 2 * 3 * 4 24 的连续序列。存储顺序是先存第一页的所有行每行按列存完再存第二页的所有行。关键理解matrix(2, 3)中的(2,3)不是一个坐标而是一个经过计算的、指向线性内存中某个特定位置的“快捷方式”。matrix(2)这个写法在VBA语法上是非法的因为VBA要求你必须提供所有维度的索引来唯一确定一个线性位置。你不能只提供“行”索引就指望VBA返回一整“行”数据因为“一行”在内存中并不是一个独立的、可被整体引用的对象它只是我们头脑中的一个逻辑划分。这就是我最初代码combinedArray(2) regionB报错的根本原因。combinedArray是一个二维数组访问它必须提供两个下标(i, j)。combinedArray(2)只提供了一个下标VBA无法将其解析为一个有效的内存地址因此直接抛出“下标越界”错误。它不是在说“第二行不存在”而是在说“你给的地址不完整我找不到”。那么如果我们真的需要把三个一维数组合并成一个类似表格的结构该怎么办呢这引出了“组合”与“嵌套”这两个核心操作。3. 数组的“组合”创建新的维度而非简单拼接当我们谈论数组的“组合”时在VBA的语境下通常指的是通过Array()函数或直接赋值将多个数组作为元素放入一个新的、更高维度的数组中。这与你试图将几个一维数组“拼”成一个二维数组有本质区别。让我们看一个经典的组合示例创建一个“数组的数组”这通常被称为交错数组Jagged ArraySub DemoArrayCombination() 创建三个一维数组 Dim arr1() As Variant: arr1 Array(A1, B1, C1) Dim arr2() As Variant: arr2 Array(A2, B2, C2, D2) 注意长度可以不同 Dim arr3() As Variant: arr3 Array(A3, B3) 关键步骤组合。创建一个新的、一维的Variant数组。 这个新数组的每个元素本身又是一个数组arr1, arr2, arr3。 Dim combined() As Variant combined Array(arr1, arr2, arr3) 此时combined 是一个一维数组长度为3。 Debug.Print combined 数组的维度数: (UBound(combined) - LBound(combined) 1) Debug.Print combined(0) 的类型是数组吗 TypeName(combined(0)) 输出Variant() 访问数据需要两层索引。 combined(0) 获取第一个子数组 arr1 combined(0)(2) 获取 arr1 中的第三个元素 C1 Debug.Print combined(0)(2) 的值是: combined(0)(2) 遍历这个“组合数组” Dim i As Long, j As Long For i LBound(combined) To UBound(combined) Debug.Print 第 i 个子数组; For j LBound(combined(i)) To UBound(combined(i)) Debug.Print combined(i)(j) ; Next j Debug.Print 换行 Next i End Sub运行这段代码你会发现combined并不是一个二维数组。UBound(combined)返回的是2假设默认0基说明它只有一个维度上界是2。但是它的每个元素combined(i)是一个Variant类型而这个Variant里面“装着”一个数组。所以我们需要用combined(i)(j)这种两级索引的方式来访问最终的数据。这种结构与真正的二维数组有何不同内存不连续真正的二维数组Dim true2D(1 To 3, 1 To 4)在内存中是24个连续位置。而上面的combined数组它的三个元素三个指针是连续存放的但每个指针所指向的arr1,arr2,arr3各自在内存中的位置是独立的不一定连续。arr1可能在内存地址1000开始arr2在地址2000开始。子数组长度可变这是交错数组最大的优势。arr1长度是3arr2长度是4arr3长度是2。在一个真正的二维数组里每一行的列数必须相同。这种不规则的数据结构用“数组的数组”来表示非常合适。访问开销访问true2D(i, j)VBA通过一次计算就能定位内存。访问combined(i)(j)VBA需要先找到combined(i)这个指针再根据这个指针找到子数组最后在子数组中计算j的位置。多了一次间接寻址理论上稍慢但对于大多数应用可忽略不计。所以“一维数组的组合”得到的是一个“元素为一维数组”的一维数组即交错数组而不是一个连续的二维数组。标题的前半句在这里得到了验证。同理“二维数组的组合”会得到一个“元素为二维数组”的一维数组或二维数组取决于你怎么组合而不是一个连续的三维数组。Sub Demo2DCombination() 创建两个小的二维数组 Dim sheet1(1 To 2, 1 To 3) As String Dim sheet2(1 To 2, 1 To 3) As String ... 给 sheet1 和 sheet2 赋值 ... 组合创建一个一维数组元素是两个二维数组 Dim workbook(1 To 2) As Variant workbook(1) sheet1 workbook(2) sheet2 访问需要三层索引 workbook(第几个表)(行, 列) 例如workbook(1)(2, 3) 访问第一个表的第2行第3列 End Sub这里的workbook是一个一维数组它的两个元素各自指向一个独立的、内存中可能不连续的二维数组块。它不是一个在内存中连续分布的 2 x 2 x 3 的三维数组。实操心得当你需要处理像“多个长度不一的列表”、“多个结构相同但数据独立的表格”时优先考虑使用这种“Variant数组嵌套基础类型数组”的交错结构。它比ReDim Preserve动态调整一个二维数组的某一维要灵活和高效得多特别是当子数组大小变化频繁时。4. 如何构建真正的多维数组初始化与循环赋值既然直接赋值行不通那我们如何构建一个真正的、内存连续的多维数组呢方法就是最基础的——逐元素初始化或赋值。这没有捷径你必须通过循环来填充每一个“单元格”。回到最初的销售数据合并例子正确的做法应该是这样的Sub BuildTrue2DArray() Dim regionA() As Variant Dim regionB() As Variant Dim regionC() As Variant Dim combinedArray() As Variant Dim i As Long, j As Long Dim numRegions As Long, numMonths As Long 模拟数据 regionA Array(100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 210) regionB Array(90, 95, 100, 105, 110, 115, 120, 125, 130, 135, 140, 145) regionC Array(80, 85, 90, 95, 100, 105, 110, 115, 120, 125, 130, 135) numRegions 3 numMonths 12 正确定义一个真正的二维数组 ReDim combinedArray(1 To numRegions, 1 To numMonths) 方法1最直接的逐元素填充针对本例最清晰 For j 1 To numMonths combinedArray(1, j) regionA(j - 1) 注意索引转换 combinedArray(2, j) regionB(j - 1) combinedArray(3, j) regionC(j - 1) Next j 方法2如果区域数据本身也是数组且数量不定使用双层循环 假设我们把源数据也放在一个“数组的数组”里以便循环 Dim sourceData() As Variant sourceData Array(regionA, regionB, regionC) 这是一个交错数组 ReDim combinedArray2(1 To numRegions, 1 To numMonths) For i 1 To numRegions For j 1 To numMonths sourceData(i-1) 是第i个区域的一维数组 (j-1) 是对应月份在源数组中的索引因为Array()默认产生0基数组 combinedArray2(i, j) sourceData(i - 1)(j - 1) Next j Next i 验证输出第二行第三列的数据应该是regionB的第三个月100 Debug.Print combinedArray(2, 3) combinedArray(2, 3) Debug.Print combinedArray2(2, 3) combinedArray2(2, 3) End Sub对于三维数组原理完全相同。假设我们要按“年-区域-月份”来组织数据Sub BuildTrue3DArray() Dim salesData() As Double Dim years As Long, regions As Long, months As Long Dim y As Long, r As Long, m As Long years 5 5年数据 regions 4 4个区域 months 12 12个月 ReDim salesData(1 To years, 1 To regions, 1 To months) 使用三重循环初始化或填充数据 For y 1 To years For r 1 To regions For m 1 To months 这里可以是读取数据库、计算等操作 salesData(y, r, m) Rnd() * 10000 用随机数模拟 Next m Next r Next y 访问2023年假设是第3年华东区第2个区域6月份的销售额 Dim targetSale As Double targetSale salesData(3, 2, 6) Debug.Print 目标销售额: Format(targetSale, Currency) End Sub关键技巧在定义多维数组时务必明确每一维度的含义例如第一维是年份第二维是产品类别第三维是月份。并在变量命名和注释中体现出来。这能极大避免后续访问时(i, j, k)到底哪个是行、哪个是列的混乱。像salesDataByYearRegionMonth这样的名字虽然长但比简单的arr要清晰得多。5. 性能与内存的深度权衡连续数组 vs 交错数组理解了两种结构的不同我们就能在具体场景中做出明智的选择。这个选择主要围绕性能和灵活性展开。真正的多维数组连续存储的优势内存局部性好数据在内存中连续存放当CPU访问一个元素时其相邻元素有很大概率已经被加载到高速缓存中。这对于需要顺序遍历尤其是按行遍历大量数据的科学计算、图像处理等场景性能提升非常显著。一次性分配ReDim语句一次性分配所有内存内存管理开销小。访问语法简洁array(i, j)的访问方式直观符合我们对表格的认知。劣势不灵活每一维的长度必须固定。虽然可以用ReDim Preserve修改最后一维的大小但这会创建一个全新的数组并复制数据代价高昂且不能修改其他维度。可能浪费空间如果你需要一个“参差不齐”的结构如每行列数不同你必须以最长的那行为准来定义列数导致短行后面存在空位浪费内存。交错数组数组的数组的优势极致灵活每个子数组长度可以独立变化可以轻松实现“参差不齐”的数据结构。添加或删除一行一个子数组也很方便。内存利用高效没有内部碎片每个子数组只占用其实际需要的内存。子数组可独立操作你可以将某个子数组如一行传递给另一个只处理一维数组的函数或过程无需提取数据。劣势访问开销需要多次间接寻址可能比连续数组慢一些。对于超大规模数据或对性能极其敏感的循环这点差异会被放大。内存碎片化每个子数组独立分配可能导致内存碎片。语法稍复杂需要arrayOfArrays(i)(j)这样的两级索引。决策指南场景一处理Excel工作表区域。当你用Range.Value或Range.Value2属性将一个矩形区域读入一个Variant变量时VBA会自动创建一个真正的二维数组即使区域只有一行或一列也是二维的第二维的上界为1。这是最高效的处理方式。你应该保持这种结构用array(row, col)来访问和操作。Dim data As Variant data ThisWorkbook.Worksheets(Sheet1).Range(A1:C10).Value2 data 是一个真正的二维数组场景二存储不规则数据。比如你要存储一个公司所有部门的员工名单每个部门人数不同。这时绝对应该使用交错数组。Dim departments() As Variant Dim hr() As String: hr Array(Alice, Bob) Dim engineering() As String: engineering Array(Charlie, David, Eve) Dim sales() As String: sales Array(Frank) departments Array(hr, engineering, sales)场景三需要频繁增删行或列。如果你需要构建一个结构并且预先不知道大小或者需要频繁在中间插入/删除行使用Collection或Dictionary嵌套数组可能比ReDim Preserve多维数组更高效。但如果最终结构固定且需要高性能计算可以等数据稳定后将其转换到真正的多维数组中。场景四与外部API或库交互。某些API或第三方库可能要求数据是连续的二维数组。这时必须使用真正的多维数组。性能实测小技巧如果你不确定哪种方式更快可以写一个简单的测试宏用Timer函数分别测量两种结构在完成核心操作如遍历求和上的耗时。VBA的性能常常反直觉实测是最可靠的依据。6. 高级应用与避坑实践ReDim、Preserve与数组函数在实际项目中仅仅会声明和赋值还不够我们经常需要动态调整数组大小或者使用VBA内置的数组函数。这里有几个关键的实践点和容易踩的坑。6.1ReDim与Preserve的微妙之处ReDim用于重新定义数组的维度大小但它会清除数组中的所有现有数据。Preserve关键字可以保留原有数据但限制非常严格只能重新定义数组的最后一维的大小。对于一维数组这没问题。对于二维数组你只能改变列数如果最后一维是列或行数如果最后一维是行这取决于你的定义但不能同时改变行数和列数也不能改变除最后一维外的任何维度。使用Preserve重新调大数组时新增的元素会被初始化为该数据类型的默认值如数值为0字符串为空串Variant为Empty。使用Preserve重新调小数组时超出新边界的原有数据将永久丢失。Sub RedimPreservePitfalls() Dim arr() As Integer ReDim arr(1 To 3, 1 To 5) 填充一些数据 arr(2, 3) 99 正确只改变最后一维第二维列的大小 ReDim Preserve arr(1 To 3, 1 To 10) 列从5扩展到10原有数据保留新列初始化为0 Debug.Print arr(2, 3) 输出 99 Debug.Print arr(2, 8) 输出 0 新列 错误尝试改变第一维行的大小同时保留数据 ReDim Preserve arr(1 To 5, 1 To 10) 运行时错误下标越界 如果你需要同时改变行和列并保留数据必须手动操作 Dim newArr() As Integer Dim oldRows As Long, oldCols As Long, newRows As Long, newCols As Long oldRows UBound(arr, 1): oldCols UBound(arr, 2) newRows 5: newCols 7 ReDim newArr(1 To newRows, 1 To newCols) 手动复制数据注意不要超出新旧数组的边界 Dim i As Long, j As Long For i 1 To WorksheetFunction.Min(oldRows, newRows) For j 1 To WorksheetFunction.Min(oldCols, newCols) newArr(i, j) arr(i, j) Next j Next i 现在 newArr 是新的数组部分数据从arr复制而来 End Sub6.2 数组函数UBound,LBound,Array,Split,Filter的维度认知UBound(array, dimension)和LBound(array, dimension)dimension参数是可选的。对于一维数组不写默认是1。对于多维数组必须指定要查询的是第几维的上界/下界。这是一个常见的错误来源忘记UBound返回的是索引上限元素个数需要UBound - LBound 1。Array(arg1, arg2, ...)这个函数非常有用但它返回的始终是一个一维的、0基索引的Variant数组。即使你传入的参数是数组它也只是把这个数组作为一个Variant元素放进去如我们前面“组合”的例子所示。它不会自动进行多维展开。Split(string, delimiter)返回一个一维的、0基索引的字符串数组。这是将字符串解析为数组的最快方法。Filter(sourceArray, match, include, compare)返回一个一维的、0基索引的字符串数组它是源数组的子集。注意它只适用于一维字符串数组。6.3 使用Variant类型接收工作表区域时的维度陷阱这是VBA数组中最经典的坑之一。Sub RangeToArrayTrap() Dim rng As Range Dim data As Variant Set rng ThisWorkbook.Worksheets(Sheet1).Range(A1) 情况1单个单元格 data rng.Value data 是一个标量不是数组TypeName(data) 返回 Double 或 String 因此下面的代码会报错 Debug.Print UBound(data) 错误需要数组 情况2单行或多行区域 Set rng ThisWorkbook.Worksheets(Sheet1).Range(A1:C1) data rng.Value data 是一个二维数组维度为 (1 To 1, 1 To 3) Debug.Print 维度数: 2 Debug.Print 行数 (第一维上界): UBound(data, 1) 输出 1 Debug.Print 列数 (第二维上界): UBound(data, 2) 输出 3 情况3单列区域 Set rng ThisWorkbook.Worksheets(Sheet1).Range(A1:A5) data rng.Value data 仍然是一个二维数组维度为 (1 To 5, 1 To 1) Debug.Print 维度数: 2 Debug.Print 行数 (第一维上界): UBound(data, 1) 输出 5 Debug.Print 列数 (第二维上界): UBound(data, 2) 输出 1 安全遍历的通用方法 Dim i As Long, j As Long If IsArray(data) Then For i LBound(data, 1) To UBound(data, 1) For j LBound(data, 2) To UBound(data, 2) Debug.Print data(i, j) Next j Next i Else 处理单个值的情况 Debug.Print data End If End Sub避坑指南从Range.Value获取数据后永远不要假设data是一个数组。先用IsArray(data)函数判断。如果是数组也永远不要假设它是一维数组。Excel总是返回一个至少是二维的Variant数组即使只有一行或一列。安全的做法是总是使用data(i, j)的双下标形式来访问并通过UBound(data, 1)和UBound(data, 2)来获取行数和列数。7. 实战构建一个灵活的、支持动态扩展的数据缓存结构最后我们综合运用以上知识来设计一个实战中常用的模式一个用于缓存数据的结构它需要支持按“键”快速查找并且每个“键”对应的“值”是一个可以动态增长的一维列表比如按用户ID缓存其操作日志。如果我们用真正的二维数组很难处理每个用户日志数量不同的情况。如果用Collection或Dictionary每个值又是一个数组这正是一个典型的“交错结构”应用场景。这里我们用Scripting.Dictionary对象因为它提供了基于键的快速查找。 需要先引用 Microsoft Scripting Runtime或者使用后期绑定 Option Explicit Sub FlexibleDataCacheDemo() Dim logCache As Object Scripting.Dictionary Set logCache CreateObject(Scripting.Dictionary) 模拟接收到的日志流 Dim userId As String, logMessage As String, timestamp As Date Dim userLogs() As String Dim i As Long 模拟数据流 Dim dataStream As Variant dataStream Array( _ Array(User001, 2023-10-01 09:00, Login), _ Array(User002, 2023-10-01 09:01, Login), _ Array(User001, 2023-10-01 10:30, Purchase Item A), _ Array(User003, 2023-10-01 11:15, Login), _ Array(User001, 2023-10-01 14:00, Logout), _ Array(User002, 2023-10-01 16:45, Update Profile) _ ) For i LBound(dataStream) To UBound(dataStream) userId dataStream(i)(0) timestamp CDate(dataStream(i)(1)) logMessage dataStream(i)(2) 检查用户是否已在缓存中 If Not logCache.Exists(userId) Then 新用户初始化一个空数组一维字符串数组 ReDim userLogs(0 To 0) 先创建一个只有一个元素的数组 userLogs(0) Format(timestamp, yyyy-mm-dd hh:nn) : logMessage logCache.Add userId, userLogs 将数组作为值存入字典 Else 老用户获取其现有的日志数组 userLogs logCache(userId) 注意这里获取的是数组的副本不是引用 扩展数组以容纳新日志 ReDim Preserve userLogs(LBound(userLogs) To UBound(userLogs) 1) userLogs(UBound(userLogs)) Format(timestamp, yyyy-mm-dd hh:nn) : logMessage 将修改后的数组存回字典因为VBA默认是传值所以需要重新赋值 logCache(userId) userLogs End If Next i 输出所有用户的日志 Dim userKey As Variant Dim userLogArray() As String For Each userKey In logCache.Keys Debug.Print 用户: userKey userLogArray logCache(userKey) For i LBound(userLogArray) To UBound(userLogArray) Debug.Print userLogArray(i) Next i Debug.Print Next userKey 查找特定用户的日志 Dim searchUserId As String searchUserId User001 If logCache.Exists(searchUserId) Then Debug.Print 找到用户 searchUserId 的日志共 _ (UBound(logCache(searchUserId)) - LBound(logCache(searchUserId)) 1) 条。 Else Debug.Print 未找到用户 searchUserId End If End Sub在这个例子中logCache字典的值是String()类型的一维数组。每个用户的日志数组长度独立可以自由扩展。这完美体现了“一维数组的组合”思想我们组合了多个一维数组每个用户的日志但并没有将它们强行塞进一个二维数组而是通过字典这个“容器”来管理它们之间的关系。这种结构既灵活又高效。最后的心得VBA数组的维度与组合核心在于理解其内存模型。真正的多维数组是“一块连续的内存用多维索引计算偏移量来访问”而数组的组合嵌套是“一个数组其元素是指向其他内存块的指针”。前者适合规整的、需要高性能连续访问的数据后者适合不规则的、需要动态调整的、或需要将子集作为独立单元传递的数据。掌握这个区别你就能根据具体场景选择最合适的工具避免陷入“为什么我的数组代码这么别扭又容易出错”的困境。下次当你下意识地想用二维数组时先问问自己每一行的长度真的都相等吗未来会变化吗如果答案是否定的或许“数组的数组”或“字典套数组”是更优雅的解。

相关资讯