ExcelHome技术论坛

 找回密码
 免费注册

QQ登录

只需一步,快速开始

快捷登录

搜索
EH技术汇-专业的职场技能充电站 妙哉!函数段子手趣味讲函数 Excel服务器-会Excel,做管理系统 效率神器,一键搞定繁琐工作
HR薪酬管理数字化实战 Excel 2021函数公式学习大典 Excel数据透视表实战秘技 打造核心竞争力的职场宝典
让更多数据处理,一键完成 数据工作者的案头书 免费直播课集锦 ExcelHome出品 - VBA代码宝免费下载
用ChatGPT与VBA一键搞定Excel WPS表格从入门到精通 Excel VBA经典代码实践指南
查看: 90169|回复: 135

[分享] 程序错误处理之On Error、Resume

  [复制链接]

TA的精华主题

TA的得分主题

发表于 2009-11-19 16:53 | 显示全部楼层 |阅读模式
本帖已被收录到知识树中,索引项:数据类型和基本语句
关于程序错误处理,是一个常见而又非常被容易忽视的问题。错误处理,顾名思义就是程序在发生错误时的处理过程。为什么要有错误处理?如果没有错误处理又会怎么样呢?
我们首先来简单的说说上面两个问题。
    一、为什么要有错误处理呢?这是因为任何一个程序都不可能说是不会发生任何错误的(注意:我们这里说的错误只是指的狭义的逻辑错,并不包括语法错)。
    二、如果没有错误处理会发生什么呢?我们先来看一段简单的代码:

  1. Sub Test()
  2.   Dim M As Long
  3.   Dim N As Long
  4.   
  5.   M = Val(InputBox("请输入一个整数", "M*N"))
  6.   N = Val(InputBox("请输入一个整数", "M*N"))
  7.   MsgBox CStr(M) & "×" & CStr(N) & "=" & CStr(M * N)
  8. End Sub
复制代码
这是一个输入两个整数然后输出这个两数乘积的过程,这段程序有没有错误呢?——没有。输入一个"3",再输入一个"2",程序立即输出了"3×2=6"。那这段程序能永远不出错吗?答案也是否定的,两次输入时任意一次输入"A"程序就会报错了,那么是否只要保证两次输入都是整数程序就不会出错了呢,答案还是否定的,只要输出的两个数乘积大于2147483647,程序还是会出错。这正验证了一句话:永远都不要相信用户的输入
    既然程序这么容易出错,那么错误处理就是一个程序必不可少的部分,任何一个好的程序员都不能忽视这一环节,谁也不会想让自己的程序随时都可能弹个错误对话框后程序就被非法关闭了。
那么,现在我们就面对了一个新的问题:如何处理错误?
    相信多数人立即想到了On Error Goto XXXX(XXXX表示一个标签或是一个行号)和On Error Resume Next。是的,这是两种最常见的错误处理方式。但是,有多少人真正了解这两条语句呢。我知道很多人天天都在用这两条语句,对它们的作用随口都能说出来。今天我们暂时就先不说明这两条语句的作用和意义了。我们先还是把它们拆开来看。On Error Goto XXXXOn Error Resume Next我们就先把它们拆成On ErrorGotoResume Next。我们先来看看On Error的帮助。
On Error 语句
启动一个错误处理程序并指定该子程序在一个过程中的位置;也可用来禁止一个错误处理程序。
语法
On Error GoTo line
On Error Resume Next
On Error GoTo 0
On Error 语句的语法可以具有以下任何一种形式:
              语句                                                             描述
On Error GoTo line              启动错误处理程序,且该例程从必要的 line 参数中指定的 line 开始。line 参数可以是任何行标签或行号。如果发生一个运行时错误,则控件会跳到 line,激活错误处理程序。指定的 line 必须在一个过程中,这个过程与 On Error 语句相同; 否则会发生编译时间错误。
On Error Resume Next        说明当一个运行时错误发生时,控件转到紧接着发生错误的语句之后的语句,并在此继续运行。访问对象时要使用这种形式而不使用 On Error GoTo。
On Error GoTo 0                 禁止当前过程中任何已启动的错误处理程序。

