简介:这份VBA编程练习题与近年真题文档面向正在学习或备考VBA的Office用户、计算机二级考生及办公自动化爱好者,帮助其通过真题演练掌握语法要点与常见考点。资源共1个doc文件,压缩包约780KB,内容以题目与解析为主,涵盖Do Until、Do Loop While等循环结构,Select Case多分支选择,ByVal与ByRef参数传递差异,以及DAO、ADO数据访问对象等核心概念。文档中每道题均配有答案与思路说明,例如循环终止条件填写、字符串长度计算、星号图形输出、双精度类型标识等,便于读者对照练习、查漏补缺。目前已有1810人学习下载,适合需要系统刷题、巩固VBA基础并提升实际编程能力的读者使用。
1. 从一份“vba编程练习题+近年真题.doc”说起:为什么刷题总在同一个坑里翻车
很多人拿到一份“vba编程练习题+近年真题.doc”,第一反应是打开 Word 从头刷到尾,把每道题的答案敲进 VBE 里跑一遍,跑通了就翻页。三个月后回头再看,同样的单元格遍历题还是写不利索,同样的字典去重题还是要在For Each里卡半天。问题不在题量,而在于这份文档本质上是题目清单,不是能力地图——它把 Range、数组、字典、正则、事件、窗体这些考点平铺在一起,却没有告诉你哪些题在练同一块肌肉,哪些题只是换了个壳。
VBA 这门语言有个特点:语法糖少、对象模型重、调试手段原始。它不像 Python 那样有丰富的第三方库兜底,很多需求必须靠Range、Array、Dictionary、FileSystemObject这几个老伙计硬扛。所以刷练习题的正确姿势,不是按文档顺序刷,而是按对象模型分层刷:先练单元格读写,再练数组与字典的批量处理,最后练事件与窗体这类交互层。近年真题的命题趋势也很明显——纯语法题在减少,结合业务场景的综合题在增加,比如“按部门汇总销售数据并生成透视表”“从多张工作表提取不重复客户名单”。
这篇文章面向两类人:一类是刚接手 VBA 维护工作、需要快速把零散知识串成体系的新手;另一类是刷了不少题但遇到真实需求还是无从下手的熟手。我会把这份文档拆成可执行的训练路径,给出每类题型的标准解法模板、参数设置和排错方法,最后落到一个能直接套用的“真题拆解框架”上。不堆题,只讲怎么把题刷出复利。
2. 把练习题按对象模型分层:Range、数组、字典、正则各练什么
2.1 先分清四类题型的训练目标
拿到一份练习题文档,我一般先花十分钟做一次快速分类,而不是直接开刷。分类的依据是这道题的核心操作落在哪个对象上。VBA 里高频对象就那么几个,每类对象对应一组固定的思维模式:
| 题型类别 | 典型题干关键词 | 核心对象 | 训练目标 |
|---|---|---|---|
| 单元格操作 | 遍历、填充、条件格式、查找 | Range / Cells | 熟悉定位与批量读写 |
| 数组处理 | 拆分、合并、排序、转置 | Array / Variant() | 减少与工作表的交互次数 |
| 字典去重 | 不重复、统计、分组、匹配 | Dictionary | 用键值对替代循环嵌套 |
| 文本解析 | 提取、替换、校验、拆分 | RegExp / Split | 处理非结构化字符串 |
这个分类不是学术分类,是刷题顺序分类。Range 题练的是“怎么少读写”,数组题练的是“怎么在内存里算完再写回”,字典题练的是“怎么把 O(n²) 降成 O(n)”,正则题练的是“怎么用模式匹配替代一堆 If”。四类题的难度是递进的,但很多文档把它们混在一起,导致新手在 Range 还没练熟的时候就被字典题劝退。
我通常建议按“Range → 数组 → 字典 → 正则”的顺序刷,每类先刷 5 道基础题,再刷 3 道综合题。基础题的目的是形成肌肉记忆,综合题的目的是练组合。比如 Range 基础题就是“把 A 列所有空单元格填上 0”,综合题就是“找出 A 列中满足 B 列条件的单元格并高亮”。
2.2 Range 题:先练“少读写”,再练“准定位”
Range 类题目最容易犯的错是在循环里频繁读写单元格。下面这段代码是典型的反面教材,也是练习题里最常见的“遍历 A 列并修改”题型:
' 反面写法:每次循环都读写工作表,1000 行就要 2000 次交互 Sub BadLoop() Dim i As Long For i = 1 To 1000 If Cells(i, 1).Value <> "" Then Cells(i, 2).Value = Cells(i, 1).Value * 2 End If Next i End Sub这段代码逻辑没错,但性能极差。工作表交互是 VBA 里最慢的操作之一,1000 行还能忍,10000 行就要等好几秒。正确的做法是一次性读入数组,在内存里处理,再一次性写回:
' 正面写法:一次读、一次算、一次写 Sub GoodLoop() Dim arr As Variant Dim i As Long ' 一次性把 A1:B1000 读入内存数组 arr = Range("A1:B1000").Value For i = 1 To UBound(arr, 1) If arr(i, 1) <> "" Then arr(i, 2) = arr(i, 1) * 2 End If Next i ' 一次性写回 Range("A1:B1000").Value = arr End Sub这里的关键参数是Range("A1:B1000").Value,它返回一个二维 Variant 数组,下标从 1 开始。UBound(arr, 1)取第一维上界,也就是行数。注意数组下标和单元格行号在这里是对齐的,不需要额外偏移。如果题目要求处理动态行数,可以用Cells(Rows.Count, 1).End(xlUp).Row先算出最后一行,再拼 Range 地址。
Range 题里还有一个高频考点是定位:Find、AutoFilter、SpecialCells。练习题里常出现“找出所有包含‘异常’的单元格并标红”,新手容易写成For Each加InStr,熟手会直接用AutoFilter或Find。这两种写法的差距在数据量上万时非常明显。我一般建议:单条件查找用Find,多条件筛选用AutoFilter,空值/公式/常量定位用SpecialCells。
2.3 数组与字典题:把嵌套循环换成键值查找
数组和字典类题目是练习题文档里的重头戏,也是近年真题区分度最高的部分。典型题干是“统计每个部门的人数”“找出两张表都出现的客户”“按月份汇总金额”。新手解法通常是双重循环:
' 新手解法:双重循环,O(n²) Sub CountDeptBad() Dim i As Long, j As Long Dim dept As String Dim cnt As Long For i = 2 To 1000 dept = Cells(i, 1).Value cnt = 0 For j = 2 To 1000 If Cells(j, 1).Value = dept Then cnt = cnt + 1 Next j Cells(i, 2).Value = cnt Next i End Sub这段代码在 1000 行时还能跑,10000 行就要几分钟。用字典改写后,复杂度降到 O(n):
' 熟手解法:字典一次遍历,O(n) Sub CountDeptGood() Dim dict As Object Dim arr As Variant Dim i As Long Dim dept As String Set dict = CreateObject("Scripting.Dictionary") arr = Range("A2:A1000").Value ' 第一遍:统计 For i = 1 To UBound(arr, 1) dept = arr(i, 1) If dept <> "" Then If dict.Exists(dept) Then dict(dept) = dict(dept) + 1 Else dict(dept) = 1 End If End If Next i ' 第二遍:写回 For i = 1 To UBound(arr, 1) dept = arr(i, 1) If dept <> "" Then Cells(i + 1, 2).Value = dict(dept) End If Next i End Sub这里有两个参数值得注意:CreateObject("Scripting.Dictionary")是后期绑定,不需要在 VBE 里手动引用库,适合分发给别人用;dict.Exists(key)判断键是否存在,比On Error Resume Next那种写法干净得多。字典的Keys和Items方法可以一次性取出所有键和值,配合Application.Transpose可以直接写回一列。
数组题里另一个高频考点是动态数组的 ReDim。练习题里常出现“把满足条件的行提取到新数组”,新手容易用ReDim Preserve在循环里反复扩容,性能很差。正确做法是先ReDim一个足够大的数组,用一个计数器记录实际长度,最后再ReDim Preserve一次裁切。这个技巧在真题里反复出现,值得单独练几遍。
2.4 正则题:用模式匹配替代字符串拼接
正则类题目在练习题文档里占比不高,但在近年真题里出现频率在上升,尤其是数据清洗场景。典型题干是“从混合文本中提取手机号”“校验身份证格式”“把多个空格替换成一个”。VBA 里用正则需要引用Microsoft VBScript Regular Expressions 5.5,或者用后期绑定CreateObject("VBScript.RegExp")。
' 提取 A 列文本中的所有 11 位数字串 Sub ExtractPhone() Dim reg As Object Dim arr As Variant Dim i As Long Dim matches As Object Set reg = CreateObject("VBScript.RegExp") reg.Global = True ' 全局匹配,不只找第一个 reg.IgnoreCase = True ' 忽略大小写 reg.Pattern = "\d{11}" ' 11 位连续数字 arr = Range("A1:A100").Value For i = 1 To UBound(arr, 1) If reg.Test(arr(i, 1)) Then Set matches = reg.Execute(arr(i, 1)) Cells(i, 2).Value = matches(0).Value End If Next i End Sub三个参数必须显式设置:Global决定是否匹配所有结果,IgnoreCase决定是否忽略大小写,Pattern是模式串。reg.Test只返回布尔值,适合做校验;reg.Execute返回匹配集合,适合做提取。matches(0).Value取第一个匹配项,如果要取所有匹配项,需要遍历matches集合。
正则题最容易翻车的地方是模式串写错但不报错。比如\d{11}写成\d{11,},匹配结果会多出很多。我一般建议先在草稿里用几组测试数据验证模式串,再写进代码。另外 VBA 的正则不支持某些高级语法,比如命名捕获组,遇到复杂需求要拆成多步处理。
3. 近年真题的命题套路:从“语法题”到“业务综合题”的拆解方法
3.1 真题的三层结构:数据层、逻辑层、输出层
近年真题和早期练习题最大的区别是场景化。早期题目直接说“用字典统计”,近年题目会说“某公司有一份销售流水,请按区域汇总并生成报表”。表面上看是加了业务背景,实际上命题结构没变,还是三层:
- 数据层:数据在哪、什么格式、有没有脏数据
- 逻辑层:要做什么计算、按什么维度分组、有没有优先级
- 输出层:结果写到哪里、什么格式、要不要排序
拆题的时候,我习惯先把题干里的这三层信息标出来。比如“从 Sheet1 的 A 到 D 列读取订单数据,按客户名称汇总金额,结果写入 Sheet2 并从高到低排序”——数据层是 Sheet1 的 A:D,逻辑层是按客户汇总金额,输出层是 Sheet2 加降序。三层拆清楚后,代码结构自然就出来了:读数据 → 字典汇总 → 数组排序 → 写回。
这个拆解方法能解决大部分“看到题不知道从哪下手”的问题。很多新手卡住不是因为不会写代码,而是因为没把题干翻译成对象操作。我一般建议在草稿纸上画一个三栏表,左边写数据来源,中间写处理逻辑,右边写输出目标,填完再动手。
3.2 综合题的代码骨架:一个可复用的四段式模板
综合题不管题干怎么变,代码骨架基本是固定的四段:读入 → 处理 → 排序 → 输出。下面这个模板可以直接套用到大部分汇总类真题:
Sub SummaryTemplate() Dim arr As Variant Dim dict As Object Dim i As Long, n As Long Dim key As String Dim result() As Variant Dim temp As Variant ' 第一段:读入数据到数组 arr = Range("A2:D" & Cells(Rows.Count, 1).End(xlUp).Row).Value ' 第二段:字典汇总 Set dict = CreateObject("Scripting.Dictionary") For i = 1 To UBound(arr, 1) key = arr(i, 1) ' 假设第一列是分组键 If dict.Exists(key) Then dict(key) = dict(key) + arr(i, 4) ' 假设第四列是金额 Else dict(key) = arr(i, 4) End If Next i ' 第三段:转成二维数组并排序 n = dict.Count ReDim result(1 To n, 1 To 2) Dim keys As Variant keys = dict.Keys For i = 0 To n - 1 result(i + 1, 1) = keys(i) result(i + 1, 2) = dict(keys(i)) Next i ' 冒泡排序,按第二列降序 Dim j As Long For i = 1 To n - 1 For j = i + 1 To n If result(j, 2) > result(i, 2) Then temp = result(i, 1): result(i, 1) = result(j, 1): result(j, 1) = temp temp = result(i, 2): result(i, 2) = result(j, 2): result(j, 2) = temp End If Next j Next i ' 第四段:写回工作表 Range("F2").Resize(n, 2).Value = result End Sub这个模板里几个参数需要根据题目调整:Range("A2:D" & ...)的列范围、arr(i, 1)的分组键列号、arr(i, 4)的数值列号、Range("F2")的输出起点。排序部分用的是冒泡,数据量小的时候够用,上万行建议换成ArrayList的Sort或者写个快速排序。注意dict.Keys返回的是 0 基数组,所以循环从 0 到 n-1,写回时下标加 1。
这个骨架的价值在于把综合题拆成了填空题。刷真题的时候,每道题只需要改几个参数,不用每次从零开始想结构。练到后面,看到题干就能直接反应出这四段分别对应什么。
3.3 真题里最容易失分的三个细节
近年真题的评分点往往不在主逻辑上,而在几个细节上。我统计过一批模拟题的失分情况,排前三的是:
第一,没有处理空值和错误值。题干里说“数据可能不完整”,很多人直接arr(i, 1)就参与计算,遇到空值就报错。正确做法是在读入后加一层判断,或者用IsError检查。这个细节在真题里几乎每年都考。
第二,输出没有清空旧数据。如果 Sheet2 之前有数据,直接Resize写回会留下残留。我一般会在写回前加一句Range("F2").CurrentRegion.ClearContents,或者用UsedRange判断。这个坑在模拟环境里不明显,真实场景里很常见。
第三,字典的键没有做类型统一。比如客户名称有的带空格有的不带,有的数字存成文本有的存成数值,字典会当成不同的键。处理方法是读入后统一Trim和CStr,或者用UCase统一大小写。这个细节在真题里经常作为“隐藏考点”出现。
4. 避坑与排查:刷 VBA 练习题时最常见的五类翻车
4.1 现象:代码在 VBE 里跑得好好的,换个文件就报错
原因:引用了特定库或依赖了特定工作表名。比如用了Dictionary的早期绑定,需要在 VBE 里手动勾选Microsoft Scripting Runtime;或者代码里硬编码了Sheets("Sheet1"),换文件后工作表名变了。
解决:统一用后期绑定CreateObject("Scripting.Dictionary"),避免库引用问题。工作表引用改用ThisWorkbook.Sheets(1)或ActiveSheet,或者用Sheets集合的索引。如果必须用名称,加一个On Error兜底,提示用户检查工作表名。
4.2 现象:循环跑得越来越慢,最后卡死
原因:在循环里频繁读写单元格,或者ReDim Preserve在循环里反复扩容。前者是工作表交互开销,后者是内存拷贝开销。
解决:读写改批量,用数组做中转。ReDim Preserve只在最后裁切时用一次,中间用计数器记录实际长度。如果数据量超过 10 万行,考虑用ADODB或者QueryTable直接查,不要用 VBA 循环硬扛。
4.3 现象:正则匹配结果和预期不一致,但代码不报错
原因:模式串写错,或者Global属性没设。比如\d{11}写成\d{11,},或者忘了reg.Global = True导致只匹配第一个。
解决:先在草稿里用几组测试数据验证模式串,确认匹配范围。Global、IgnoreCase、MultiLine三个属性根据需求显式设置。VBA 正则不支持命名捕获组和零宽断言的部分语法,复杂需求拆成多步。
4.4 现象:字典统计结果少了几个键
原因:键的类型不一致。比如“北京”和“北京 ”(带空格)被当成两个键,或者数字 100 和文本 "100" 被当成两个键。
解决:读入后统一处理,key = Trim(CStr(arr(i, 1))),需要忽略大小写就加UCase。如果键是数字,统一转成CDbl或CLng。这个坑在真实数据里非常常见,练习题里往往不体现,但真题里经常考。
4.5 现象:排序结果不对,或者排序后数据错位
原因:排序时只交换了一列,没有同步交换关联列。比如按金额排序时只换了金额列,客户名称没跟着换,导致数据错位。
解决:排序时用临时变量同时交换所有关联列,或者把整行作为一个整体交换。数据量大时不要用冒泡,改用ArrayList的Sort配合自定义比较器,或者把数据写到工作表后用Range.Sort排完再读回数组。
5. 把真题刷出复利:一个可复用的拆解框架与验证习惯
刷题刷到最后,拼的不是题量,是拆解速度和验证习惯。我自己的做法是维护一个“题型-模板”对照表,每刷一道新题,先判断它属于哪类题型,然后直接调用对应的模板骨架,只改参数不重写结构。这个习惯让我的刷题效率在两个月内翻了一倍,因为大部分题目的 80% 代码是重复的。
具体来说,我会把常见题型归纳成六个模板:单元格遍历模板、数组批量处理模板、字典汇总模板、字典去重模板、正则提取模板、综合汇总模板。每刷一道新题,先归类,再套模板,最后只关注题目特有的那 20% 逻辑。比如“按部门汇总销售额”和“按区域汇总订单量”本质是同一个模板,只是分组键和数值列不同。
验证习惯方面,我坚持三个动作:第一,用边界数据测试,比如空值、单行、全重复、全不重复;第二,对比手工结果,取前 10 行用 Excel 公式算一遍,和代码结果比对;第三,记录翻车点,每道题跑通后写一行注释,说明这道题最容易错的地方。这个习惯坚持三个月后,我发现自己对真题的“隐藏考点”越来越敏感,看到题干就能猜到出题人想考什么。
最后一个技巧是关于真题的二次利用。很多人刷完一遍就把文档扔了,我一般会做两件事:一是把每道题的代码骨架提取出来,存到一个单独的模块里,标注题型和关键参数;二是把题干改一改,比如把“按部门汇总”改成“按月份汇总”,把“降序”改成“升序”,自己给自己出变体题。这个过程能逼着你理解模板的边界,而不是死记代码。
我自己的教训是:早期刷题追求“跑通就行”,结果遇到变体题就卡壳。后来强迫自己每道题都写注释、做变体、记翻车点,虽然刷题速度慢了,但真实需求来的时候能直接上手。VBA 这门语言,语法一周就能学完,剩下的时间都是在练“怎么把业务翻译成对象操作”。希望帮到你。
本文还有配套的精品资源,点击获取