Julia机器学习核心编程:人人可用的高性能科学计算
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

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行的函数调用中,我们传入了所有的参数值,这时原来设置的可选参数的默认值就没有用了,计算的元素值全部被替换成所传入的值。