程式扎記: [ Ruby Gossip ] Basic : 方法 - def 定義方法

標籤

2014年10月22日 星期三

[ Ruby Gossip ] Basic : 方法 - def 定義方法

Source From Here 
Preface 
在 Ruby 中要定義方法,是使用 def 來定義,例如,以下是個求最大公因數的方法定義: 
  1. def gcd(m, n)  
  2.     n == 0 ? m : gcd(n, m % n)  
  3. end  
  4. puts gcd(2030) # 顯示 10  
在上例中,gcd 是函式名稱,m 與 n 為參數(Parameter)名稱,如果要傳回值可使用 return,如果沒有指定 return,則以最後一個陳述句執行結果的傳回值,作為函式的傳回值。 

def 定義方法 
在某些語言中,這稱之為定義函式,但在Ruby中def確實是定義物件上的方法,只不過上例中沒有指明方法由誰擁有,呼叫gcd時也沒有指定訊息接收者。你也可以明確指定方法由哪個物件擁有,呼叫時也可以指定訊息接收者。例如: 
  1. obj = Object.new  
  2. def obj.gcd(m, n)  
  3.     n == 0 ? m : gcd(n, m % n)  
  4. end  
  5. puts obj.gcd(2030) # 顯示 10  
上例中,為 obj 定義了 gcd 單例方法(Singleton method)。如果在頂層環境中定義方法時,沒有指定方法由哪個物件擁有,則方法是由 Object 擁有的私有private實例方法Instance method)。例如上面第一個範例的 gcd 方法,相當於以下寫法: 
  1. class Object  
  2.     # 以下定義了gcd實例方法  
  3.     def gcd(m, n)  
  4.         n == 0 ? m : gcd(n, m % n)  
  5.     end  
  6.     private :gcd     
  7. end  
定義在頂層的方法沒有指定擁有者時,就是 Object 擁有的私有實例方法,Object 為所有類別的父類別,因此任何類別或模組定義中,就可以直接呼叫。 

之後還會談到,呼叫方法時沒有指定訊息接收者,預設以 self 為訊息接收者,如果是呼叫私有方法,不用也不能撰寫 self除了一個特例,之後會談到),因為私有方法只能在物件內部使用,不可透過「物件.訊息」的方式呼叫。例如: 
 

以上是 def 定義方法時的細節,實際上初學者,將沒有指定擁有者的方法當作函式來看待,會是比較容易理解的方式。 

在 Ruby 中不支援其它語言重載方法的概念例如Java,也就是在 Ruby 中同一個名稱空間中,不能有相同的方法名稱如果你定義了兩個方法具有相同的名稱但擁有不同的參數個數,則後者定義會覆蓋前者定義。例如: 
 

由於 Ruby 是動態語言,只需在設計時確認傳入方法的物件所擁有的特性或方法,無需採方法重載中,依型態不同來區別所呼叫方法的部份,至於依參數個數不同來區別的方法重載概念,在 Ruby 中可以使用預設引數(Argument)來解決。例如: 
  1. # encoding: Big5  
  2. def sum(a, b, c = 0)  
  3.     a + b + c  
  4. end  
  5.   
  6. puts sum(102030)  # 顯示 60  
  7. puts sum(1020)      # 顯示 30  
像 sum 這種加總數字的需求,事先可能不知道要傳入的引數個數,可以在定義方法的參數時使用 *,表示該參數接受不定長度引數。例如: 
  1. def sum(*numbers)  
  2.     total = 0  
  3.     numbers.each do |number|  
  4.         total += number  
  5.     end  
  6.     total  
  7. end  
  8.   
  9. puts sum(12)       # 顯示 3  
  10. puts sum(123)    # 顯示 6  
  11. puts sum(1234) # 顯示 10  
你傳入方法的引數,會被收集在一個陣列中,再設定給 numbers 參數。在 陣列型態 中提過,* 可以用來拆解陣列,將元素逐一指定給數個變數,這個語法也適用在方法的參數指定,你可以將一個陣列傳入,只要在傳入時加上 *,則陣列中每個元素會自動指定給各個參數。例如: 
  1. def sum(a, b, c)  
  2.     a + b + c  
  3. end  
  4.   
  5. numbers = [123]  
  6. puts sum(*numbers)   # 顯示 6  
如果方法中傳回陣列,也可以如此指定。例如: 
  1. def some  
  2.     [123]  
  3. end  
  4. x, y, z = some  
  5. puts "#{x}, #{y}, #{z}"   # 顯示 123  
方法中的參數若沒有預設引數,或使用 * 設定接受不定長度引數,則呼叫方法時該參數不一定要接收引數。如果方法中的參數混用必要引數與非必要引數,則引數一律優先滿足必要引數的參數
 

參數中必要引數的部份一徑優先分派引數,所以在 sum(1, 2, 3) 時,a、c、d 為必要引數,所以被指定了1、2、3,因為沒有引數了,所以 b 是空陣列。類似地: 
>> def some(a, b = 10, c, d)
>> p a, b, c, d
>> end
=> nil
>> some(1, 2, 3, 4)
1
2
3
4

=> [1, 2, 3, 4]
>> some(1, 2, 3)
1
10
2
3

=> [1, 10, 2, 3]

因為 a、c、d 為必要引數,所以 some(1, 2, 3) 時,被指定了 1、2、3,因為沒有引數了,所以 b 會採預設值。如果混用預設引數與接收不定長度引數的參數,則在分配完必要引數之後,接下來再分配預設引數,剩下的才給接受不定長度引數的參數。例如: 
 
接收不定長度引數的參數,必須在預設引數的右邊,否則會發生錯誤。 

如果方法的最後一個參數接受雜湊物件,例如: 
 

則{}可以省略,例如 
 
這樣的方法呼叫方式,讓程式原始碼更像是個組態檔案! 

則定義方法時,最後一個參數可以使用 * 設定為不定長度引數,如此兩種呼叫方式都可以支援: 
 

在 Ruby 中,方法中還可以定義方法,可以使用區域方法將某個函式中的演算組織為更小的單元,例如,在 選擇排序 的實作時,每次會從未排序部份選擇一個最小值放置到已排序部份之後,在底下的範例中,尋找最小值的演算就實作為區域方法的方式: 
  1. def selection(number)  
  2.     # Search index with minimum value among nums[m..-1]  
  3.     def min(nums, m, j)  
  4.         if j == nums.length  
  5.             m  
  6.         elsif nums[j] < nums[m]  
  7.             min(nums, j, j+1)  
  8.         else  
  9.             min(nums, m, j+1)  
  10.         end  
  11.     end  
  12.   
  13.     for i in 0..(number.length - 1)  
  14.         m = min(number, i, i+1)  
  15.         if m != i  
  16.             number[i], number[m] = number[m], number[i]  
  17.         end  
  18.     end  
  19. end  
  20.   
  21. number = [152397]  
  22. printf("Before sort: %s\n", number)  
  23. selection(number)  
  24. printf("After sort: %s\n", number)  
不過在Ruby中,區域方法不可以直接存取包裹它的外部方法之參數(或宣告在區域方法前的區域變數)。關於變數範圍,之後還會細部討論。 

Supplement 
[ DS with Java ] Section 4.1 : Selection Sort

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!