天下武功,唯快不破!
在這個時間就是金錢、效率就是生命的年代,浪費時間就是罪惡,尤其是對于程序猿來說,如果自己開發(fā)的程序,雖然功能實現(xiàn)了,但是在執(zhí)行上還存在卡、慢的現(xiàn)象,那么不失為一件頗讓人沮喪的事情。
大家都知道在Office里面可以用VBA進(jìn)行二次開發(fā),為日常工作提供了一條快捷的途徑,善用它,可以大大的改善我們的工作體驗和質(zhì)量。今天跟大家分享的不是VBA本身,而是一些VBA代碼常用的優(yōu)化技巧,大神請繞路(也歡迎指點),VBA初學(xué)者或者愛好者可以看看,哪怕是得到一點點啟發(fā)和借鑒,那也是極好的。
1.盡量調(diào)用內(nèi)置功能,即,使用系統(tǒng)提供的屬性、方法和函數(shù)
很多時候我們要實現(xiàn)某些功能,如果本身對excel不熟悉的話,可能會想辦法去實現(xiàn)某些看上去很復(fù)雜的功能,殊不知,其實excel本身已經(jīng)提供了類似的功能,有的時候可能僅僅是一個函數(shù)就解決了的事情,結(jié)果你搞半天,說不定辛苦弄出來,結(jié)果效率和效果還沒內(nèi)置的好。
在很大程度上,這簡直是一定的,試想,你用的微軟的軟件,你編出來的東西能比別人開發(fā)人員還NB(不乏天才,所以我有所保留)?所以碰到這種情況還是省省心吧,能調(diào)用內(nèi)置的盡量調(diào)用內(nèi)置。
也有例外,如:
sub 獲取數(shù)組最大值() 'VBA法
dim arr(),temp as byte,i,j,tim as long
for j=1 to 100000 '循環(huán)100000次,從而可以更好的進(jìn)行時間比較
arr=array(1,7,8,6,9,3,5,7,6,8,9,4,1,2,4) '數(shù)組的值
temp=arra(1) '將數(shù)組中的第一個值賦予變量temp 'temp=worksheetfunction.max(arr) 調(diào)用max函數(shù),效率反而偏低
for i=1 to UBound(arr) '循環(huán)比較數(shù)組中所有元素
if arra(i)>temp then temp=arr(i) '如果數(shù)組中某元素大于變量,即將該值賦予變量temp
next i
next j
end sub
先前在幫助一些學(xué)生朋友解決VBA難題的時候,碰到過別人計算機老師出題,為了偷懶,直接挑幾個工作表函數(shù),讓學(xué)生拿代碼給編出來,這是典型的放著內(nèi)置的不用而自己編的案例,當(dāng)然老師的出發(fā)點可能是訓(xùn)練學(xué)生的邏輯思維和算法理解能力。
2.盡量減少使用對象引用
在循環(huán)中盡量減少對對象的引用,多用with... end with語句,圓點越少越省時
用set語句將反復(fù)引用的對象設(shè)置為對象變量,因為變量存在內(nèi)存中
利用對象循環(huán)代替單元格循環(huán),如判斷單元格批注,可以循環(huán)批注comments對象,直接找到每個批注,再利用parent獲取該批注的父對象單元格即可。
sub批注循環(huán)()
dim com as comment,address asstring
for each com in activeSheet.comments
address=address&com.parent.address(0,0)&chr(10)
next
msgbox address
endsub
sub 單元格循環(huán)()
dim rng as range,address as string,bl as boolean
on error resume next
for each rng in activesheet.usedrange
bl=rng.comment.visible
if err=0 then address=address&rng.address(0,0)&chr(10)
err.clear:
next rng
msgbox address
end sub
3.減少對象的激活和選擇
少用select和activate語句
這個通常會出現(xiàn)在我們自己錄制宏代碼的時候,excel記錄了大量的點擊和激活動作,而這些都不是必須的,大多數(shù)都可以省略掉(有些操作必須用到激活或者選擇的除外)
4.關(guān)閉屏幕更新
就是程序運行的時候會看到屏幕在閃爍,這個會影響計算機的性能,也會閃瞎眼,所以基本上編程的人都會默認(rèn)使用這一條(尤其是對于運行時間比較長的,短的可以視情況不加)
Application.ScreenUpdating=False '通常放在循環(huán)語句前...Application.ScreenUpdating=True
5.變量的使用
強制變量聲明(OptionExplicit):在設(shè)置里面勾選,會節(jié)省時間,自動帶出,而不是自己手動敲出來的
盡量顯式聲明變量:事前連接--early binding;定義object類型的屬于事后連接,late binding。通常事前定義會節(jié)省時間,但往往會有兼容性問題,后定義的適配性比較好
選擇合適的變量類型:缺省會默認(rèn)Variant型,但是會增加內(nèi)存耗用,能明確的就具體指明
善用變量:對于反復(fù)出現(xiàn)的數(shù)值或者字符串,盡量聲明一個常量來取代該值,直接調(diào)用,修改的時候也能做到一改都改,而不用改動多個地方
講到這里,想強調(diào)一下,有時候為了效率,有的人喜歡在代碼編寫的時候采用一些簡寫或者隱式聲明,這個確實會讓我們少敲不少字,但是帶來的另外一個問題是,一旦代碼出現(xiàn)了問題,調(diào)試或者找錯誤的時候就會很抓狂了,可能從整體的時間效率上反而不劃算,所以還是建議大家養(yǎng)成良好的編程習(xí)慣,變量都寫清楚,勤備注,這樣易讀性和可維護(hù)性好
6.善用帶$的字符串處理函數(shù)
VBA中有2套字符串處理函數(shù),帶$和不帶$的。
如果不帶$的的函數(shù)處理字符串,則VBA將字符串作為variant數(shù)據(jù)計算,用帶$的,則當(dāng)string處理。而前者需要耗費更多的內(nèi)存
7.善用循環(huán)中的步長減少循環(huán)次數(shù)
如判斷奇偶,有了步長,就可以省去判斷語句,加快速度
講到循環(huán),還有一個類似的例子:
就是我們在遍歷某個區(qū)域的時候,如果能用目標(biāo)區(qū)域(Target)與已用區(qū)域(UsedRange)的交集(Intersect)區(qū)域來循環(huán)的話,會縮小循環(huán)的范圍,規(guī)避掉一些空的單元格的比對
8.利用數(shù)組代替單元格對象
將中間過程存在數(shù)組中,直接從內(nèi)存調(diào)用,最后再讀取
這個應(yīng)該是應(yīng)用的最多的,效果是最明顯的,少了單元格的交互,會省不少事。
當(dāng)然字典作為特殊的數(shù)組存在形式,一樣的也能來做這個事情
實例:
對幾千個個學(xué)生中不及格的成績標(biāo)示“不及格”
sub 對小于60分成績進(jìn)行注釋()
Dim i as integer,tim as long,arr1(),arr2()'將成績賦予數(shù)組變量arr1=range([b2],cells(rows.count,2).end(xlup))
'重置第二個數(shù)組變量大小
ReDim arr2(1 to UBound(arr1),1 to 1)
'循環(huán)數(shù)組for i=1 to UBound(arr1)
if arr1(i,1)<60 then arr2(i,1)="不及格"
next i
'將第二個數(shù)組的值賦予單元格
range([c2],cells(rows.count,2).end(xlup).offset(0,1))=arr2
end sub
9.重復(fù)調(diào)用UDF時才使用它
有點繞啊,UDF,即user-defined-function,即用戶自定義函數(shù)
為了程序運行的高效,我們通常會把長代碼拆成幾個子代碼或者自定義函數(shù),來相互調(diào)用。達(dá)到方便調(diào)試和互相引用的目的。UDF適合多次調(diào)用時,否則調(diào)用的速度甚至比執(zhí)行的速度更慢。
比較短小的和僅使用一次的function,建議直接內(nèi)置在sub代碼中而不是調(diào)用UDF。
10.將不改變值或者屬性的語句放到循環(huán)語句外
這一點往往會被人忽略,可能循環(huán)的重要語句本身就一兩句,其他無關(guān)語句很多的話,在循環(huán)的時候會反復(fù)check,這個過程會耗時
11.利用長度計算判斷單元格是否為空
第一次看到羅剛軍老師用這個的時候百思不得姐,上論壇問了很多人也不知道。后來在實踐中,逐漸發(fā)現(xiàn)這個效率更高(很隱晦,比較難得找合適的例子,老師能發(fā)現(xiàn)這個確實是下了功夫的),而且在很多時候用len方法還能解決別的一些比較棘手的問題,比如判斷非空單元格的時候或者某些對象的時候,我們有時候搞不清楚返回的值的屬性,
xx=""
xx is empty
xx is nothing
都不如一句len(xx)=0來得簡便
range("a1")="" '建議len法,此法效率更高len(range("a1"))=0 '.value省略,range的默認(rèn)屬性
好了說了這么多,想必大家都躍躍欲試了,那么怎么知道自己優(yōu)化結(jié)果是積極的呢?我們可以測試啊,下面附上測試代碼:
程序運行時間測試代碼:
sub aaa()
dim tim as long
tim=timer '獲取當(dāng)前時間
for ...
next
msgbox format(timer-tim,"0.00")&"秒" '執(zhí)行報告時間
end sub
通常有2種方法timer函數(shù)和time函數(shù),網(wǎng)上很多人問有啥區(qū)別,搞不清楚,這里簡單解釋一下(理解有誤的話,望高手不吝賜教):
時間很短時,可以用timer函數(shù),返回秒,即相對于當(dāng)天相對午夜0點時候經(jīng)過了多少秒(感覺時間長了,統(tǒng)計不是蠻準(zhǔn),自己編程體會)
時間比較長時,可以用time,time是按照時間如22:58:30這樣記錄的,即運行前后電腦時間相減