ExcelHome技术论坛

 找回密码
 免费注册

QQ登录

只需一步,快速开始

快捷登录

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

[分享] 分享一些关于VBA代码优化提速的资料

  [复制链接]

TA的精华主题

TA的得分主题

发表于 2009-12-7 15:03 | 显示全部楼层 |阅读模式
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
本帖已被收录到知识树中,索引项:数据类型和基本语句
说明:下面的资源是在网上收集到的关于VB的代码优化提速技巧的资料,鉴于VBA与VB的关系,资料中绝大多数技巧对于VBA同样适用。其中的大多数代码都是亲自在VBA中测试过的,某些地方加了一点注解。

         如果有前辈发过,就当我火星好了。能帮到第一次看到的朋友最好,知道了的朋友就略过无视吧。

--------------------------------------------------------------------------------------------------------------------------------------------

在这个帖子中你将看到下列信息

字符串(本楼)
Mid$高速生成长字符串——代码并不一定短才好
用InStr函数实现代码减肥——太意外了
使用"$-类型"字符串函数会更快
妙用Replace函数替代字符串连接操作符&——用起来感觉太好了
固定长度字符串数组:赋值快,释放快!
创建任意长度重复字符串的简洁方法
另辟蹊径处理字符串中的字符:字节数组法
无闪烁地快速附加字符串到textbox控件
表达式(二楼)
精用Boolean表达式让代码减肥
And、Or和Xor优化表达式
变量、常量、数组、函数(三楼)
函数名巧做局部变量
火眼识破隐藏的Variant变量——给偷懒和以C,C++出身的朋友
为常量定义合适的类型
静态变量慢于动态变量
访问简单变量总是快于数组元素值
快速清除数组部分内容
快速初始化Variant和String类型数组
控件、其他(五楼)
从头开始删除集合项目——快速删除集合
GoSub在编译程序中速度变慢
减少DoEvents语句的数量
读取文件内容的简洁方法
字体对象克隆招法
你真正理解"Allow Unrounded Floating Point Operations"选项的含义吗?——呵呵,我不理解
除法运算符"\"与"/"的区别
Friend过程快于Public过程
使用Objptr函数快速查找集合中的对象,ObjPtr妙用
创建新表时,快速拷贝字段
快速找到选中的OptionButton
表单及控件的引用阻止了表单的卸载
重定义编译DLL文件的基地址——太深奥了
快速调入TreeView控件以及ListView控件的子项内容
版友提供技巧及高级经典代码总结(六楼)

老伙计2008——被传递的变量运算速度,比本地变量慢4倍

高级代码精典总结

OK,节约用地,从此开始:

=====================================我是分割线 No.1==============================
  • Mid$高速生成长字符串

         大家都知道, &操作符的执行速度是相当慢的,特别是处理长字符串时.当必须重复地在同一变量上附加字符时,有一个基于Mid$命令的技巧可以使用.基本思路就是:预留一个足够长的空间存放操作的结果.下面是应用这个技术的一个例子.
假设要建立一个字符串,它要附加从1开始的10000个整数:"1 2 3 4 5 6 7 ... 9999 10000".下面是最简单的实现代码:
  1. res = ""
  2. For i = 1 to 10000
  3. res = res & Str(i)
  4. Next
复制代码
         代码虽然简单,但问题也很明显:Res变量将被重分配10000次.下面的代码实现同样的目的,但效果明显好转:
  1. Dim res As String
  2. Dim i As Long
  3. Dim index As Long
  4. ‘预留足够长的缓冲空间
  5. res = Space(90000)
  6. ‘指针变量,指出在哪里插入字符串
  7. index = 1
  8. ‘循环开始
  9. For i = 1 to 10000
  10. substr = Str(i)
  11. length = Len(substr)
  12. ‘填充字符串的相应区间段数值
  13. Mid$(res, index, length) = substr
  14. ‘调整指针变量
  15. index = index + length
  16. Next
  17. ‘删除多余字符
  18. res = Left$(res, index - 1)
复制代码
        测试表明:在一个333MHz的计算机上,前段代码执行时间为2.2秒,后者仅仅为0.08秒!代码虽然长了些,可是速度却提高了25倍之多.呵呵,由此看来:代码也不可貌相啊(看样子,代码并不一定短才好)


=================我是分割线===============
用InStr函数实现代码减肥

         可以采用”旁门左道”的方式使用Instr函数实现代码的简练.下面是一个典型的例子,检测字符是否是一个元音字母:
       1、普通的方法:
  1. If UCase$(char) = "A" Or UCase$(char) = "E" Or UCase$(char) = "I" Or UCase$(char) = "O" Or UCase$(char) = "U" Then
  2.       ' it is a vowel
  3. End If
  4. 'VBA中例子函数
  5. Function 是否元音字母1(char As String) As Boolean
  6.       是否元音字母 = False
  7.       If UCase$(char) = "A" Or UCase$(char) = "E" Or UCase$(char) = "I" Or UCase$(char) = "O" Or UCase$(char) = "U"  Then
  8.             是否元音字母 = True
  9.       End If
  10. End Function
复制代码
       2、更加简练的方法:
  1. If InStr("AaEeIiOoUu", char) Then
  2. ' it is a vowel
  3. End If
  4. 'VBA中例子函数
  5. Function 是否元音字母2(char As String) As Boolean
  6. 是否元音字母 = False
  7. If InStr("AaEeIiOoUu", char) Then
  8. 是否元音字母 = True
  9. End If
  10. End Function
复制代码
同样,通过单词中没有的字符作为分界符,使用InStr来检查变量的内容.下面的例子检查Word中是否包含一个季节的名字:

       1、普通的方法:
  1. If LCase$(word) = "winter" Or LCase$(word) = "spring" Or LCase$(word) = "summer" Or LCase$(word) = "fall" Then
  2. 'it is a season's name这是一个季节名称??
  3. End If
复制代码
       2、更加简练的方法:
  1. If Instr(";winter;spring;summer;fall;", ";" & word & ";") Then
  2. ' it is a season's name
  3. End If
