Ruby 中的函数 (function) 的本质其实是方法 (method)。关于这两者的差异,我们会在《第四周:面向对象再入门》中再详述。我们在这一章节中还是作为函数来处理。
理解什么是程序中的函数,我们先理解什么是数学中的函数。在小学和初中中学习的函数通常是比较狭隘的,往往把函数和表达式画上等号。但站在集合论的角度,我们可以更好理解函数的本质。我们把定义域和值域看成两个集合,函数就是定义域和值域的对应关系。
如果我们用比较粗糙的观点来看,函数 \(f\) 就是一个黑盒 (black box)。当你扔进去一个定义域里的 \(x\),函数就会返回一个经过函数处理的结果 \(y\),即 \(y = f(x)\)。程序中的函数也是这样一个东西,但是和数学中的函数也有一些细微差异。
目的上来说,程序中的函数和数学上的函数是一致的。在数学中,函数的引入极大简化了表达。重复的运算可以由函数或函数的组合来表达,同时我们也可以对函数本身进行进一步的研究(比如导数)。程序中也是同理。程序的开发是为了解决人类的重复性工作,其代码过程中也必然会有大量重复的,需要复用的地方。而函数就为这种场景提供了绝佳的解决方案。
但程序中的函数和数学上的函数还存在一定差异。对于一个数学函数,对于一个给定的 \(x\),无论传入多少次,其结果都是不变的。但这一行为在大多数编程语言中通常是不被保证的,即这一函数函数是不是纯函数 (pure function)。我们会在之后的例子中进一步讨论这一问题。
当我们把需求、比萨和咖啡扔给程序员,程序员就会输出程序。
从这个角度上来看,程序员不也是一个函数吗?
函数的三大要素:定义域、值域和映射方法。
对应到程序中就是参数、返回值和函数代码片段。我们也从这三块来理解 Ruby 中的函数定义。
def succ(x) # <- 函数的名称与参数
x + 1 # <- 函数的返回值
end # <- 函数代码结束
succ(1) # => 2
Ruby 的函数以 def
开始,def
是 define 的缩写,即要在此定义函数。
def
后接一个空格,然后是函数的名称,这里例子中是 succ
,即之后要用 succ
这个名字来调用这一函数。
函数名后紧跟一对括号,括号内是参数列表。函数的定义域在程序中就是以「参数 (arguments)」来实现的。如果没有参数,可以省略这一对括号。例如:
def hello
puts 'Hello'
end
括号内的参数是一些变量名,外面传入的参数就会在这里以这些变量被传入函数内。如果需要传入多个参数,则以逗号 ,
分割。
def add(a, b)
a + b
end
add(1, 2) # => 3
函数的映射方法是通过函数代码来实现的。参数列表后,end
关键字前即为函数代码,函数代码可以利用函数中的变量进行一些变换从而实现函数的功能。而最后一步,则是将函数的计算结果传回给函数的调用。函数的值域是通过「返回值 (return value)」来实现的。
在 Ruby 中函数有两种方法定义返回值。
-
使用 return 来返回值
-
默认将最后一次运算结果返回
在下面这个例子中:
def add(a, b)
a + b
end
add(1, 2) # => 3
函数的第一行也是最后一行代码是 a + b
,Ruby 会自动将这最后的计算结果作为返回值。
而类似于其它编程语言,Ruby 也支持使用 return
关键字来返回值。同时 return
也意味着程序的提前运行,这在我们之后讲到流程控制时会非常有用。
def add(a, b)
return a + b
puts 'wat?' # 这一行不会被运行
end
调用自己定义的函数如同我们之前调用过的所有系统内建的函数一样,直接函数名加上参数即可。
def add(a, b)
return a + b
end
add(1, 1) # => 2
add 1, 1 # => 2
在许多编程语言中,参数必须被 ()
包裹住。在 Ruby 中,如果嵌套关系明确,那么 ()
也可以省略。这也是为什么我们之前打印内容到屏幕时可以写成 puts 'Hello World'
,这其实和 puts('Hello World')
等价。
Tip
|
豆知识:
void v.s. nil Ruby 和其它语言还有一个在函数返回值上的差异。在 Ruby 中任何方法都有「返回值」,但返回值可能为「空」。这句话听起来非常拗口,我们来简单比较一下。如果我们在 C++ 语言中定义一个函数如下: void void_function() {
return;
}
int main() {
auto x = void_function(); // illegal, 非法代码。
return 0;
} 这段代码是非法的,因为 但是如果在 Ruby 中定义一个空函数, def nil_func
return
end
x = nil_func
p x # => nil 但是我们保持语义改用 Ruby 来写。 |
Note
|
小练习
|
Tip
|
豆知识:定义好的函数可以取消吗?
虽然这样的需求很少会发生,但 Ruby 还真的支持取消定义好的函数的特性。可以试一试下面的代码: def foo
'bar'
end
foo # => 'bar'
undef foo
foo # => NameError |