3.4 理解函数中变量的作用域
当我们在Julia中定义函数时,也可以在函数体内定义变量。在这种情况下,该变量在该函数的局部范围内有效,因此称为局部变量。而未在函数体内声明的变量在全局范围内有效,因此称为全局变量。
不同代码块中的变量可以使用相同的名称,但引用的是不同的实体,这种特性就是由其范围规则所定义的。
Julia有两种主要的范围类型:全局范围和局部范围。其中局部范围可以被嵌套。除非另有说明,否则模块和REPL中的变量通常在全局范围内;循环、函数、宏、try-catch-finally块中的变量在局部范围内。
下面我们用一个例子来解释局部范围。
【范例3-14】变量的局部范围
我们在for结构中定义了一个局部变量hello,并试图在for结构外输出它。
01 julia> for i= 1:100 02 hello = i 03 end 04 julia> hello 05 ERROR: UndefVarError: hello not defined
代码01~03行定义了一个for结构,其中声明了一个hello变量。在04行我们试图输出hello的值,但是却得到了05行的报错,报错类型是未定义变量错误,提示hello并没有被定义。这是为什么呢?
因为对于整个程序来说,for结构属于一个局部,所以在for结构中声明的hello仅在for循环的范围内可用,在for循环的范围外不可用。
我们修改上一个函数,使得在循环外部也可以访问到hello。
01 julia> for i=1:100 02 global hello 03 hello = i 04 end 05 06 julia> hello 07 100
这段代码和范例3-14的代码几乎完全一样,唯一的区别在于添加了02行,用global关键字显式声明hello为全局变量,这样就可以在06行要求显示hello的值时,成功地将hello的值打印出来了。
Julia使用了一种称为词法作用域的机制,简单来说,就是函数的作用域不会从其调用对象的作用域继承,而是从函数定义的作用域继承。为了更清楚地理解这一点,我们通过一个例子来说明。
【范例3-15】词法作用域例子
本例定义了一个模块,并在模块外对其进行了调用。
01 julia> module Utility 02 name = "Julia" 03 tell_name() = name 04 end 05 Utility 06 07 julia> name = "Python" 08 "Python" 09 10 julia> Utility.tell_name() 11 "Julia"
代码01~05行用module关键字创建了一个名为“Utility”的模块,它包含一个name变量和一个tell_name()函数。我们将Utility模块内的name变量的值设置为“Julia”。为了对比,07行我们在Utility模块外声明了另一个name变量,并将它的值设置为“Python”。
在代码10行,当调用Utility.tell_name()时,我们得到的值是“Julia”。这表明该函数使用了在Utility模块中定义的name变量的值,这是因为函数tell_name()本身被定义在Utility模块中。所以,在Utility模块外声明的另一个name变量,不会影响函数的运行结果。
Julia还提供了对局部范围的进一步分类,分为软局部范围和硬局部范围,刚才的函数介绍的是硬局部范围。我们将在接下来的章节中重新讨论范围,但是现在,我们继续将注意力放在函数的范围上。
假设有一个alpha()函数,它的作用是将参数传递给一个名为“x”的局部变量并返回x。同时,我们定义了另一个全局变量x,并将它的值设置为100。
【范例3-16】函数范围
我们在函数内和函数外分别声明了两个不同的x变量。
01 julia> x = 100 02 100 03 04 julia> function alpha(n::Int64) 05 x = n 06 return x 07 end 08 alpha (generic function with 1 method) 09 10 julia> alpha(50) 11 50 12 13 # 全局变量x并没有改变 14 julia> x 15 100
代码01行定义了一个变量x。04~07行定义了一个函数,在其中定义了另一个变量x,并通过参数赋值的形式对x的值进行了更改。在代码10行我们对该函数进行了调用,返回的x的值是50。代码14行直接输出x的值,输出的x的值是100。
如果仔细观察,就会发现x的值自始至终都是100,但是当我们调用alpha(50)函数时,函数返回的是50而不是100。这是因为在函数内部声明的变量(即函数中的局部变量x)在函数内部被赋值为参数所传递的数值(即n),而全局变量x的值对函数内部的局部变量x却无法直接造成影响。
如果想要在函数内部使用全局声明的x,该怎么办呢?我们可以使用global关键字。
【范例3-17】在函数内部使用全局变量
我们将函数中的x变量替换为全局的x,然后观察调用函数后结果发生了怎样的改变。
01 julia> x = 100 02 100 03 04 julia> function alpha(n::Int64) 05 global x = n 06 end 07 alpha (generic function with 1 method) 08 09 julia> alpha(50) 10 50 11 12 # 全局变量x的值现在被改变了 13 julia> x 14 50
这段代码和范例3-16的代码几乎完全相同,只是在05行函数体内删除了为局部变量x指定数值的代码,取而代之的是为全局变量x指定n的值。结果很明显,和我们所期望的一样,全局变量x的值已经被改变为50,而不是原始值100了。因为在函数体中我们操作的不再是局部变量x,而是全局变量x。