复制代码
有时候,甚至可以使用InStr来替代Select Case代码段,但一定要注意参数中的字符数目.下面的例子中,转换数字0到9的相应英文名称为阿拉伯数字: (下面这个太高档了,很难想到)

       1、普通的方法:
  1. Select Case LCase$(word)
  2. Case "zero"
  3. result = 0
  4. Case "one"
  5. result = 1
  6. Case "two"
  7. result = 2
  8. Case "three"
  9. result = 3
  10. Case "four"
  11. result = 4
  12. Case "five"
  13. result = 5
  14. Case "six"
  15. result = 6
  16. Case "seven"
  17. result = 7
  18. Case "eight"
  19. result = 8
  20. Case "nine"
  21. result = 9
  22. End Select
复制代码
       2、更加简练的方法
  1. result =InStr(";zero;;one;;;two;;;three;four;;five;;six;;;seven;eight;nine;", ";" & LCase$(word) & ";") \ 6
复制代码
——怎么样?这个是比较变态吧,很难想到,不过确实很巧妙。很难想象吧,上面那么长一段代码这里就这一句就搞定了



======================我也是分割线======================

使用"$-类型"字符串函数会更快
——在这里确实很少见到大家用含$的函数,本人也是偶尔用用,我原来的那本VB6教材中还是有介绍的
         VB官方文档似乎很鼓励使用"无$"类字符串函数,比如:Left、LTrim或者UCase,而不是实现同样功能的Left$、LTrim$和UCase$函数.但是我们必须认识到:前者返回variant类型的数值,当用于字符串表达式中时,最终必须要转换为字符串(string)类型. 因此,在严格要求时间的代码段中,我们应该使用后者,它们将快5-10%.

=======================又见分割线=======================

妙用Replace函数替代字符串连接操作符&
         你大概不知道Replace函数还能这么用吧?比如下面的语句:
  1. MsgBox "Disk not ready." & vbCr & vbCr & _
  2. "Please check that the diskette is in the drive" & vbCr & _
  3. "and that the drive's door is closed."
复制代码
可以看出,为了显示完整的字符串含义,要将可打印字符与非打印字符(比如:回车符vbCr)用&符号连接在一起.结果是:长长的字符连接串变得难于阅读.但是,使用Replace函数,可以巧妙地解决这个问题.方法就是:将非打印字符以字符串中不出现的一个可打印字符表示,这样完整地写出整个字符串,然后使用Replace函数替换那个特别的打印字符为非打印字符(比如:回车符vbCr).代码如下:
  1. MsgBox Replace("Disk not ready.§§Please check that the diskette is in the " _
  2. & "drive§and that the drive's door is closed.", "§", vbCr)
复制代码
注:这个可真的没有想到过,汗一个


=========================我不说你也知道我是分割线了吧========================

固定长度字符串数组:赋值快,释放快!
          固定长度字符串的处理速度通常慢于可变长度字符串,这是因为所有的VB字符串函数和命令只能识别可变长度字符串.因此,所有固定长度字符串必然被转换为可变长度字符串.
          但是,由于固定长度字符串数组占据着一块连续的内存区域,因此在被分配以及释放时,速度明显快于可变长度的数组.比如:在一个Pentium 233MHz机器上,对于一个固定长度为100,000的数组,给其中30个位置分配数值,大约只花费半秒种的时间.而如果是可变长度的数组,同样的操作要耗费8秒之多!后者的删除操作耗时大约0.35秒,但固定长度的数组几乎可以立即"毙命"!如果应用程序中涉及到这么大的一个数组操作,选择固定长度方式数组绝对是确定无疑的了,无论是分配数值,还是释放操作,都可以风驰电掣般完成.

=========================== 我又来了=========================================

创建任意长度重复字符串的简洁方法
           String$函数只能重复复制单字符(99%的朋友都知道吧),当需要重复复制2个或多个字符时,就需要一个循环.看起来是否很麻烦?
           然而,使用以下的函数就能解决这个问题.基本思路是:建立一个空格字符串,其长度为要重复复制的数目,然后替换每一个空格为要复制的字符串
  1. Function ReplicateString(Source As String, Times As Long) As String
  2. ReplicateString = Replace$(Space$(Times), " ", Source)
  3. End Function
复制代码
但是请注意:根据字符串的长度以及重复的数目,这个方法也许比传统的循环方法要慢些.


=========================== 我又来了=====================================

另辟蹊径处理字符串中的字符:字节数组法
          当要处理字符串中的每一个字符时,可以将字符串赋值到一个byte数组中进行操作.要记住:每一个Unicode字符对应双字节.
         这种方法通常要快许多,因为节省了大量的Mid$函数操作以及大量的临时字符串空间.下面的代码是统计字符串中空格数目的最快方法
  1. Dim b() as Byte, count As Integer
  2. b() = source$
  3. For i = 0 to UBound(b) Step LenB("A")
  4. If b(i) = 32 Then count = count + 1
  5. Next
复制代码
请注意上面代码中LenB()函数的特殊用法:在VB4(32位)、VB5和VB6中它返回数值2, 在VB4(16位)中返回数值1.因此,我们就可以使用同一代码段,而无需#If编译指令. ( 什么?不知道VB456,就当这句话没说


================================= 换个马甲 =============================

无闪烁地快速附加字符串到textbox控件
           附加文本到TextBox或者RichTextBox控件的通常方法是在当前内容上连接上新的字符串:
Text1.Text = Text1.Text & newString
           但还有一个更快的方法,并且会减少连接操作的闪烁感,代码如下:
Text1.SelStart = Len(Text1.Text)
Text1.SelText = newString

好吧,本楼到此为止,下一楼来贴表达式的东东。

[ 本帖最后由 Moneky 于 2009-12-21 13:19 编辑 ]

评分

10

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2009-12-7 15:04 | 显示全部楼层
这一楼是关于表达式的
精用Boolean表达式让代码减肥
        当设置基于表达式结果的Boolean型数值时,要避免使用多余的If/Then/Else语句结果.比如:
  1. If SomeVar > SomeOtherVar Then
  2. BoolVal = True
  3. Else
  4. BoolVal = False
  5. End If