说明
如果不使用 On Error 语句,则任何运行时错误都是致命的;也就是说,结果会导致显示错误信息并中止运行。
一个“允许的”错误处理程序是由 On Error 语句打开的一个处理程序;一个“活动的”错误处理程序是处理错误的过程中允许的错误处理程序。如果在错误处理程序处于活动状态时(在发生错误和执行 Resume、Exit Sub、Exit Function 或 Exit Property 语句之间这段时间)又发生错误,则当前过程的错误处理程序将无法处理这个错误。控件返回调用的过程。如果调用过程有一个已启动的错误处理程序,则激活错误处理程序来处理该错误。如果调用过程的错误处理程序也是活动的,则控件将再往回传到前面的调用过程,这样一直进行下去,直到找到一个被允许的但不是活动的错误处理程序为止。如果没有找到被允许而且不活动的错误处理程序,那么在错误实际发生的地方,错误本身是严重的。错误处理程序每次将控件返回调用过程时,该过程就成为当前过程。在任何过程中,一旦错误处理程序处理了错误,在当前过程中就会从 Resume 语句指定的位置恢复运行。
注意 一个错误处理程序不是 Sub 过程或 Function 过程。它是一段用行标签或行号标记的代码。

大家看到这里是不是对On Error已经有了一个比较清晰的了解了吧。如果还不是很清楚的话可以再看看下面的过程,它演示了On Error Goto XXXX和On Error Resume Next,相信大家看完后就能立即明白这两条语句了。

  1. Sub TestError1()
  2.   Dim I As Long
  3.   
  4.   On Error Resume Next  ' 指定发生错误时不处理,直接运行下一条语句
  5.   I = "A1"              ' 发生错误,由于已经指定了发生错误时不处理,故Err对象立即返回直接运行下一条语句。
  6.   Debug.Print "被忽略的错误,错误代码:" & Err.Number & " 错误信息" & Err.Description & " 错误源:" & Err.Source & " 当前I的值=" & I
  7.   Err.Clear             ' 清空所有错误记录
  8.   On Error GoTo ERROR1  ' 指定下面的错误发生时直接跳转至Error1标号处
  9.   I = 2147483648#       ' 发生错误,由于指定了跳转,故直接转至Error1,而不会再执行下面的语句
  10.   I = 100
  11.   Debug.Print "程序正常返回,当前I的值=" & I
  12.   Exit Sub
  13. ERROR1:
  14.   Debug.Print "发生错误,错误代码:" & Err.Number & " 错误信息" & Err.Description & " 错误源:" & Err.Source & " 当前I的值=" & I
  15. End Sub
复制代码
相信现在大家已经完全明白了这两条语句的用法了。但是,大家也看到了,第二条语句出错时,输出错误代码后程序直接退出了,或许有人又会想,如果当第二次错误发生时能不能输出错误代码后再返回到出错的下一条语句再断续执行程序呢?这时我们就要使用到Resume Next了。还是老规矩,把它拆成ResumeNext来看,我们看看Resume的帮助。
Resume 语句
在错误处理程序结束后,恢复原有的运行。
语法
Resume [0]
Resume Next
Resume line
Resume 语句的语法可以具有以下任何一种形式:
              语句                                                             描述
Resume                        如果错误和错误处理程序出现在同一个过程中,则从产生错误的语句恢复运行。如果错误出现在被调用的过程中,则从最近一次调用包含错误处理程序的过程的语句处恢复运行。
Resume Next                如果错误和错误处理程序出现在同一个程序中,则从紧随产生错误的语句的下个语句恢复运行。如果错误发生在被调用的过程中,则对最后一次调用包含错误处理程序的过程的语句(或 On Error Resume Next 语句),从紧随该语句之后的语句处恢复运行。
Resume line                在必要的 line 参数指定的 line 处恢复运行。line 参数是行标签或行号,必须和错误处理程序在同一个过程中。

说明
在错误处理程序之外的任何地方使用 Resume 语句都会导致错误发生。

      我们看到了Resume Next,但是很明显,它只是Resume 三种调用方式中最常见的一种,不过上面的信息已经足够了,我们使用Resume Next就可以达到上面的要求了。

  1. Sub TestError2()
  2.   Dim I As Long
  3.   
  4.   On Error Resume Next  ' 指定发生错误时不处理,直接运行下一条语句
  5.   I = "A1"              ' 发生错误,由于已经指定了发生错误时不处理,故Err对象立即返回。
  6.   Debug.Print "被忽略的错误,错误代码:" & Err.Number & " 错误信息" & Err.Description & " 错误源:" & Err.Source & " 当前I的值=" & I
  7.   Err.Clear             ' 清空所有错误记录
  8.   On Error GoTo ERROR2  ' 指定下面的错误发生时直接跳转至Error2标号处
  9.   I = 2147483648#       ' 发生错误,由于指定了跳转,故直接转至Error1,而不会再执行下面的语句
  10.   I = 100               ' 由于在Error2后面指定了Resume Next,所以程序还会再次返回到这里开始执行,这就是和过程TestError1不同之处
  11.   Debug.Print "程序正常返回,当前I的值=" & I
  12.   Exit Sub
  13. ERROR2:
  14.   Debug.Print "发生错误,错误代码:" & Err.Number & " 错误信息" & Err.Description & " 错误源:" & Err.Source & " 当前I的值=" & I
  15.   Resume Next           ' 返回发生错误处的下一条语句,继续执行
  16. End Sub
