盡可能少使用“.”,使用對象變量
在前面已經介紹過的對長對象引用使用對象變量以及使用With…End With等都是簡化”.”的方法。因為在代碼中的每個句點都表示至少一個(而且可能是多個)過程調用,而這些過程調用必須在后臺執行。真正好的做法是在局部進行緩存對象引用,例如,應該把對象模型中較高層次的對象引用保存到局部對象變量中,然后用這些對象引用創建其他較低層次的對象引用。例如,引用某單元格數據時,可用如下代碼:
Dim i As Long
For i=1 to 10
Workbooks("Book1.xls").Worksheets("Sheet1").Cells(1,i).Value=i
Next i
但下面的代碼運行效率更高,因為代碼中引用Workbook對象和Worksheet對象的調用命令只執行一次,而上面的代碼中卻要執行10次。
Dim ws As Worksheet
Dim i As Long
Set ws= Workbooks("Book1.xls").Worksheets("Sheet1")
For i=1 to 10
ws.Cells(1,i).Value=i
Next i
當您一遍又一遍的使用相同對象引用時,您可以將該對象引用設置成一個變量,然后使用該變量代替對象引用。這樣,您在代碼中只需對該對象變量進行引用即可。
例如,下面的示例在每行中調用Workbook對象的Sheets屬性、Range屬性和Value屬性三次,當您循環1000次時,總共要調用屬性6000次。
Sub DoThis1()
Dim Start As Double, Finish As Double
Start = Timer
'--------------------------------------
Dim N As Long
For N = 1 To 1000
Workbooks("Book1").Sheets(1).Range("c5").Value = 10
Workbooks("Book1").Sheets(1).Range("d10").Value = 12
Next
'--------------------------------------
Finish = Timer
MsgBox "本次運行的時間是" & Finish - Start
End Sub
您能在循環開始前通過設置Workbooks(“Book1”).Sheets(1)作為一個對象變量來優化上面的例子,下面的示例在每行僅調用一個Range屬性,當循環1000次時,總共只調用該屬性2000次。
注意,“Value”是一個缺省屬性,通常不需要明確指定它,它將被自動調用。因此,該屬性在下面的代碼中被忽略。然而,就養成良好的編程習慣而言,還是建議您最好寫明該屬性。
Sub DoThis2()
'快約35%以上
Dim Start As Double, Finish As Double
Start = Timer
'--------------------------------------
Dim ThisBookSheet As Object, N As Long
Set ThisBookSheet = Workbooks("Book1").Sheets(1)
For N = 1 To 1000
ThisBookSheet.Range("c5") = 10
ThisBookSheet.Range("d10") = 12
Next
'--------------------------------------
Finish = Timer
MsgBox "本次運行的時間是" & Finish - Start
End Sub
您可以比較這兩個示例的運行速度,它們都得到同樣的結果,但在我的機子上運行時,第二個示例比第一個快60%。當然,您還能使用With…End With語句獲得相同的結果。
您也能不設置明確的對象變量,而是使用With語句減少對象的重復引用。上面的示例也能使用下面的代碼,該代碼僅調用Workbooks屬性和Sheets屬性一次,當循環1000次時,總共調用1000次屬性。
Sub DoThis3()
'快約35%以上
Dim Start As Double, Finish As Double
Start = Timer
'--------------------------------------
Dim N As Long
With Workbooks("Book1").Sheets(1)
For N = 1 To 1000
.Range("c5") = 10
.Range("d10") = 12
Next
End With
'--------------------------------------
Finish = Timer
MsgBox "本次運行的時間是" & Finish - Start
End Sub
上述三個示例均得到相同的結果,但在我的機子上運行時,本示例比第一個示例快50%以上。
在一個語句中進行復制或者粘貼
在用宏錄制代碼時,首先是選擇一個區域,然后再執行ActiveSheet.Paste。在使用Copy方法時,可以在一個語句中指定復制的內容及要復制到的目的地。
例如,將B5:C6區域的內容復制到以單元格B8開始的區域中,使用宏錄制器的代碼為:
Range("B5:C6").Select
Selection.Copy
Range("B8").Select
ActiveSheet.Paste
經修改后的最佳代碼是:
Range("B5:C6").Copy Destination:=Range("B8")
合理地使用消息框和窗體
在一個很長的程序中,嘗試著將消息框或者窗體安排顯示在程序的最開始或最后面,避免干擾用戶。此外,盡管窗體提供了許多功能,但它們能夠導致文件大小迅速增加。還有就是盡量避免給工作表單元格鏈接用戶窗體控件,因為這樣將會導致鏈接更新操作,影響程序運行速度。
盡可能加速對數字的運算
(1)當對整數進行除法時,您可以使用整型除法運算符(/)而不是浮點除法運算符(/),因為無論參與除法運算的數值類型如何,浮點除法運算符總會返回Double類型的值。
(2)在任何具有整數值的算術表達式中使用Single或Double值時,整數均將被轉換成Single或Double值,最后的結果將是Single或Double值。如果要對作為算術運算結果的數字執行多次操作,可能需要明確地將該數字轉換為較小的數據類型。
提高字符串操作的性能
(1)盡可能少使用連接操作。可以在等號左邊使用Mid函數替換字符串中的字符,而不是將它們連接在一起。使用 Mid 函數的缺點是替換字符串必須與要替換的子字符串的長度相同。例如,
Dim strText As String
strText = "this is a test"
Mid(strText, 11, 4) = "tent"
(2)VBA提供許多可用來替換函數調用的內部字符串常量。例如,可以使用vbCrLf常量來表示字符串中的回車/換行組合,而不是使用Chr(13) & Chr(10)。
(3)字符串比較操作的執行速度很慢。有時,可以通過將字符串中的字符轉換為 ANSI 值來避免這些操作。例如,下列代碼會檢查字符串中的第一個字符是否為空格:
If Asc(strText) = 32 Then
上面的代碼會比以下代碼更快:
If Left(strText, 1) = " " Then
使用Asc()檢驗ANSI的值
在VBA中,可以使用Chr$()函數把數轉換成字符,并確定ANSI的值,但是更好的是使用Asc()函數把字符串轉換成數值,然后確定它的ANSI值。如果需要進行有限次數的這種檢驗,對程序代碼的效率可能不會產生很大影響,但是,如果需要在多個循環內進行這種檢驗時,這將節省處理時間并且有助于程序代碼更快地執行。
使用Len()檢驗空串
盡管有多種方法可檢驗空串,但首選的是使用Len()函數。為了測試零長度的串,可以選擇把串與””相比較,或者比較串的長度是否為0,但這些方法比用Len()函數要用更多的執行時間。當對字符串應用Len()函數并且函數返回0值時,說明該字符串是空的或者是零長度的字符串。
并且,因為在If語句內非零值被認為是True,所以直接使用Len()函數而不必與””或0比較,減少了處理時間,因此執行更快。
有效地使用數組
用VBA數組而不是單元格區域來處理數據,即可以先將數據寫入到某個數組,然后用一個語句就可以將數組中的數據傳遞到單元格區域中。(前文已述)
在創建已知元素的確定數組時,使用Array函數對于節約空間和時間以及寫出更具效率的代碼是非常理想的。例如,
http://www.49028c.com/
Dim Names As Variant
Names=Array("Fan","Yang","Wu","Shen")
此外,應該盡量使用固定大小的數組。如果確實選擇使用了動態數組,應該避免數組每增加一個元素就改變一次數組的大小,最好是每次增加一定數量的元素。
使用Excel的內置函數
對于要實現的某一功能,如果有Excel的內置函數能夠實現,那么就用Excel的內置函數,不需要另外自定義函數,因為自定義的函數總比Excel內置的函數慢。
考慮在VBA代碼中使用工作表函數
操作單元格區域的Excel工作表函數通常比完成同樣任務的VBA程序更快(但不能確??偸沁@樣,您可以對它們進行速度測試)
例如,在代碼中使用SUM工作表函數比用VBA代碼在單元格區域中循環并相加值要快得多,以此為例,下面的代碼運行速度相對較慢。
Sub AddItSlow()
Dim Start As Double, Finish As Double
Start = Timer
'--------------------------------------
'為了進行測試,我們循環5次
Dim N As Long
For N = 1 To 5
'***************************
Dim Cell As Range
For Each Cell In Worksheets(2).Range("A1:G200")
[a1] = [a1] + Cell.Value
Next Cell
'***************************
Next N
'--------------------------------------
Finish = Timer
MsgBox "本次運行的時間是" & Finish - Start
End Sub
下面的代碼實現相同的功能,但運行得更快(幾乎瞬間完成)。
Sub AddItFaster()
'快近600倍
Dim Start As Double, Finish As Double
Start = Timer
'--------------------------------------
'為了進行測試,我們循環5次
Dim N As Long
For N = 1 To 5
'***************************
[a1] = application.WorksheetFunction. _
Sum(Worksheets(2).Range("A1:G200"))
'***************************
Next
'--------------------------------------
Finish = Timer
MsgBox "本次運行的時間是" & Finish - Start
End Sub
產生統計結果的函數(例如PRODUCT、COUNT、COUNTA和COUNTIF)是代替運行速度更慢的VBA代碼的很好的選擇,并且,一些工作表函數(例如MATCH和LOOKUP)能夠將單元格區域作為參數。
不要認為工作表函數總是更快的
如下例所示,在VBA中沒有Max或Min函數,但Excel中有該函數。于是,您能編寫出如下代碼:
Sub MaxIt1()
Dim Start As Double, Finish As Double
Start = Timer
'--------------------------------------
'為了測試,我們循環10000次
Dim N As Long
For N = 1 To 10000
'***************************
[J1] = Application.Max([J2], [J3])
'***************************
Next N
'--------------------------------------
Finish = Timer
MsgBox "本次運行時間是" & Finish - Start
End Sub
或者,您能在VBA中使用下面的方式實現相同的功能:
Sub MaxIt2()
Dim Start As Double, Finish As Double
Start = Timer
'--------------------------------------
'為了測試,我們循環10000次
Dim N As Long
For N = 1 To 10000
'***************************
If [J2] >= [J3] Then [J1] = [J2] Else [J1] = [J3]
'***************************
Next N
'--------------------------------------
Finish = Timer
MsgBox "本次運行的時間是" & Finish - Start
End Sub
比較上面的兩個程序,可能認為使用工作表函數會更快,但事實上用VBA代碼可以獲得幾乎相同的速度。因此,在一些大的循環中,您可以對實現同樣功能的工作表函數的VBA代碼進行測試。一些內置的VBA函數事實上運行速度也是慢的,因此,在編寫代碼時,在不同方式之間進行速度測試總是值得的。
但是,在代碼中經常使用的簡單的函數,就直接編寫代碼,而不是使用WorksheetFunction對象。
使用Range.SpecialCells()來縮小需要處理的單元格數。
只要有可能就使用集合索引值
您能在集合中使用名稱或者數字來指定某個單一的對象,但使用對象的索引值通常是更快的。如果您使用對象的名字,VBA必須解析名字成為索引值;但如果您使用索引值,就能避免這個額外的步驟。
但另一方面,我們要注意到在集合中通過名稱指定對象有很多優點。使用對象名稱能使您的代碼更容易閱讀和調試。此外,通過名稱指定一個對象比通過索引值更安全,因為當您的代碼運行時該對象的索引值可能變化。
例如,某菜單的索引值表示它在菜單欄中的位置,但是如果在菜單欄中添加了菜單或者刪除了菜單,該菜單的索引值會變化。這樣,您就不應該考慮代碼的速度,而應保證代碼運行可靠。您使用索引值加快代碼速度之前,應該確保該索引值在代碼運行過程中或使用應用程序時不會改變。
使用完全受限制的對象引用
使用完全受限制的對象引用消除了引用模糊并確保變量有明確的類型。
一個完全受限制的對象引用包括了對象庫名稱,如下代碼所示:
Dim wb As Excel.Workbook
如果您使用通用的對象數據類型聲明變量和參數,在運行過程中VBA可能必須對它們的引用進行解析為(某對象的)屬性和方法,這將導致速度變慢。
一個通用對象數據類型示例如下:
Dim wb As Workbook
使用已有的VBA方法
也有一些特定目的的VBA方法,它們提供在單元格區域執行特定操作的一種簡單的方式。例如工作表函數,這些特定的方法比使用通常的VBA編碼完成相同的任務要更快。最常用的是”Replace”方法和”Find”方法。
Replace方法:
下面的示例用了一種相當慢的方式代碼改變單元格區域H1:H20000中每個單元格的值。
Sub NowDoThis1()
Dim Start As Double, Finish As Double
Start = Timer
'--------------------------------------
Dim Cell As Range
For Each Cell In Worksheets(1).Range("H1:H20000").Cells
If Cell.Value = 4 Then Cell.Value = 4.5
Next
'--------------------------------------
Finish = Timer
MsgBox "本次運行的時間是" & Finish - Start
End Sub
下面的示例使用Replace方法進行同樣的操作,但運行得更快。
Sub NowDoThis2()
'快約兩倍
Dim Start As Double, Finish As Double
Start = Timer
'--------------------------------------
Worksheets(1).Range("H1:H20000").Replace "4", "4.5"
'--------------------------------------
Finish = Timer
MsgBox "本次運行的時間是" & Finish - Start
End Sub
Find方法:
下面的代碼使用一種相對較慢的方法在單元格區域I1:I5000中值為4的單元格內添加一個藍色的橢圓。
Sub FindItSlow()
Dim Start As Double, Finish As Double
Start = Timer
'--------------------------------------
Dim Cell As Range
For Each Cell In Worksheets(1).Range("I1:I5000").Cells
If Cell.Value = 4 Then
With Worksheets(1).Ovals.Add(Cell.Left, _
Cell.Top, Cell.Width, _
Cell.Height)
.Interior.Pattern = xlNone
.Border.ColorIndex = 5
End With
End If
Next
'--------------------------------------
Finish = Timer
MsgBox "本次運行的時間是" & Finish - Start
End Sub
下面的示例使用了Find方法和FindNext方法執行相同的任務,但運行速度更快。
Sub FindItFaster()
'快約25倍
Dim Start As Double, Finish As Double
Start = Timer
'--------------------------------------
Dim Cell As Range, FirstAddress As String
With Worksheets(1).Range("I1:I5000")
Set Cell = .Find(4)
If Not Cell Is Nothing Then
FirstAddress = Cell.Address
Do
With Worksheets(1).Ovals.Add(Cell.Left, _
Cell.Top, Cell.Width, _
Cell.Height)
.Interior.Pattern = xlNone
.Border.ColorIndex = 5
End With
Set Cell = .FindNext(Cell)
Loop Until Cell Is Nothing Or Cell.Address = FirstAddress
End If
End With
'--------------------------------------
Finish = Timer
MsgBox "本次運行的時間是" & Finish - Start
End Sub
關于帶有特定目的的VBA方法的更多的信息,您可參見VBA幫助系統相關主題。
結語
當然,代碼優化可能不是絕對必要的,這依賴于您要做的工作……如果您正好編寫了一個快速且簡短的或者是一次性使用且與速度和/或簡潔要求無關的代碼,您就不需要優化代碼。
但另一方面,如果您處理一個帶有很多數據、工作簿、工作表等大的工程,再次檢查您第一次編寫好的代碼,看看是否您的代碼需要優化,而這樣做總是值得的。
最終,您將養成編寫代碼的好習慣,將會使您的代碼更簡潔、運行更快速、并且容易為您自已和他人閱讀和調試。同時,由于您的代碼簡潔,因而輸入更快,工作效率更高。
新聞熱點
疑難解答