复制代码
上面这段代码就很烦琐,它们完全可以使用下面的一行代码来替代:
  1. BoolVal = (SomeVar > SomeOtherVar)
复制代码
括号不是必须的,但可以增加可读性.根据表达式中的操作数不同,后者比前者执行起来大约快50%到85%.后者中的括号对速度没有影响. 

        有时,使用这个技术实现代码的简练并非很明显.关键是要牢记:所有的比较操作结果或者是0(false),或者是-1(True).所以,下面例子中的2段代码是完全相同的,但是第2段要运行得快些:
1、传统方法:
  1. If SomeVar > SomeOtherVar Then
  2. x = x + 1
  3. End If
复制代码
2、更简练的方法
  1. x = x - (SomeVar > SomeOtherVar)
复制代码
----------------------------------------------------------------------------------------------------------------------

And、Or和Xor来优化表达式
        要检测一个整数值的最高有效位是否有数值,通常要使用如下的代码(有二种情况:第一组If判断表明对Integer类型,第二组对Long类型):
  1. If intValue And &H8000 Then
  2. ' most significant bit is set
  3. End If
  4. If lngValue And &H80000000 Then
  5. ' most significant bit is set
  6. End If
复制代码
但由于所有的VB变量都是有符号的,因此,最高有效位也是符号位,不管处理什么类型的数值,通过下面的代码就可以实现检测目的:
  1. If anyValue < 0 Then
  2. ' most significant bit is set
  3. End If
复制代码
&#160; &#160; &#160; &#160; '注:上面这段话似乎涉及到数据在内存中的储存表达方式,本人不是太明白,不过下面的代码还是有效的,也都能够理解

&#160; &#160; &#160; &#160; 另外,要检测2个或者更多个数值的符号,只需要通过一个Bit位与符号位的简单表达式就可以完成.下面是应用这个技术的几段具体代码:
  1. 1、判断X和Y是否为同符号数值:
  2. If (x < 0 And y < 0) Or (x >= 0 And y >=0) Then ...
  3. ' the optimized approach
  4. If (x Xor y) >= 0 Then
  5. 2、判断X、Y和Z是否都为正数
  6. If x >= 0 And y >= 0 And z >= 0 Then ...
  7. ' the optimized approach
  8. If (x Or y Or z) >= 0 Then ...
  9. 3、判断X、Y和Z是否都为负数
  10. If x < 0 And y < 0 And z < 0 Then ...
  11. ' the optimized approach
  12. If (x And y And z) < 0 Then ...
  13. 4、判断X、Y和Z是否都为0
  14. If x = 0 And y = 0 And z = 0 Then ...
  15. ' the optimized approach
  16. If (x Or y Or z) = 0 Then ...
  17. 5、判断X、Y和Z是否都不为0
  18. If x = 0 And y = 0 And z = 0 Then ...
  19. ' the optimized approach
  20. If (x Or y Or z) = 0 Then ...
复制代码
要使用这些来简单化一个复杂的表达式,必须要完全理解boolean型的操作原理.比如,你可能会认为下面的2行代码在功能上是一致的:
  1. If x <> 0 And y <> 0 Then
  2. If (x And y) Then ...
复制代码
然而我们可以轻易地证明他们是不同的,比如X=3(二进制=0011),Y=4(二进制=0100).不过没有关系,遇到这种情况时,我们可以对上面的代码进行局部优化,就能实现目的.代码如下:
  1. If (x <> 0) And y Then ...
复制代码
这楼到此为止,下面接着贴 变量、常量、数组、函数

[ 本帖最后由 Moneky 于 2009-12-8 11:33 编辑 ]

评分

1

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2009-12-7 15:05 | 显示全部楼层
这一楼是关于&#160; &#160;变量、常量、数组、函数
函数名巧做局部变量 这个我倒是无师自通,经常这样干
&#160; &#160;&#160; &#160;&#160;&#160;很多程序员都没有认识到"在函数本身中使用函数名"的妙处,这就象对待一个局部变量一样.应用这个技巧可以起到临时变量的作用,有时还能加速程序运行.看看下面的代码:
  1. Function Max(arr() As Long) As Long
  2. Dim res As Long, i As Long
  3. res = arr(LBound(arr))
  4. For i = LBound(arr) + 1 To UBound(arr)
  5. If arr(i) > res Then res = arr(i)
  6. Next
  7. Max = res
  8. End Function
复制代码
去掉res变量,使用函数名称本身这个局部变量,可以使程序更加简练
  1. Function Max(arr() As Long) As Long
  2. Dim i As Long
  3. Max = arr(LBound(arr))
  4. For i = LBound(arr) + 1 To UBound(arr)
  5. If arr(i) > Max Then Max = arr(i)
  6. Next
  7. End Function
复制代码
=================================很久没有报过名头了似乎==============================
火眼识破隐藏的Variant变量
&#160; &#160; &#160; &#160; 如果没有用As语句声明变量,默认类型就是Variants,比如:&#160;
Dim name ' this is a variant
&#160; &#160; &#160; &#160; 或者,当前模块下没有声明Option Explicit语句时,任何变量都是Variants类型.&#160;
&#160; &#160; &#160; &#160; 许多开发者,特别是那些先前是C程序员的人,都会深信下面的语句将声明2个Interger类型变量:&#160;
Dim x, y As Integer &#160; &#160;&#160; &#160;这个非常常见

