3.3 参数类型
函数参数是以输入的形式传递给函数的变量,以便让函数返回一个特定的输出值。一个非常简单的函数是只有一个参数,例子如下:
【范例3-7】带有一个参数的函数
本例中定义的函数接收一个字符串参数,并在函数体内使用该参数。
01 julia> function say_hello(name) 02 println("Hello $name") 03 end 04 say_hello (generic function with 1 method) 05 julia> say_hello("Julia") 06 Hello Julia
代码01~03行定义了一个函数,并传入一个参数name。和之前定义的函数不同的是,在本例中,通过“$函数名”的形式调用了传入参数的值。代码05行调用该函数,并传入一个字符串类型的参数。代码06行是函数执行后的输出结果,可以看到,传入的参数被包含在字符串中一并输出了。
我们甚至不需要对字符串和参数进行连接操作,而是直接在字符串中使用传入的变量就可以了。
需要记住的是,尽管Julia中的类型是动态类型,但它支持使用静态类型的变量。修改前面的代码,我们可以得到以下代码。
【范例3-8】带有一个静态类型参数的函数
我们通过“::”运算符限定了传入参数的类型。
01 julia> function say_hello(name::String) 02 println("Hello $name") 03 end 04 say_hello (generic function with 1 method) 05 julia> say_hello("Julia ") 06 Hello Julia
代码01~03行声明的函数和范例3-7中的函数相似,不同之处是这里限制了输入参数的类型,让函数只能接收字符串类型的参数。
读者可能已经注意到,函数的行为方式没有发生变化。但是,声明传递的参数类型有一个巨大的好处,就是提高了速度(本书后面介绍Julia与性能相关的增强功能时将详细讨论)。
我们可以通过多种方式将参数传递给函数,下面一一讨论。
3.3.1 无参函数
有时我们想定义一个没有参数的函数,就像下面这个函数一样。
【范例3-9】一个没有参数的函数
本例中定义了一个没有参数也没有过程的函数。
01 julia> function does_nothing 02 end 03 does_nothing (generic function with 0 methods)
虽然这个函数什么都没做,但是有时它会有特殊的用途,比如只想让函数定义以接口的形式出现,就可以使用它。
3.3.2 可变参数
当我们不确定事先传递给函数的参数的数量时,可变参数(Varargs)就派上用场了。因为在这种情况下,我们希望可以向函数传递任意数量的参数。
在Julia中,我们可以通过“…”来声明一个函数的参数是可变参数。下面通过一个例子来进一步解释。
【范例3-10】一个带有可变参数的函数
本例中的函数用于将传入的参数一次输出到屏幕上。
01 julia> function letsplay(x,y…) 02 println(x) 03 println(y) 04 end 05 letsplay (generic function with 1 method) 06 07 julia> letsplay("苹果","香蕉","榴 ") 08 苹果 09 ("香蕉","榴 ")
代码01~04行声明了letsplay函数,仔细观察声明时的参数列表,这里使用“…”代表传入的参数数量是不确定的,有可能很少,也有可能很多。这时Julia可以将这些参数解释为位置并相应地映射水果和位置之间的关系。
代码07行对函数进行了调用,其第一个参数x通过位置映射到苹果,并在println()函数中显示。所以,x="苹果"。
第二个参数y被解释为一个元组,因为它在声明时后面跟着“…”。因此,y被映射到香蕉和榴梿。所以,y=("香蕉","榴梿")。
另外,传递给函数的参数可以通过多种方式预先声明。例如有一个函数,它以范例3-11所示的方式接收可变参数。
【范例3-11】传递一个元组给可变参数
本例中定义了一个接收可变参数的函数,并向该函数传递一个元组的元素作为参数值。
01 julia> x = (2,4,6,8,10) 02 (2,4,6,8,10) 03 julia> function numbers(a…) 04 println("the arguments are -> ", x) 05 end 06 numbers (generic function with 1 method) 07 julia> numbers(x) 08 the arguments are -> (2,4,6,8,10)
代码01行首先定义了一个元组x,作为后面准备传递给函数的参数。代码03~05行定义了一个函数,用来输出我们刚才输入的参数。可以看出,即使传入的是一个元组,也不会有任何问题。
下面我们就通过typeof函数来得到x的数据类型,就如你所看到的,确实向函数传递了一个元组的值。
01 julia> typeof(x) 02 Tuple{Int64,Int64,Int64,Int64,Int64}
我们也可以将x作为数组传递,但是最终结果不会受到影响。为了证明这一点,我们将x初始化为数组并重新编写代码,结果在我们意料之中。
【范例3-12】传递一个数组给可变参数
本例中将一个数组传递给可变参数。
01 julia> x = [1, 3, 5, 7, 9] 02 5-element Array{Int64,1}: 03 1 04 3 05 5 06 7 07 9 08 09 julia> typeof(x) 10 Array{Int64,1} 11 12 julia> numbers(x) 13 the arguments are -> [1, 3, 5, 7, 9]
代码01行声明了一个列数组,作为要传递给函数的参数。代码02~07行是x的值的输出结果。代码09行调用了typeof()函数来查看x的类型,结果如10行所示,类型是一个数组。在代码12行,我们将x作为参数传递给numbers,13行正确地输出了结果。
3.3.3 可选参数
有时候,在特定用例的实现过程中,你可能希望某些参数是固定的(即该参数必填)或者有一个默认值。例如,你希望将一个数字转换为二进制或十六进制形式,最适合的方法是设置一个base参数作为底数,然后根据需求为其设置不同的默认值。
通过这种方法,你只需要一个函数将base设置成2或16,即convert_to_base(base=2)或convert_to_base(base=16)即可,而不是使用convert_to_binary()和convert_to_hex()两个不同的函数。
【范例3-13】具有可选参数的函数
本例中定义了一个函数,它可以有必填参数和可选参数。
01 # 函数f有一个必填参数 02 # 有两个可选参数 03 julia> function f(x, y=20, z=3) 04 x+y+z 05 end 06 f (generic function with 1 method) 07 08 julia> f(3) 09 26 10 julia> f(100, 40) 11 143 12 julia> f(100, 40, 20) 13 160
代码01~03行定义了一个函数,用来计算三个数的和。这里我们为一些参数指定了默认值,这就意味着有默认值参数的函数,即使在传入参数时不传递该参数,函数也能够根据默认值来正常运行。
代码08行只输入一个参数3,函数f至少要接收一个参数,因为在定义时x是没有默认值的,所以x是必填参数。从09行的运行结果可以看出,该函数在实际运行时将x的传入值和y、z的默认值进行了相加。
代码10行向该函数传入了两个参数,它们分别对应参数x和参数y。从11行的运行结果可以看出,这里仅有z使用了默认值进行计算,而y的默认值20被传入的值40替换了。
同样,在代码12行的函数调用中,我们传入了所有的参数值,这时原来设置的可选参数的默认值就没有用了,计算的元素值全部被替换成所传入的值。