簡述
所謂函式副作用是指,當調用函式時,被調用函式除了返回函式值之外,還對主調用函式產生附加的影響。例如,調用函式時在被調用函式內部:
·修改全局量的值;
·修改主調用函式中聲明的變數的值(一般通過指針參數實現)。
函式副作用會給程式設計帶來不必要的麻煩,給程式帶來十分難以查找的錯誤,並且降低程式的可讀性。例如,由於雙目運算的兩個運算分量的計算次序不同,而帶來運算結果不同,就是由函式副作用引起的。對函式副作用的看法與對GOTO語句的看法一樣,在程式設計語言界一直有分歧,有人主張保留,有人主張取消。探測認為,可以保留函式副作用,但是應該限制程式設計師儘量不要使用函式副作用。由於函式副作用的影響,會產生以下問題。
·會使雙目運算的結果依賴於兩個運算分量的計算次序;
·還可能使某些在數學上明顯成立的事實,在程式中就不一定成立。
表現
從語言的函式設計可以看到,函式能夠無條件訪問全局數據(包括全局變數、全局數組、全局靜態數據),通過指針或引用參數訪問主調函式的棧數據或者屬於程式整體的堆數據,甚至系統全局堆數據。函式副作用正是通過函式訪問這些數據所表現出來的。
1、良性副作用
通過指針或引用參數設計的函式,都具有訪問外部數據的傾向。或許可以利用某個函式對外部數據的修改,簡化或免於後續計算的工作。例如:
設計該getStr函式時,以獲取C串為目標,但需要在循環輸入字元中使用下標計數變數。將下標變數len有意設計成全局數據後,該變數正好可以用於之後的字串處理。
如果沒有這個副作用的沒計,後續輸出逆反字串的工作,需要再次計算字串的長度。
當然,為了保證反覆調用的正確性,在開始輸入字元時應預先初始化變數len。因為訪問了全局變數Ien,所以,函式getStr只能局限於本程式。在設計時,需要注意其他函式是否有修改變數len的行為,以保證len值的時效性。
2、惡性副作用
記憶體泄漏是一種典型的函式惡性副作用。例如,輸入n(參數)個整數,計算其中有幾個逆序的函式:
該函式對於整數參數n,申請了堆空間來保存輸入數據,並進行逆序計算。這本身無可非議,但因為在函式運行結束時沒有返還堆記憶體,就使得運行環境發生了改變。
也許對緊接著的函式調用和數據訪問沒有明顯的影響,但是由於占用了堆記憶體,減少了總的可用堆空間存量。如果有許多該函式的計算,隨著一次次的調用,就會影響程式的正常運行,甚至影響整個計算機的正常運行。
預防
函式在運行期間,訪問全局變數、全局數組、非靜態數據、堆數據(通過指針和引用)、主調函式數據(通過指針或引用)都有可能產生一定程度的副作用。
有副作用的函式,其影響不一定會在直接的主調函式中反映出來,可能在多層調用中反映出來,或者遞歸結構,或者異常機制的處理結構等。
設計有副作用的函式,都是有目的的。
例如,通過指針和引用參數,主動讓被調函式獲得外部數據訪問權,以便提高數據處理的性能。
例如,通過開闢全局數據,使得函式聲明簡單。
例如,通過返回指針或引用的做法,獲得共享堆空間,以簡化計算工作。
要利用函式副作用,首先應該明白副作用產生的原因,從而避開惡性副作用的產生。
函式副作用設計,還與程式設計方法有密切的關係。因為計算總也離不開數據,為了設計無副作用的函式,在對象化編程中,將相關數據儘量封裝在獨立的數據類型中,在該數據類型中去從事專門的數據操作。這樣就避免了為了計算而讓數據公開化、共享化,陷於不得不設計有副作用的函式的不利境地。