&#160; &#160; &#160; &#160; 而实际上,x被声明为了variant类型.由于variant类型变量要比Integer类型慢很多,所以要特别注意这种情况.正确的一行声明方法是:&#160;
Dim x As Integer, y As Integer
============================================================
为常量定义合适的类型
&#160; &#160; &#160; &#160; VB在内部使用最简单、最可能的数据类型保存符号数值,这意味着最通常的数字类型-比如0或者1-都按照Integer类型存储.如果在浮点表达式中使用这些常量,可以通过常量的合适类型来加速程序运行,就象下面的代码:&#160;
value# = value# + 1#.
&#160; &#160; &#160; &#160; 这个语句强迫编译器按照Double格式存储常量,这样就省却了运行时的隐含转换工作.还有另外的一种处理方法就是:在常量声明时就进行相应类型的定义,代码如下:&#160;
Const ONE As Double = 1&#160; &#160; '汗啊,从来没有这么写过,以后要改了
==============================================================
静态变量慢于动态变量
——注:下面这个我不是很理解
&#160; &#160; &#160; &#160; 在过程中引用静态局部变量要比引用常规局部动态变量慢2-3倍.要想真正地加速过程的执行速度,最彻底的方法就是将所有的静态变量转换为模块级别变量.&#160;
&#160; &#160; &#160; &#160; 这种方法的唯一不足是:过程很少是自包含的,如果要在其他工程中重用,就必须同时拷贝并粘贴这些模块级别变量.&#160;
&#160; &#160; &#160; &#160; 另外的一种处理方法是:在时间要求高的循环前,将静态变量数值装入动态变量中.&#160;
&#160; &#160; &#160; &#160; 善用"Assume No Aliasing"(注:似乎是假定无别名)编译选项&#160;
&#160; &#160; &#160; &#160; 据说,如果过程能够2次或多次引用同样的内存地址,那么过程就会包含别名数值.一个典型的例子如下:
  1. Dim g_GlobalVariable As Long
  2. ...
  3. Sub ProcWithAliases(x As Long)
  4. x = x + 1
  5. g_GlobalVariable = g_GlobalVariable + 1
  6. End Sub