复制代码
如果是在错误处理过程中再发生错误怎么办?或许你想到了用On Error Resume Next,于是我们就有了这样一段程序

  1. Sub TestError3()
  2.   Dim I As Long
  3.   
  4.   On Error GoTo ERROR3  ' 指定发生错误时不处理,直接运行下一条语句
  5.   I = "A1"              ' 发生错误,由于指定了跳转,故直接转至Error3,而不会再执行下面的语句
  6.   Debug.Print "发生错误,错误代码:" & Err.Number & " 错误信息" & Err.Description & " 错误源:" & Err.Source & " 当前I的值=" & I
  7. ERROR3:
  8.   Err.Clear             ' 清空所有错误记录
  9.   On Error Resume Next  ' 指定下面的错误发生时忽略错误,直接跳转至下一条语句继续执行
  10.   I = 2147483648#       ' 发生错误,由于已经指定了发生错误时不处理,故Err对象应立即返回,
  11.                         ' 但是由于Err对象本身因上一条I = "A1"错误而处于激活状态,这条错误并不能像我们所预期的
  12.                         ' 一样直接忽略错误跳到下一条,而是产生了一条严重错误,将会弹出错误提示对话框,程序挂起
  13.   Debug.Print "被忽略的错误,错误代码:" & Err.Number & " 错误信息" & Err.Description & " 错误源:" & Err.Source & " 当前I的值=" & I
  14.   I = 100
  15.   Debug.Print "程序正常返回,当前I的值=" & I
  16. End Sub
复制代码
执行它后,我们发现了一个严重问题,程序还是抛出了一个异常并被挂起。这是怎么回事?难到是Resume Next不起作用了?带着这个疑问我们再次返回到On Error的帮助,有这么一段话:
一个“活动的”错误处理程序是处理错误的过程中允许的错误处理程序。如果在错误处理程序处于活动状态时(在发生错误和执行 ResumeExit SubExit FunctionExit Property 语句之间这段时间)又发生错误,则当前过程的错误处理程序将无法处理这个错误。

       原来如此,错误处理过程只能是处理别人的错误,一旦处理过程本身发生错误,由于错误处理过程已经处于激活状态,就会导致无法再继续处理错误了。我们正是因为在错误处理过程中又产生了错误,所以程序还是被挂起了。那么,我们该怎么处理才能得到自己想要的结果呢?既然只是错误处理过程在激活状状态下无法处理错误,那么我们是不是只要让错误处理过程返回到非激活状态下,就可以了呢?我们只好请出 Resume line 上场。

  1. Sub TestError4()
  2.   Dim I As Long
  3.   
  4.   On Error GoTo ERROR4  ' 指定发生错误时不处理,直接运行下一条语句
  5.   I = "A1"              ' 发生错误,由于指定了跳转,故直接转至Error3,而不会再执行下面的语句
  6.   Debug.Print "发生错误,错误代码:" & Err.Number & " 错误信息" & Err.Description & " 错误源:" & Err.Source & " 当前I的值=" & I
  7. ERROR4:
  8.   Err.Clear             ' 清空所有错误记录
  9.   Resume ERROR4_Next    ' 指定错误陷井处理完毕并返回ERROR4_Next,
  10.                         ' 这样就做的目的就是为了释放Err对象错误处理,使下面的On Error Resume Next生效
  11. ERROR4_Next:
  12.   On Error Resume Next  ' 指定下面的错误发生时忽略错误,直接跳转至下一条语句继续执行
  13.   I = 2147483648#       ' 发生错误,由于已经指定了发生错误时不处理,故Err对象立即返回
  14.   Debug.Print "被忽略的错误,错误代码:" & Err.Number & " 错误信息" & Err.Description & " 错误源:" & Err.Source & " 当前I的值=" & I
  15.   I = 100
  16.   Debug.Print "程序正常返回,当前I的值=" & I
  17. End Sub
复制代码
运行后,我们终于看到了自己想要的结果。
有了上面的一系列实验,我们再回头来看On Error Goto 0Resume 0就非常容易理解了。我们还是再用两段程序来实验一下吧(实践出真理嘛):

  1. Sub TestError5()
  2.   Dim I As Long
  3.   
  4.   On Error Resume Next
  5.   I = 2147483648#       ' 发生错误,由于已经指定了发生错误时不处理,故Err对象立即返回
  6.   Debug.Print "被忽略的错误,错误代码:" & Err.Number & " 错误信息" & Err.Description & " 错误源:" & Err.Source & " 当前I的值=" & I
  7.   On Error GoTo 0       ' 停止在当前过程中处理错误。
  8.   I = "A1"              ' 由于错误处理被停止,所以程序会在这里弹出错误,并挂起。
  9.   Debug.Print "程序正常返回,当前I的值=" & I
  10. End Sub
