跳至内容

方法

为了避免重复相同的消息,我们可以定义一个方法并多次调用它,而不是使用变量。

方法定义由关键字 def 后跟方法名表示。从关键字 end 开始到结束的所有表达式都是方法体的一部分。

def say_hello
  puts "Hello Penny!"
end

say_hello
say_hello
say_hello() # syntactically equivalent method call with parentheses

提示

方法调用明确地由名称后面的括号表示,但可以省略。只有在需要消除歧义时才需要,例如,如果 say_hello 也是一个局部变量。

参数

如果我们想问候不同的人,但都是以相同的方式?我们可以定义一个方法,通过参数允许自定义,而不是编写单独的消息。参数就像方法体内部的局部变量。参数在方法名后面的括号中声明。调用方法时,可以传入作为方法参数值映射的参数。

def say_hello(recipient)
  puts "Hello #{recipient}!"
end

say_hello "World"
say_hello "Crystal"

提示

方法调用中的参数通常放在括号中,但通常可以省略。say_hello "World"say_hello("World") 在语法上是等价的。

一般建议使用括号,因为它可以避免歧义。但是,如果表达式读起来像自然语言,它们经常会被省略。

默认参数

可以为参数分配默认值。如果方法调用中缺少参数,则使用它。通常,参数是必需的,但当存在默认值时,可以省略它。

def say_hello(recipient = "World")
  puts "Hello #{recipient}!"
end

say_hello
say_hello "Crystal"

类型限制

我们的示例方法期望 recipient 是一个 String。但任何其他类型也可以工作。例如,尝试 say_hello 6

这对这个方法来说不一定是问题。使用任何其他类型都是有效的代码。但从语义上来说,我们希望以 String 类型的姓名来问候人们。

类型限制限制了参数允许的类型。它们出现在参数名后面,用冒号分隔。

def say_hello(recipient : String)
  puts "Hello #{recipient}!"
end

say_hello "World"
say_hello "Crystal"

# Now this expression doesn't compile:
# say_hello 6

现在名称不能再是数字或其他数据类型了。但这并不意味着你不能用数字作为名字来问候别人。数字只需要用字符串表示即可。例如,尝试 say_hello "6"

重载

限制参数的类型可用于位置重载。当方法具有未限制的参数,如 say_hello(recipient) 时,对方法 say_hello所有调用都会转到该方法。但是,通过重载,可以存在具有不同参数类型限制的多个同名方法。每个调用都路由到最适合的重载。

# This methods greets *recipient*.
def say_hello(recipient : String)
  puts "Hello #{recipient}!"
end

# This method greets *times* times.
def say_hello(times : Int32)
  puts "Hello " * times
end

say_hello "World"
say_hello 3

重载不仅由类型限制定义。参数的数量以及命名参数也是相关特征。

返回值

方法返回一个值,该值成为方法调用的值。默认情况下,它是方法中最后一个表达式的值

def adds_2(n : Int32)
  n + 2
end

puts adds_2 40

方法可以使用 return 语句在它主体中的任何位置返回。传递给 return 的参数将成为方法的返回值。如果没有参数,则为 nil

以下示例说明了显式隐式 return 的使用

# This method returns:
# - the same number if it's even,
# - the number multiplied by 2 if it's odd.
def build_even_number(n : Int32)
  return n if n.even?

  n * 2
end

puts build_even_number 7
puts build_even_number 28

返回值类型

让我们开始定义一个方法,我们期望它将返回一个 Int32 值,但错误地返回一个 String

def life_universe_and_everything
  "Fortytwo"
end

puts life_universe_and_everything + 1 # Error: no overload matches 'String#+' with type Int32

因为我们从来没有告诉编译器我们期望方法返回一个 Int32,所以编译器能做的最好的就是告诉我们没有 String#+ 方法接受 Int32 值作为参数(即编译器指向使用该值的地方,而不是指向错误的根源:方法返回值类型)。

如果使用类型信息,错误消息可以更准确,所以让我们再次尝试该示例,但现在指定类型

def life_universe_and_everything : Int32
  "Fortytwo"
end

puts life_universe_and_everything + 1 # Error: method top-level life_universe_and_everything must return Int32 but it is returning String

现在编译器可以准确地向我们显示问题的根源。正如我们所见,提供类型信息对于在编译时发现错误非常有用。