复制代码
如果传递给这个过程g_GlobalVariable变量,则将通过一个直接引用以及x参数两种方式修改变量的数值2次. (注:这里可以理解,但从来没有写过类似这样的代码 :-D

&#160; &#160; &#160; &#160; 注:下面的话是对VB说的
&#160; &#160; &#160; &#160; 别名数值经常是不良编程习惯的产物,对于程序优化有害无益.事实上,如果能够完全确认应用程序从来没有使用到别名变量,就可以打开"Assume No Aliasing"高级编译选项,这将告知编译器没有过程可以修改同一内存地址,使编译器产生更加有效率的汇编代码.更特别的是,编译程序将试图缓冲这些数据到CPU的寄存器中,从而明显地加速了程序运行.&#160;
总结一下,当遇到以下情况时,就不会有别名数值:
(1) 过程不引用任何全局变量
(2) 过程引用了全局变量,但从来不通过ByRef参数类型传递同一变量给过程
(3) 过程含有多个ByRef参数类型,但从来不传递同一变量到其中的2个或者多个之中.
===========================================================================
访问简单变量总是快于数组元素值
&#160; &#160;&#160; &#160;&#160; &#160;读写数组中的元素速度通常都慢于访问一个简单变量,因此,如果在一个循环中要重复使用同一数组元素值,就应该分配数组元素值到临时变量中并使用这个变量.下面举一个例子,检测整数数组中是否存在重复项:
  1. Function AnyDuplicates(intArray() As Integer) As Boolean
  2. '如果数组包含重复项,返回True
  3. Dim i As Long, j As Long,
  4. Dim lastItem As Long
  5. Dim value As Integer
  6. '只计算机UBound()一次
  7. lastItem = UBound(intArray)
  8. For i = LBound(intArray) To lastItem
  9. ' 保存intArray(i)到非数组变量中
  10. value = intArray(i)
  11. For j = i + 1 To lastItem
  12. If value = intArray(j) Then
  13. AnyDuplicates = True
  14. Exit Function
  15. End If
  16. Next
  17. Next
  18. '没有发现重复项
  19. AnyDuplicates = False
  20. End Function
复制代码
上述程序有2层循环,通过缓存intArray(i)的数值到一个普通的、非数组变量中,节省了CPU运行时间.经测试,这将提高80%的速度.
        注:这其实是一种思想。我们在Excel VBA中如果,如果一段代码中需要多次引用到一个对象的属性值(如cells(5,5),range.address等等),那么我们可以考虑在代码真正处理前先用一个临时变量将那个对象属性值保存起来,在代码中只对那个变量进行操作即可,最后如有需要再将变量写回到原对象。

===================================================================
快速清除数组部分内容 后面楼还有清除集合的技巧
&#160; &#160; &#160; &#160; 清除动态数组的最快方法是使用ReDim,清除静态数组则是使用删除.但是如果只想清除数组的一部分内容,怎么办呢?看上去似乎只能使用For-Next循环了.&#160;
&#160; &#160; &#160; &#160; 如果处理的是数字数组,有一个较快的方法.它基于ZeroMemory API函数,正如函数名所示,它能将一块内存区域填充为0.看看怎么应用这个函数来清除一个Long类型数组的一部分内容:
  1. Private Declare Sub ZeroMemory Lib "kernel32" Alias "RtlZeroMemory" (dest As Any, ByVal Bytes As Long)
  2. '定义数组,填充数据
  3. Dim a(1000) As Long
  4. For i = 1 To UBound(a)
  5. a(i) = i
  6. Next
  7. '从a(200)开始清除100个元素内容
  8. ZeroMemory a(200), 100 * Len(a(1))
复制代码
请注意上述代码中清除长度的表示方法,使用Len()函数保证了当数组类型改变时代码仍然有效.&#160;

================================================================================
快速初始化Variant和String类型数组
&#160; &#160; &#160; &#160; 在编程的时候,将变量进行初始化也是非常重要的!特别的变体类型和字符串类型,下面将有个优化过程
VB中没有提供定义数组并同时初始化其内容的方法,所以大多数情况下,必须单独地设置每一个元素,就象下面一样:
  1. Dim strArray(0 To 3) As String
  2. strArray(0) = "Spring"
  3. strArray(1) = "Summer"
  4. strArray(2) = "Fall"
  5. strArray(3) = "Winter"
复制代码
在VB4、VB5和VB6中,可以使用Array()函数随意创建一个Variants类型数组:
  1. Dim varArray() As Variant
  2. varArray() = Array("Spring", "Summer", "Fall", "Winter") '这个我们现在似乎经常在用噢
复制代码
但却没有同样的函数能创建非Variant类型数组.但是我们发现,在VB6中可以使用Split()函数创建字符串数组:
  1. Dim varArray() As String
  2. '由Split建立的数组下标通常是从0开始的
  3. varArray() = Split("Spring;Summer;Fall;Winter", ";")
复制代码
在VB6中,同样能充分利用函数返回数组的能力,创建数组初始化程序段.比如下面的代码段:&#160;

&#160; &#160; &#160; &#160; ——注:这一段原来不理解在ldy版主的解释下已经理解了,不理解的可以看第3页ldy版主的应用例子
  1. Function ArrayInt(ParamArray values() As Variant) As Integer()
  2. Dim i As Long
  3. ReDim res(0 To UBound(values)) As Integer
  4. For i = 0 To UBound(values)
  5. res(i) = values(i)
  6. Next
  7. ArrayInt = res()
  8. End Function
复制代码
同时,也可以创建一个子程序段来检测传递给它的数值的类型,并返回正确类型的数组.这种情况下,函数应该定义为返回Variant.



好啦,此楼也到此为止吧,下一楼贴些&#160;&#160;控件和其他的一些资料

[ 本帖最后由 Moneky 于 2009-12-8 21:38 编辑 ]

TA的精华主题

TA的得分主题

发表于 2009-12-7 15:05 | 显示全部楼层

TA的精华主题

TA的得分主题

 楼主| 发表于 2009-12-7 15:05 | 显示全部楼层
下面是一些关于控件和……其他不好分类的东东

快速删除集合——从头开始删除集合项目
&#160; &#160; &#160; &#160; 删除集合中的所有内容有许多方法,其中有些非常得迅速.来看看一个包含10,000个项目的集合:
下面这段是创建一个集合
  1. Dim col As New Collection, i As Long
  2. For i = 1 To 10000
  3.    col.Add i, CStr(i)
  4. Next
复制代码
可以从末尾位置为起点删除集合内容,如下:
  1. For i = col.Count To 1 Step -1
  2. col.Remove i
  3. Next
复制代码
也可以从开始位置为起点删除集合内容,如下:
  1. For i = 1 To col.Count Step 1
  2. col.Remove 1 '这里原文是错的,原文是 col.remove i
  3. Next
复制代码
测试证明,后者要快于前者百倍多(我也试过,太震撼了),比如0.06秒比4.1秒.原因在于:当引用接近末尾位置的集合项目时,VB必须要从第1个项目开始遍历整个的项目链.更有趣的是,如果集合项目的数量加倍,那么从末尾开始删除与从头开始删除,前者要比后者花费的时间将成倍增长,比如前者是24秒,后者可能为0.12秒这么短!
&#160; &#160; &#160; &#160; 最后提醒您:删除集合的所有内容的最快方法就是"毁灭"它,使用下面的语句:
  1. Set col = New Collection
复制代码
对于一个包含20,000个项目的集合,上述操作仅仅耗时0.05秒,这比使用最快的循环操作进行删除也要快2倍左右.
========================================================
GoSub在编译程序中速度变慢这个对VBA似乎没有借鉴意义,但为了完整性还是贴上来
&#160; &#160; &#160; &#160; 编译为本地代码的VB应用程序中,如果使用 Go Subs 命令,就会比通常的 Subs 或者 Function 调用慢5-6倍;相反,如果是p-code模式,就会相当快.
&#160; &#160; &#160; &#160; ——注:我从来没有在VB/VBA中用过go sub (不记得到底是什么了难道是:goto sub?),以前在Qbasic中似乎用过

==========================下面这个关于Doevents=======================
减少DoEvents语句的数量
&#160; &#160; &#160; &#160; 不要在代码中放置不必要的DoEvents语句,尤其是在时间要求高的循环中.遵循这个原则,至少能在循环中的每N次反复时才执行DoEvents语句,从而增强效率.比如使用下面的语句:
  1. If (loopNdx Mod 10) = 0 Then DoEvents
复制代码
如果只是使用DoEvents来屏蔽鼠标以及键盘操作,那么就可以在事件队列中存在待处理项目时调用它.通过API函数GetInputState来检查这个条件的发生:
  1. Declare Function GetInputState Lib "user32" Alias "GetInputState" () As Long
  2. ' ...上一句是API声明
  3. '下面这句可以写在一个循环中
  4. If GetInputState() Then DoEvents
复制代码
======================================================================
读写文件 如果数据量大的时候,读取文件内容的简洁方法
&#160; &#160; &#160; &#160; 读取text文件的最快方法是使用Input$函数,就象下面的过程:
  1. Function FileText (filename$) As String
  2. Dim handle As Integer
  3. handle = FreeFile
  4. Open filename$ For Input As #handle
  5. FileText = Input$(LOF(handle), handle)
  6. Close #handle
  7. End Function
复制代码
使用上述方法要比使用Input命令读取文件每一行的方法快很多.下面是应用这个函数读取Autoexec.bat的内容到多行textbox控件的例子:
  1. Text1.Text = FileText("c:\autoexec.bat")
复制代码
但请注意:当文件包含Ctrl-Z(EOF)字符时,上面的函数代码可能会发生错误.因此,要修改一下代码:
  1. Function FileText(ByVal filename As String) As String
  2. Dim handle As Integer
  3. ' 判断文件存在性
  4. If Len(Dir$(filename)) = 0 Then
  5. Err.Raise 53 '文件没有找到
  6. End If
  7. ' 以binary模式打开文件
  8. handle = FreeFile
  9. Open filename$ For Binary As #handle
  10. ' 读取内容,关闭文件
  11. FileText = Space$(LOF(handle))
  12. Get #handle, , FileText
  13. Close #handle
  14. End Function
复制代码
====================================================================
字体对象克隆招法
&#160; &#160; &#160; &#160; 当要应用一个控件的字体到另一控件时,最直接的方法就是直接赋值:
  1. Set Text2.Font = Text1.Font
复制代码
但多数情况下这种方法并不奏效,因为这实际上是将同一字体的引用分配给了2个控件.换言之,当随后修改其中之一控件的字体时,另外一个控件也受到影响.因此,要实现我们的目的,需要做的就是克隆字体对象并赋值给需要的控件.&#160;
&#160; &#160; &#160; &#160; 最简单的克隆字体的方法是手工地拷贝所有单独的字体属性,就象下面一样:
  1. Function CloneFont(Font As StdFont) As StdFont
  2. Set CloneFont = New StdFont
  3. CloneFont.Name = Font.Name
  4. CloneFont.Size = Font.Size
  5. CloneFont.Bold = Font.Bold
  6. CloneFont.Italic = Font.Italic
  7. CloneFont.Underline = Font.Underline
  8. CloneFont.Strikethrough = Font.Strikethrough
  9. End Function
复制代码
'函数的应用
  1. Set Text2.Font = CloneFont(Text1.Font)
复制代码
如果使用VB6,就可以使用PropertyBag对象快速拷贝所有字体属性,并且代码会很简练、速度也快2倍:
  1. Function CloneFont(Font As StdFont) As StdFont
  2. Dim pb As New PropertyBag
  3. '拷贝字体到PropertyBag对象中
  4. pb.WriteProperty "Font", Font
  5. '恢复字体对象到新控件
  6. Set CloneFont = pb.ReadProperty("Font")
  7. End Function
复制代码
但是我们还能进一步地对代码进行优化,方法就是使用可被所有StdFont对象识别的隐藏IFont接口.这个接口具有一个Clone方法,用它就可以精确地实现我们的目的.它以非正常方式执行:创建一个克隆Font对象,然后返回相应的引用.这可能是实现克隆目的的最简洁代码了,而且,执行速度也是这里列举的3种方法中最快的一个,要比使用PropertyBag对象的方法快大约3倍左右.来看看具体代码:
  1. Function CloneFont(Font As IFont) As StdFont
  2. Font.Clone CloneFont
  3. End Function
复制代码
====================================================================
你真正理解"Allow Unrounded Floating Point Operations"选项的含义吗?
&#160; &#160; &#160; &#160; 注:这似乎是VB编译器选项 允许不舍入的浮点操作 反正我是不理解的。我们用VBA的看看就算了吧。
&#160; &#160; &#160; &#160; 来自微软的资料鼓吹:高级优化对话框中的所有编译选项都被认为是不稳定的,它们可能导致不正确的结果,甚至程序崩溃.对于其中的大多数,这种说法是正确的,但是经常有一个叫做"Allow Unrounded Floating Point Operations"的选项能够给予正确的结果,防止应用程序产生bug.考虑下面的代码段:
  1. Dim x As Double, y As Double, i As Integer
  2. x = 10 ^ 18
  3. y = x + 1 ' this can't be expressed with 64 bits
  4. MsgBox (y = x) ' 显示 "True" (不正确的结果)
复制代码
严格地说,由于X和Y变量不包含相同的数值,MsgBox将显示False.可问题是,由于数值1E18与1E18+1都以相同的64位浮点Double类型来表示,它们最终包含了几乎相同的数值,最后的MsgBox结果将是True.&#160;
&#160; &#160; &#160; &#160; 如果打开了"Allow Unrounded Floating Point Operations"编译选项,VB就能重用已在数学协处理器堆栈中的数值,而不是内存中的数值(比如:变量).因为FPU堆栈具备80位的精度,因此就可以区分出这2个数值的不同:
  1. ' if the program is compiled using the
  2. ' "Allow Unrounded Floating Point Operations" compiler option
  3. MsgBox (y = x) ' 显示 "False" (正确的结果)
复制代码
总结一下:当以解释模式、或者编译的p-code模式、或者编译的native代码模式但关掉"Allow Unrounded Floating Point Operations"选项这3种方式运行一个程序时,所有浮点数字运算在内部都以80位的精度进行处理.但如果有一个数值是存储在64位Double变量中,结果就是接近的了,并且,随后使用那个变量的表达式也将产生近似的结果,而不是绝对正确的结果.&#160;
&#160; &#160;&#160; &#160; &#160; &#160; &#160; 相反,如果打开"Allow Unrounded Floating Point Operations"编译选项后运行一段native编译代码,在随后的表达式中VB就经常能重用内部的80位数值,而忽略存储在变量中的当前数值.注意:我们并不能完全控制这个功能,VB也许对此生效,也许就不生效,这要取决于表达式的复杂程度以及最初分配数值语句与随后产生结果的表达式语句的距离远近.&#160;
=====================================================================================
除法运算符"\"与"/"的区别
&#160; &#160; &#160; &#160; ——注:本人历来以为 \ 是整除运算符&#160; &#160;结果只取整数部分,难道不对?
&#160; &#160; &#160; &#160; 整数间执行除法运算时(不就是整除么?),要使用 "\" 而不是 "/". "/" 运算符要求返回一个单一数值,所以,表面上看似简单的一行代码:
  1. C% = A% / B%
复制代码
实际上包含了3个隐含的转换操作:2个为除法运算做准备,从Integer转换到Single;一个完成最后的赋值操作,从Integer转换到Single.但是如果使用了"\"操作符,情况就大不相同了!不仅不会有这么多中间步骤,而且执行速度大大提高.&#160;
&#160; &#160; &#160; &#160; 同时请记住:使用"/"操作符做除法运算时,如果其中之一是Double类型,那么结果就将是Double类型.所以,当2个Integer或者Single类型数值做除法运算时,如果想得到高精度的结果,就需要手工强迫其中之一转换为Double类型:&#160;
'结果为 0.3333333
Print 1 / 3
'结果为 0,333333333333333
Print 1 / 3#
============================================================
Friend过程快于Public过程
&#160; &#160; &#160; &#160; ——注:本人很少用到,主要是没有达到随手写类的能力,每次写的时候还是先到VB6中用类创建工具搭好框架再写代码的
&#160; &#160; &#160; &#160; 你可能会非常惊奇:Friend类型过程的执行速度要明显快于Public类型.这可以通过创建一个带有Private类和Public类 (设定Instancing = MultiUse)的ActiveX EXE工程看到,在2个类模块中添加下面的代码:
  1. Public Sub PublicSub(ByVal value As Long)
  2. '
  3. End Sub
  4. Public Function PublicFunction(ByVal value As Long) As Long
  5. '
  6. End Function
  7. Friend Sub FriendSub(ByVal value As Long)
  8. '
  9. End Sub
  10. Friend Function FriendFunction(ByVal value As Long) As Long
  11. '
  12. End Function
复制代码
然后,在表单模块中创建一个循环,执行每个例程许多次.比如,要在一个Pentium II机器上查看执行时间上的区别,可以调用每个例程1,000,000次.下面是测试的结果:&#160;
&#160; &#160; &#160; &#160; Private类模块中,反复调用1,000,000次Public Sub或者Function耗费了0.46秒,而调用内容相同的Friend类型模块则分别只有0.05秒和0.06秒.前后竟然相差了8-9倍之多!对于MultiUse类型的Public类模块,也是一样的结果.&#160;
&#160; &#160; &#160; &#160; 对于这个不可思议的结果的可能解释是:Friend型过程没有处理汇集和拆装代码的消耗(Public过程可以从当前工程外被调用,因此COM必须要来回地汇集数据).&#160;
&#160; &#160; &#160; &#160; 但是在多数情况下,这些时间差别是不明显的,特别是程序中包含一些复杂和耗时的语句时.&#160;
&#160; &#160; &#160; &#160; 即使这样,Friend型过程仍有其他的优势高于Public类型,比如:接受和返回在BAS模块中定义的UDT变量的能力.
=============================================================
使用Objptr函数快速查找集合中的对象,ObjPtr妙用
&#160; &#160; &#160; &#160; ObjPtr函数的一个最简单但是却最有效的用途就是提供快速寻找集合中对象的关键字.假设有一个对象集合,它没有可以当做关键字以从集合中取回的属性.那么,我们就可以使用ObjPtr函数的返回值作为集合中的关键字:
  1. Dim col As New Collection
  2. Dim obj As CPerson '这篇文章的原作者非常可能是C出身的:)
  3. '创建新的CPerson对象,并添加到集合中
  4. Set obj = New CPerson
  5. obj.Name = "John Smith"
  6. col.Add obj, CStr(ObjPtr(obj)) '关键字必须是字符串
复制代码
因为任何对象都有一个明确的ObjPtr数值,而且它是不变的,所以,我们可以容易地、快速地从集合中取回它:
  1. ' 删除集合中的对象
  2. col.Remove CStr(ObjPtr(obj))
复制代码
这个技巧可以适用于任何类型的对象,包括VB中的表单和控件,以及外部对象.&#160;
使用ObjPtr检测2个对象变量是否指向同一对象
&#160; &#160; &#160; &#160; 判断2个对象变量释放指向同一对象的方法是使用Is操作符,代码如下:
  1. If obj1 Is obj2 Then ...
复制代码
但当2个对象是同一类型时,或者指向同一个二级接口时,我们就可以利用ObjPtr()函数对代码进行一些优化处理:
  1. If ObjPtr(obj1) = ObjPtr(obj2) Then ...
复制代码
后者的执行速度将比前种方法快40%多.但是请注意,2种方法原本就是很有效率的,只有在时间要求非常严格的上百成千次的循环中,才会体现出这种差别.&#160;
==========================================================================
创建新表时,快速拷贝字段 这是VB用的,我们VBA看看算了吧
&#160; &#160; &#160; &#160; 在VB6中,无需离开开发环境就可以创建新的SQL Server和Oracle表.方法很简单:打开DataView窗口,用鼠标右键单击数据库的表文件夹,再选择新表格菜单命令.&#160;
&#160; &#160; &#160; &#160; 当处理相似表格时,就是说具有许多相同字段的表格,我们完全可以在很短的时间内容完成设定操作.具体步骤是:在设计模式下打开源表格,加亮选择要拷贝字段对应的行,按Ctrl-C拷贝信息到粘贴板;然后,在设计模式打开目标表格,将光标置于要粘贴字段所在的位置,按Ctrl-V.&#160;
&#160; &#160; &#160; &#160; 这样,就拷贝了所有的字段名称以及它们所带的属性.
==================================================================
快速找到选中的OptionButton
&#160; &#160; &#160; &#160; ——注:下面的代码是VB中用的,VBA要借鉴还需要加工一下
&#160; &#160; &#160; &#160; OptionButton控件经常是作为控件数组存在的,要快速找到其中的哪一个被选中,可以使用下面的代码:
  1. '假设控件数组包含3个OptionButton控件
  2. intSelected = Option(0).Value * 0 - Option(1).Value * 1 - Option(2).Value * 2
复制代码
注意,因为第一个操作数总是0,所以上述代码可以精简如下:
  1. intSelected = -Option(1).Value - Option(2).Value * 2
复制代码
=======================================================================
表单及控件的引用阻止了表单的卸载
&#160; &#160; &#160; &#160; ——注:这里的表单其实就是VBA中的UserForm,VB中的Form
&#160; &#160; &#160; &#160; 当指派表单或者表单上的控件到该表单模块以外的一个对象变量中时,如果要卸载表单,就必须首先将那个变量设置为 to Nothing.也就是说,如果不设置为Nothing,即使看不到这个对象了,但它仍旧是保存在内存中的.&#160;
&#160; &#160; &#160; &#160; 注意:这并非是一个bug,这仅仅是COM引用规则的一个结果.唯一要注意的就是引用的这个控件将阻止整个表单的卸载操作,它将依赖于它的父表单而存在.&#160;
=======================================================
快速调入TreeView控件以及ListView控件的子项内容
&#160; &#160; &#160; &#160; 有一个简单但仍未发现的技巧可用于在TreeView控件中装载多个节点,或者在ListView控件中装载多个ListItems.这种方法要比传统做法快.先看看下面这个传统方法
  1. For i = 1 To 5000
  2. TreeView1.Nodes.Add , , , "Node " & i
  3. Next
复制代码
改进一下,代替重复引用TreeView1对象的Nodes集合,我们可以先将之保存在临时对象变量中:
  1. Dim nods As MSComctlLib.Nodes
  2. Set nods = TreeView1.Nodes
  3. For i = 1 To 5000
  4. nods.Add , , , "Node " & i
  5. Next
复制代码
甚至,如果使用With代码块,还可以不需要临时变量:
  1. With TreeView1.Nodes
  2. For i = 1 To 5000
  3. .Add , , , "Node " & i
  4. Next
  5. End With
复制代码
经测试,优化的循环代码要比传统方法执行速度快40%左右.原因在于:将Nodes集合对象保存在临时变量中,或者应用With代码块后VB将使用隐藏的临时变量后,就可以避免在循环中重复绑定Nodes对象到它的父TreeView1对象上.由于这种绑定是低效率的,因此省却它就能节省大量的执行时间.&#160;
&#160; &#160; &#160; &#160; 同样的道理对于其他ActiveX控件也生效:&#160;
ListView控件的ListItems、ListSubItems以及ColumnHeaders集合
Toolbar控件的Buttons和ButtonMenus集合
ImageList的ListImages集合
StatusBar控件的Panels集合
TabStrip控件的Tabs集合







Let's End,下楼贴一些总结代码(原来网站网友跟帖总结的代码)

[ 本帖最后由 Moneky 于 2009-12-8 12:04 编辑 ]

TA的精华主题

TA的得分主题

 楼主| 发表于 2009-12-7 15:06 | 显示全部楼层
其他版友提供的技巧
老伙计2008  提供         被传递的变量运算速度,比本地变量慢4倍(注:我试了一下,是3倍左右,或许和电脑有关另外在http://club.excelhome.net/thread-515492-1-1.html也有讨论,速度差别并不是这么大,造成下面代码测试时间差别大的主要原因是因为test2中参数i没有声明类型)。下面是示例。
代码:
  1. Sub test1()
  2.     Dim i As Long
  3.     Dim j As Long
  4.     Dim t As Single
  5.     i = 0: j = 0
  6.     t = Timer
  7.    
  8.     For j = 1 To 10000000
  9.         i = i + 1
  10.     Next
  11.     t = Timer - t
  12.     MsgBox CStr(t) & "   i=" & CStr(i)
  13.     i = 0
  14.     test2 i
  15. End Sub
  16. Sub test2(i)
  17.     Dim j As Long
  18.     Dim t As Single
  19.     t = Timer
  20.     For j = 1 To 10000000
  21.         i = i + 1
  22.     Next
  23.     t = Timer - t
  24.     MsgBox CStr(t) & "   i=" & CStr(i)
  25. End Sub
复制代码

[ 本帖最后由 老伙计2008 于 2009-12-21 01:01 编辑 ]




高级代码精典总结
  1. '使用InStr来替代Select
  2. x= InStr(";zero;;one;;;two;;;three;four;;five;;six;;;seven;eight;nine;", ";" & LCase("one") & ";") \ 6
  3. '精用Boolean表达式,让代码再减肥
  4. x=1-(3>2)
  5. x=(a > b)
  6. '减少DoEvents语句的数量
  7. Declare Function GetInputState Lib "user32" Alias "GetInputState" () As Long
  8. If GetInputState() Then DoEvents
  9. 'XOR用来对两个表达式进行逻辑互斥或运算
  10. [result =] expression1 Xor expression2
  11. If (x Or y Or z) >= 0 Then ... 代替 If x >= 0 And y >= 0 And z >= 0 Then ...
  12. '统计shource中的空格数
  13. Dim b() as Byte, count As Integer
  14. b() = source$
  15. For i = 0 to UBound(b) Step LenB("A")
  16. If b(i) = 32 Then count = count + 1
  17. Next
  18. '无闪烁地快速附加字符串到textbox控件
  19. Text1.SelStart = Len(Text1.Text)
  20. Text1.SelText = newString
  21. '快速找到选中的OptionButton
  22. intSelected = -Option(1).Value - Option(2).Value * 2
  23. '增强的string()函数功能
  24. response.write Replace(Space(3), " ", "hello!<br>")
  25. '快速调入TreeView控件以及ListView控件的子项内容
  26. With TreeView1.Nodes
  27. For i = 1 To 5000
  28. .Add , , , "Node " & i
  29. Next
  30. End With
  31. '快速调入TreeView控件以及ListView控件的子项内容
  32. With TreeView1.Nodes
  33. For i = 1 To 5000
  34. .Add , , , "Node " & i
  35. Next
  36. End With
  37. 它的速度还能更快
复制代码
暂时完毕,后面有空了再来编辑排版了,先将就吧。

[ 本帖最后由 Moneky 于 2009-12-22 00:23 编辑 ]

评分

1

查看全部评分

TA的精华主题

TA的得分主题

发表于 2009-12-7 15:09 | 显示全部楼层
本帖最后由 ExcelHome 于 2012-10-6 16:40 编辑

期待精彩内容!!!


TA的精华主题

TA的得分主题

发表于 2009-12-7 15:15 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
期待楼主早点贴出分享。

TA的精华主题

TA的得分主题

发表于 2009-12-7 15:26 | 显示全部楼层

TA的精华主题

TA的得分主题

发表于 2009-12-7 15:47 | 显示全部楼层
您需要登录后才可以回帖 登录 | 免费注册

本版积分规则

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

GMT+8, 2024-11-18 21:31 , Processed in 0.040848 second(s), 8 queries , Gzip On, MemCache On.

Powered by Discuz! X3.4

© 1999-2023 Wooffice Inc.

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

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

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