复制代码


  1. Sub TestError6()
  2.   Dim I As Long
  3.   Dim D As Double
  4.   
  5.   On Error GoTo ERROR6  ' 指定发生错误时跳转至标号Error6处开始执行
  6.   D = 2147483650#
  7.   I = D                 ' 发生隐性错误,D的值大于Long能表达的最大值 2,147,483,647
  8.   Debug.Print "程序正常返回,当前I的值=" & I
  9.   Exit Sub
  10. ERROR6:
  11.   Debug.Print "发生错误,错误代码:" & Err.Number & " 错误信息" & Err.Description & " 错误源:" & Err.Source & " 当前I的值=" & I & " 当前D的值=" & Format(D, "###,###")
  12.   D = D - 1
  13.   Resume                ' Resume等价于 Resume 0 ,即返回上次出错的语句继续执行,注意区分它与 Resume Next 的区别。
  14.                         ' 由于前面的 D=D-1 使得发生一次错误就让D自减1,
  15.                         ' 这样就使得程序在D的等于2,147,483,647时正常返回退出,但要小心使用这种方式,
  16.                         ' 因为如果D是小于Long能表达的最小负数 -2,147,483,648 时会使得程序不断循环出错,
  17.                         ' 最终停止在D溢出错误上,相信我,这一定会是个非常痛苦的等待 ^_^
  18. End Sub
复制代码
这两段代码非常的明了,只要运行后就可以直白的看到结果了。

[ 本帖最后由 joforn 于 2009-11-19 21:48 编辑 ]

评分

12

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2009-11-19 16:54 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
原以为论坛的字符数限制,一个帖写不完,不过发现去掉部分说明后还是能写完
打了这么多的字,真是累啊。
希望大家看完后能有所收获,也不枉我眼酸手痛一场了

[ 本帖最后由 joforn 于 2009-11-19 17:41 编辑 ]

TA的精华主题

TA的得分主题

发表于 2009-11-19 16:58 | 显示全部楼层

TA的精华主题

TA的得分主题

发表于 2009-11-19 17:04 | 显示全部楼层
ERROR2:
  Debug.Print "发生错误,错误代码:" & Err.Number & " 错误信息" & Err.Description & " 错误源:" & Err.Source & " 当前I的值=" & I
  Resume Next           ' 返回发生错误处的下一条语句,继续执行
End Sub

Resume Next  这种用法我还不知道,学习了

TA的精华主题

TA的得分主题

 楼主| 发表于 2009-11-19 17:42 | 显示全部楼层
原帖由 lipton 于 2009-11-19 17:04 发表
ERROR2:
  Debug.Print "发生错误,错误代码:" & Err.Number & " 错误信息" & Err.Description & " 错误源:" & Err.Source & " 当前I的值=" & I
  Resume Next           ' 返回发生错误处的下一条语句,继续执行 ...


客气,共同学习。

TA的精华主题

TA的得分主题

 楼主| 发表于 2009-11-19 20:00 | 显示全部楼层

TA的精华主题

TA的得分主题

发表于 2009-11-19 20:06 | 显示全部楼层

TA的精华主题

TA的得分主题

发表于 2009-11-19 20:45 | 显示全部楼层

TA的精华主题

TA的得分主题

发表于 2009-11-19 20:53 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
很好的整理和综合,收下,谢谢。

TA的精华主题

TA的得分主题

发表于 2009-11-19 22:52 | 显示全部楼层
为了楼主的辛苦顶一下
做程序要有严谨的态度。。。
缜密的思维。。。。
严密的逻辑。。
马马虎虎,混混的或许能做好人但做不好程序
您需要登录后才可以回帖 登录 | 免费注册

本版积分规则

手机版|关于我们|联系我们|ExcelHome

GMT+8, 2024-11-23 22:36 , Processed in 0.038048 second(s), 10 queries , Gzip On, MemCache On.

Powered by Discuz! X3.4

© 1999-2023 Wooffice Inc.

沪公网安备 31011702000001号 沪ICP备11019229号-2

本论坛言论纯属发表者个人意见,任何违反国家相关法律的言论,本站将协助国家相关部门追究发言者责任!     本站特聘法律顾问:李志群律师

快速回复 返回顶部 返回列表