方法¶ ↑
方法實作程式碼的功能。以下是簡單的方法定義
def one_plus_one 1 + 1 end
方法定義包含 def
關鍵字、方法名稱、方法主體、return
值和 end
關鍵字。呼叫方法時,將執行方法主體。此方法傳回 2
。
自 Ruby 3.0 起,也有一個簡寫語法,適用於僅包含一個表達式的方法
def one_plus_one = 1 + 1
此部分僅涵蓋定義方法。另請參閱 呼叫方法的語法文件。
Method
名稱¶ ↑
Method
名稱可以是其中一個運算子,或必須從字母或第八位元組設定為 1 的字元開始。它可以包含字母、數字、_
(底線或下底線)或第八位元組設定為 1 的字元。慣例是使用底線來分隔多字方法名稱中的字詞
def method_name puts "use underscores to separate words" end
Ruby 程式必須使用與 US-ASCII 相容的字元集撰寫,例如 UTF-8、ISO-8859-1 等。在這些字元集中,如果設定第八個位元,表示一個延伸字元。Ruby 允許方法名稱和其他識別碼包含這些字元。Ruby 程式不能包含某些字元,例如 ASCII NUL (\x00
)。
以下是有效 Ruby 方法的範例
def hello "hello" end def こんにちは puts "means hello in Japanese" end
通常方法名稱與 US-ASCII 相容,因為鍵盤上都有按鍵可以輸入這些字元。
Method
名稱可以用 !
(驚嘆號)、?
(問號) 或 =
(等號) 結尾。
驚嘆號方法 (方法名稱結尾的 !
) 的呼叫和執行方式與任何其他方法相同。然而,根據慣例,帶有驚嘆號的方法被視為危險的。在 Ruby 的核心函式庫中,危險方法表示方法以驚嘆號 (!
) 結尾,表示它與非驚嘆號等效方法不同,會永久修改其接收器。Ruby 核心函式庫幾乎都會有每個驚嘆號方法 (方法名稱以 !
結尾) 的非驚嘆號對應方法 (方法名稱不以 !
結尾),不會修改接收器。這個慣例通常適用於 Ruby 核心函式庫,但其他 Ruby 函式庫可能適用或不適用。
根據慣例,以問號結尾的方法會傳回布林值,但它們可能不總是只傳回 true
或 false
。它們通常會傳回一個物件來表示真值 (或「真值」)。
以等號結尾的方法表示賦值方法。
class C def attr @attr end def attr=(val) @attr = val end end c = C.new c.attr #=> nil c.attr = 10 # calls "attr=(10)" c.attr #=> 10
賦值方法無法使用簡寫語法定義。
以下是各種 Ruby 算子的方法名稱。每個算子只接受一個引數。算子後方是算子的典型用途或名稱。為算子建立替代意義可能會造成混淆,因為使用者預期加號用來加總、減號用來減法等。此外,您無法變更算子的優先順序。
+
-
加法
-
-
減法
*
-
乘法
**
-
次方
/
-
除法
%
-
模數除法,
String#%
&
-
AND
^
-
XOR (異或)
>>
-
右移
<<
-
左移,附加
==
-
相等
!=
-
不相等
===
-
大小寫相等。請參閱
Object#===
=~
-
樣式比對。(不只適用於正規表示式)
!~
-
不符合
<=>
-
比較又稱為太空船運算子。請參閱
Comparable
<
-
小於
<=
-
小於或等於
>
-
大於
>=
-
大於或等於
如需定義一元方法減號和加號,請在運算子後加上 @
,例如 +@
class C def -@ puts "you inverted this object" end end obj = C.new -obj # prints "you inverted this object"
需要 @
來區分一元減號和加號運算子與二元減號和加號運算子。
您也可以在波浪號和非 (!
) 一元方法後加上 @
,但這不是必要的,因為沒有二元波浪號和非運算子。
一元方法接受零個引數。
此外,可以定義元素參考和指派的函式:分別為 []
和 []=
。兩者都可以接受一個或多個引數,而元素參考可以不接受任何引數。
class C def [](a, b) puts a + b end def []=(a, b, c) puts a * b + c end end obj = C.new obj[2, 3] # prints "5" obj[2, 3] = 4 # prints "10"
傳回值¶ ↑
預設情況下,函式會傳回在函式主體中評估的最後一個表達式。在上述範例中,評估的最後一個 (也是唯一一個) 表達式是簡單的總和 1 + 1
。return
關鍵字可用於明確表示函式傳回值。
def one_plus_one return 1 + 1 end
它也可以用於在評估最後一個表達式之前讓函式傳回。
def two_plus_two return 2 + 2 1 + 1 # this expression is never evaluated end
請注意,對於指派函式,在使用指派語法時,傳回值將被忽略。相反地,將傳回引數
def a=(value) return 1 + value end p(self.a = 5) # prints 5
直接呼叫函式時,將傳回實際傳回值
p send(:a=, 5) # prints 6
範圍¶ ↑
定義函式的標準語法
def my_method # ... end
將函式新增到類別中。您可以使用 class
關鍵字在特定類別上定義執行個體函式
class C def my_method # ... end end
可以在另一個物件上定義函式。您可以定義「類別函式」(在類別上定義的函式,而不是類別的執行個體) 如下所示
class C def self.my_method # ... end end
但是,這只不過是 Ruby 中更強大語法能力的一種特殊情況,也就是新增函式到任何物件的能力。類別是物件,因此新增類別函式只是新增函式到 Class
物件。
將函式新增到物件的語法如下
greeting = "Hello" def greeting.broaden self + ", world!" end greeting.broaden # returns "Hello, world!"
self
是指編譯器正在考慮的目前物件的關鍵字,這可能會讓在定義類別函式時使用 self
變得更清楚一些。的確,將 hello
函式新增到類別 String
的範例可以改寫成這樣
def String.hello "Hello, world!" end
像這樣定義的函式稱為「單例函式」。broaden
只會存在於字串執行個體 greeting
中。其他字串不會有 broaden
。
覆寫¶ ↑
當 Ruby 遇到 def
關鍵字時,它不會將其視為錯誤,如果該方法已存在:它只是重新定義它。這稱為覆寫。這就像擴充核心類別,這是一個潛在的危險能力,並且應該謹慎使用,因為它可能會導致意外的結果。例如,考慮這個 irb 會話
>> "43".to_i => 43 >> class String >> def to_i >> 42 >> end >> end => nil >> "43".to_i => 42
這將有效地破壞任何使用 String#to_i
方法從字串中解析數字的程式碼。
引數¶ ↑
方法可以接受引數。引數清單在方法名稱之後
def add_one(value) value + 1 end
當呼叫時,add_one
方法的使用者必須提供引數。引數是方法主體中的局部變數。然後,該方法將新增一個到此引數並傳回該值。如果給定 1
,此方法將傳回 2
。
引數周圍的括號是可選的
def add_one value value + 1 end
在速記方法定義中,括號是強制性的
# OK def add_one(value) = value + 1 # SyntaxError def add_one value = value + 1
多個引數以逗號分隔
def add_values(a, b) a + b end
當呼叫時,必須按照確切的順序提供引數。換句話說,引數是位置性的。
預設值¶ ↑
引數可以有預設值
def add_values(a, b = 1) a + b end
預設值不需要首先出現,但具有預設值的引數必須分組在一起。這是可以的
def add_values(a = 1, b = 2, c) a + b + c end
這將引發 SyntaxError
def add_values(a = 1, b, c = 1) a + b + c end
預設引數值可以參照已評估為局部變數的引數,並且引數值總是從左到右評估。所以這是允許的
def add_values(a = 1, b = a) a + b end add_values # => 2
但這將引發 NameError
(除非定義了名為 b
的方法)
def add_values(a = b, b = 1) a + b end add_values # NameError (undefined local variable or method `b' for main:Object)
Array
分解¶ ↑
您可以使用引數中的額外括號分解(解壓縮或從中提取值)Array
def my_method((a, b)) p a: a, b: b end my_method([1, 2])
這會列印
{:a=>1, :b=>2}
如果引數在 Array
中有額外的元素,它們將被忽略
def my_method((a, b)) p a: a, b: b end my_method([1, 2, 3])
這與上述輸出相同。
您可以使用 *
來收集其餘引數。這將 Array
拆分為第一個元素和其餘部分
def my_method((a, *b)) p a: a, b: b end my_method([1, 2, 3])
這會列印
{:a=>1, :b=>[2, 3]}
如果引數回應 to_ary,則將分解引數。您只應在可以將物件用於 Array
的位置時定義 to_ary。
使用內部括號只使用一個已傳送的引數。如果引數不是 Array
,它將被指定給分解中的第一個引數,並且分解中的其餘引數將為 nil
def my_method(a, (b, c), d) p a: a, b: b, c: c, d: d end my_method(1, 2, 3)
這會列印
{:a=>1, :b=>2, :c=>nil, :d=>3}
您可以任意巢狀分解
def my_method(((a, b), c)) # ... end
陣列/雜湊引數¶ ↑
在引數前加上*
會將任何剩餘的引數轉換為陣列
def gather_arguments(*arguments) p arguments end gather_arguments 1, 2, 3 # prints [1, 2, 3]
陣列引數必須出現在任何關鍵字引數之前。
可以收集開頭或中間的引數
def gather_arguments(first_arg, *middle_arguments, last_arg) p middle_arguments end gather_arguments 1, 2, 3, 4 # prints [2, 3]
如果呼叫者在所有位置引數後提供關鍵字,陣列引數會擷取雜湊
作為最後一個項目。
def gather_arguments(*arguments) p arguments end gather_arguments 1, a: 2 # prints [1, {:a=>2}]
不過,這只會在方法未宣告任何關鍵字引數時發生。
def gather_arguments_keyword(*positional, keyword: nil) p positional: positional, keyword: keyword end gather_arguments_keyword 1, 2, three: 3 #=> raises: unknown keyword: three (ArgumentError)
另外,請注意,可以將單獨的*
用來忽略引數
def ignore_arguments(*) end
呼叫方法時,也可以使用單獨的*
將引數直接傳遞給另一個方法
def delegate_arguments(*) other_method(*) end
關鍵字引數¶ ↑
關鍵字引數類似於具有預設值的定位引數
def add_values(first: 1, second: 2) first + second end
將接受任意關鍵字引數,並使用**
def gather_arguments(first: nil, **rest) p first, rest end gather_arguments first: 1, second: 2, third: 3 # prints 1 then {:second=>2, :third=>3}
使用關鍵字引數呼叫方法時,引數可以出現在任何順序。如果呼叫者傳送未知的關鍵字引數,而方法不接受任意關鍵字引數,則會引發ArgumentError
。
若要要求特定的關鍵字引數,請勿包含關鍵字引數的預設值
def add_values(first:, second:) first + second end add_values # ArgumentError (missing keywords: first, second) add_values(first: 1, second: 2) # => 3
混合關鍵字引數和定位引數時,所有定位引數都必須出現在任何關鍵字引數之前。
另外,請注意,可以將**
用來忽略關鍵字引數
def ignore_keywords(**) end
呼叫方法時,也可以使用**
將關鍵字引數委派給另一個方法
def delegate_keywords(**) other_method(**) end
若要標記方法為接受關鍵字,但實際上不接受關鍵字,可以使用**nil
def no_keywords(**nil) end
使用關鍵字或非空關鍵字展開呼叫此類方法會導致ArgumentError
。支援此語法,以便稍後可以將關鍵字新增到方法,而不會影響向後相容性。
如果方法定義不接受任何關鍵字,且未使用**nil
語法,則呼叫方法時提供的任何關鍵字都會轉換為雜湊
定位引數
def meth(arg) arg end meth(a: 1) # => {:a=>1}
區塊引數¶ ↑
區塊引數由&
表示,且必須放在最後
def my_method(&my_block) my_block.call(self) end
最常使用區塊引數將區塊傳遞給另一個方法
def each_item(&block) @items.each(&block) end
如果你只是將區塊傳遞給另一個方法,則不需要為區塊命名
def each_item(&) @items.each(&) end
如果你只會呼叫區塊,而不會以其他方式操作它或將它傳送至其他方法,則建議使用沒有明確區塊參數的 yield
。此方法等同於本節中的第一個方法
def my_method yield self end
引數轉送¶ ↑
自 Ruby 2.7 起,可以使用全引數轉送語法
def concrete_method(*positional_args, **keyword_args, &block) [positional_args, keyword_args, block] end def forwarding_method(...) concrete_method(...) end forwarding_method(1, b: 2) { puts 3 } #=> [[1], {:b=>2}, #<Proc:...skip...>]
僅在以 ...
定義的方法中,才能使用轉送 ...
呼叫。
def regular_method(arg, **kwarg) concrete_method(...) # Syntax error end
自 Ruby 3.0 起,定義和呼叫中 ...
之前可以有前導引數(但在定義中,它們只能是沒有預設值的定位引數)。
def request(method, path, **headers) puts "#{method.upcase} #{path} #{headers}" end def get(...) request(:GET, ...) # leading argument in invoking end get('http://ruby-lang.org', 'Accept' => 'text/html') # Prints: GET http://ruby-lang.org {"Accept"=>"text/html"} def logged_get(msg, ...) # leading argument in definition puts "Invoking #get: #{msg}" get(...) end logged_get('Ruby site', 'http://ruby-lang.org') # Prints: # Invoking #get: Ruby site # GET http://ruby-lang.org {}
請注意,在轉送呼叫中省略括號可能會導致意外結果
def log(...) puts ... # This would be treated as `puts()...', # i.e. endless range from puts result end log("test") # Prints: warning: ... at EOL, should be parenthesized? # ...and then empty line
Exception
處理¶ ↑
方法有一個隱含的例外處理區塊,因此你不需要使用 begin
或 end
來處理例外。這
def my_method begin # code that may raise an exception rescue # handle exception end end
可以寫成
def my_method # code that may raise an exception rescue # handle exception end
類似地,如果你希望即使引發例外也能總是執行程式碼,你可以使用 ensure
而不用 begin
和 end
def my_method # code that may raise an exception ensure # code that runs even if previous code raised an exception end
你也可以將 rescue
與 ensure
和/或 else
結合,而不用 begin
和 end
def my_method # code that may raise an exception rescue # handle exception else # only run if no exception raised above ensure # code that runs even if previous code raised an exception end
如果你希望僅對方法的一部分進行例外救援,請使用 begin
和 end
。有關更多詳細資訊,請參閱 例外處理 頁面。