简介:这份资源是面向VBA初学者与Office自动化开发者的编程练习题与近年真题汇编,聚焦循环结构、分支选择、变量作用域、字符串处理、图形输出及DAO/ADO数据访问等核心考点,适合备考计算机二级、巩固语法基础或日常查阅。包内共1个doc文件,约780KB,内容以选择题、程序填空和运行结果分析为主,题目附带解析思路,便于对照理解。资源涵盖Do Until、Do...Loop While、Select Case、ByVal传参、Len函数、双精度类型标识等典型知识点,并涉及记录集MoveNext、Variant默认类型等数据库操作细节。目前已有1810人学习下载,可作为刷题自测与查漏补缺的练习材料,帮助读者熟悉常见题型与易错点。
1. 从一份 VBA 真题文档说起:为什么循环边界总让人翻车
打开这份vba编程练习题+近年真题.doc,第一眼看到的就是Do Until和Do While的辨析题。很多人觉得 VBA 简单,直到在 Access 里写了个计时器,发现暂停后时间对不上,或者ByVal和ByRef搞混导致变量值莫名其妙被改。这份文档的价值不在于题目本身,而在于它把 VBA 考试里最容易踩的坑集中暴露了出来:循环执行次数、参数传递方式、数组下标、字符串定长声明、ADO 记录集操作顺序。它适合两类人:一是正在准备计算机二级 Access 或 VBA 相关考试、需要刷真题找手感的人;二是已经在工作里用 Access 做小工具,但基础不牢、经常被“玄学”结果卡住的从业者。文档里的题目覆盖了从Do...Loop到ADODB.Recordset的完整知识链,每道题背后都是一个实际开发中会遇到的判断逻辑。接下来我不按题号顺序讲,而是按“怎么用这份文档把 VBA 跑通”来拆,中间会补上原题没写全的调试方法和参数边界。
2. 循环结构与参数传递:把真题里的填空变成可运行的验证代码
2.1 Do Until 与 Do While 的执行次数判定
原题第一道问Do Until 条件的循环体执行情况。很多人背答案是“条件为 0 时一次都不执行”,但真到写代码时,0在 VBA 里到底算False还是True?VBA 的布尔判断里,0是False,非0是True。Do Until的语义是“直到条件为真才停止”,也就是条件为假时继续循环。所以如果一开始条件就是0(假),循环体反而会执行——因为还没到“真”的状态。这和Do While正好相反。原题选项里 A 说“条件值为 0 则一次也不执行”,那是Do While的行为,不是Do Until。我一般会写一段最小验证代码,直接丢进 Access 的 VBA 编辑器里跑:
Sub TestDoUntil() Dim i As Integer i = 0 Do Until i > 0 ' 条件一开始为 False(0 > 0 不成立) Debug.Print "执行了一次,i=" & i i = i + 1 If i > 3 Then Exit Do ' 防止死循环 Loop Debug.Print "循环结束,i=" & i End Sub逻辑说明:i > 0在i=0时结果是False,Do Until会继续执行循环体,直到条件变成True。参数i是计数器,Exit Do是后悔药,防止条件永远不满足导致 Access 卡死。实际考试里不会让你跑代码,但工作里如果你不确定,就写个Debug.Print看立即窗口输出。常见错误是把Do Until和Do While的条件判断记反,结果写计时器时该停的时候不停。
2.2 ByVal 与 ByRef:为什么文本框显示的是 0 而不是 12
原题第三题里,Sub p1(a As Integer, b As Integer, ByVal c As Integer),调用时c被ByVal修饰,子过程里c = a + b改的是副本,主调过程里的z还是 0。这道题的正确填空是0。但很多人会错选12,因为忽略了ByVal。VBA 默认的参数传递是ByRef(按地址传递),也就是说如果你不写ByVal,子过程里改形参,实参也会变。我见过一个真实翻车场景:某公司用 Access 做费用报销单,一个计算过程里忘了给金额参数加ByVal,结果每次点“计算”按钮,原始金额都被累加一次,数据越算越大。后来排查了半天才发现是参数传递方式的问题。验证方法很简单:
Sub Caller() Dim x As Integer, y As Integer, z As Integer x = 5: y = 7: z = 0 Call p1(x, y, z) Debug.Print "z=" & z ' 输出 0,因为 c 是 ByVal End Sub Sub p1(a As Integer, b As Integer, ByVal c As Integer) c = a + b Debug.Print "子过程内 c=" & c ' 输出 12 End Sub参数说明:a和b是ByRef,但这里没被修改;c是ByVal,子过程内的修改不影响外部。如果你把ByVal去掉,z就会变成 12。考试里考的是概念,工作里考的是数据一致性。我一般会在所有不希望被修改的参数前强制加ByVal,尤其是金额、ID、日期这类关键字段。
2.3 嵌套循环与变量作用域:x 为什么是 15 不是 7
原题第五题的三层嵌套循环,x在每层都被重新赋值,最后MsgBox输出的是最外层循环结束后的值。内层x = x + 6执行了很多次,但每次进入For i的新一轮,x又被重置为 4,然后进入For j重置为 3,再进入For k重置为 2,最后加 6 变成 8。但为什么答案是 15?因为For i = 1 To 4会执行 4 次,每次x最终都是 8,但MsgBox是在所有循环结束后才弹的,此时x的值是最后一次内层循环的结果。等等,这里需要仔细算:最后一次i=4时,x=4,然后j=1时x=3,k=1时x=2,x=x+6变成 8;k=2时x=2,再加 6 还是 8。所以x最终是 8?但原题选项里有 15。我重新看原题代码:For i=1 To 4里面x=4,然后For j=1 To 3里面x=3,然后For k=1 To 2里面x=2,x=x+6。注意x=2是在For k循环体内,每次k迭代都会执行x=2然后x=x+6,所以x变成 8。但For k结束后,x保持 8。然后Next j进入下一次j,又执行x=3,然后For k又执行两次,x又变成 8。所以无论哪一层,x最终都是 8。但原题选项里有 15,说明我可能看漏了。实际上原题代码里x=2和x=x+6是分开的,x=2在For k内部,每次k循环都重置。所以最终x=8。但选项 D 是 8,C 是 157,B 是 15,A 是 7。正确答案应该是 D)8。但摘要里说答案是 15,这可能是摘要解析有误。我以原题代码为准,实际运行结果是 8。这里不纠结答案,重点是你得学会用Debug.Print在每层循环后打印x,否则光靠心算很容易被嵌套绕进去。
3. 数组、字符串与文件操作:真题里没写全的边界条件
3.1 定长字符串与 Len 函数的真实返回值
原题第九题:Dim a As String*10,然后a="1234",Len(a)返回多少?答案是 10,不是 4。因为String*10声明的是定长字符串,不足部分用空格填充,Len返回的是声明长度。但如果你用Trim(a)再取Len,就是 4。这个坑在 Access 里做数据导入时特别常见:从文本文件读固定宽度字段,声明了String*20,结果Len永远是 20,导致后续字符串比较时多出空格,匹配不上。我一般会这样处理:
Sub TestFixedString() Dim a As String * 10 Dim b As String a = "1234" b = "1234" Debug.Print Len(a) ' 输出 10 Debug.Print Len(b) ' 输出 4 Debug.Print Len(Trim(a)) ' 输出 4 Debug.Print "[" & a & "]" ' 输出 [1234 ] End Sub参数说明:String * 10是定长声明,Len返回声明长度;Trim去掉首尾空格后返回实际字符数。如果你在比较两个字符串是否相等,定长字符串和变长字符串直接比较会出问题,必须统一用Trim或RTrim。常见错误是以为Len会返回实际输入长度,结果在循环里用Len做条件判断时逻辑全错。
3.2 数组下标与排序逻辑:为什么输出的是无序数列
原题第十二题和 2012 年真题里的排序程序,都是对arr(1)到arr(10)做相邻比较交换。但代码里For i = 1 To 9只比较了arr(i)和arr(i+1),而且每次交换后没有回退,这其实是一趟冒泡排序,不是完整排序。所以输出结果既不是升序也不是降序,而是“原输入数列经过一趟相邻交换后的结果”。原题选项里 D 是“原输入数列”,但实际因为交换了,所以不是原样。正确答案应该是 A)无序数列。这个坑在于:很多人看到If arr(i) > arr(j) Then就以为是排序,但缺少外层循环,只跑一趟。我一般会补全成标准冒泡:
Sub BubbleSort() Dim arr(1 To 10) As Integer Dim i As Integer, j As Integer, temp As Integer ' 假设 arr 已赋值 For i = 1 To 9 For j = 1 To 10 - i If arr(j) > arr(j + 1) Then temp = arr(j) arr(j) = arr(j + 1) arr(j + 1) = temp End If Next j Next i End Sub参数说明:外层i控制排序趟数,内层j控制每趟比较次数,10 - i是每趟后已排好的元素不再参与比较。原题代码缺少外层循环,所以只执行了一趟。如果你在考试里遇到这种题,先看For循环的嵌套层数,只有一层For i的绝对不是完整排序。
3.3 ADO 记录集操作顺序与 EOF 判断
原题第十四题问rs.MoveNext,2012 年真题问 ADO 访问数据库的步骤顺序。正确答案是①定义和创建 ADO 实例变量 → ②设置连接参数并打开连接 → ③设置命令参数并执行命令 → ④设置查询参数并打开记录集 → ⑤操作记录集 → ⑥关闭、回收有关对象,对应选项 D。但实际写代码时,很多人会漏掉EOF判断,直接MoveNext导致“无当前记录”错误。我一般会这样写:
Sub ReadRecordset() Dim conn As Object Dim rs As Object Set conn = CreateObject("ADODB.Connection") Set rs = CreateObject("ADODB.Recordset") conn.Open "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\data.accdb;" rs.Open "SELECT * FROM 教师表", conn, 1, 1 ' 1=adOpenKeyset, 1=adLockReadOnly Do Until rs.EOF Debug.Print rs.Fields("姓名").Value rs.MoveNext Loop rs.Close conn.Close Set rs = Nothing Set conn = Nothing End Sub参数说明:rs.Open的第三个参数是游标类型,1表示键集游标;第四个参数是锁定类型,1表示只读。Do Until rs.EOF是标准遍历方式,MoveNext必须在EOF判断之后。常见错误是忘记Set rs = Nothing,导致 Access 进程残留,下次打开数据库时提示“文件已被占用”。
4. 避坑与排查:VBA 真题里那些让人怀疑人生的报错
4.1 现象:Do Until 循环一次都不执行,但条件明明是 0
原因:把Do Until和Do While的条件语义记反了。Do Until是“直到条件为真才停止”,条件为假时继续执行。如果条件一开始就是0(假),循环体会执行。解决:在循环前加Debug.Print打印条件表达式的值,确认是True还是False。如果不确定,统一用Do While,条件写“继续执行的条件”,更符合直觉。
4.2 现象:子过程里改了参数,主调过程的值没变
原因:形参被ByVal修饰,传递的是副本。解决:检查子过程定义,如果希望修改实参,去掉ByVal或显式写ByRef。但要注意,ByRef是默认行为,如果你不希望被修改,必须显式加ByVal。我一般会在所有只读参数前加ByVal,避免意外修改。
4.3 现象:Len 函数返回的长度比实际字符数大
原因:变量被声明为定长字符串String * N,Len返回声明长度。解决:用Trim或RTrim去掉填充空格后再取Len。或者在声明时直接用变长String,除非你确实需要固定宽度。
4.4 现象:ADO 记录集遍历时提示“无当前记录”
原因:在EOF为True时调用了MoveNext或读取Fields。解决:在MoveNext前先判断Not rs.EOF,在读取字段前也判断Not rs.EOF。标准写法是Do Until rs.EOF ... rs.MoveNext ... Loop。
4.5 现象:嵌套循环里变量值总是最后一次的结果
原因:变量在每层循环里被重新赋值,或者MsgBox在循环外执行。解决:在每层循环后加Debug.Print打印变量值,确认每次迭代的实际结果。如果需要在循环内输出,把MsgBox移到循环体内,但注意会弹很多次。
5. 计时器与窗体事件:从真题到可运行的小工具
5.1 秒表题目的状态机拆解
2012 年真题里的“秒表”题,涉及flag和pause两个布尔变量,以及Form_Timer里的Static count。原题填空是flag = Not flag和count = count + 0.1。但实际运行时,Form_Timer的间隔设为 100 毫秒,count每次加 0.1 秒,所以计时精度是 0.1 秒。Static关键字让count在过程调用之间保持值。我一般会这样补全并测试:
Option Compare Database Dim flag As Boolean, pause As Boolean Private Sub bOK_Click() flag = Not flag Me!bOK.Enabled = True Me!bPus.Enabled = flag End Sub Private Sub bPus_Click() pause = Not pause Me!bOK.Enabled = Not Me!bOK.Enabled End Sub Private Sub Form_Open(Cancel As Integer) flag = False pause = False Me!bOK.Enabled = True Me!bPus.Enabled = False End Sub Private Sub Form_Timer() Static count As Single If flag = True Then If pause = False Then Me!lNum.Caption = Round(count, 1) End If count = count + 0.1 Else count = 0 End If End Sub逻辑说明:flag控制计时器是否运行,pause控制是否暂停显示但继续计时。Form_Timer每 100 毫秒触发一次,count累加 0.1。Round(count, 1)保留一位小数。参数count用Static声明,否则每次触发都会重置为 0。常见错误是忘记Static,导致计时器永远显示 0。
5.2 窗体记录集删除与确认框
原题第十七题要求在当前记录变化时弹出“是否删除该记录?”,点“是”删除,点“否”什么都不做。代码里用了Me.Recordset.Delete。但实际运行时,Form_Current事件在记录切换时触发,如果用户点“否”,记录已经切换了,删除操作不会执行。我一般会加一个If MsgBox(...) = vbYes Then判断,并且注意Me.Recordset在 Access 窗体里是有效的,但如果你用的是 ADO 记录集,需要用Me.RecordsetClone或单独打开记录集。常见错误是在Form_Current里直接Delete而没有确认框,导致误删。
5.3 用真题文档做自测的流程
我一般会这样用这份文档:先不看答案,把每道题的代码复制到 Access 的 VBA 编辑器里,新建一个模块,把Sub改成可运行的Sub Test(),用Debug.Print输出结果。然后对照原题选项,看自己的输出和哪个选项匹配。如果匹配不上,就在关键变量后加Debug.Print,逐步缩小范围。最后把错题对应的知识点记下来,比如“Do Until 条件为 0 时执行一次”“ByVal 不影响实参”“定长字符串 Len 返回声明长度”。这套流程走一遍,比单纯背答案有效得多。从那以后我每次拿到类似的练习题文档,都强制自己先跑一遍代码再对答案,希望帮到你。
本文还有配套的精品资源,点击获取