从Matlab迁移过来的用户会觉得Julia的作用域有点迷。说实话我也绕了几个弯子。为了避免把读者带进概念的漩涡中,本文列举几个典型情况,帮助读者迅速解决作用域的问题。
循环体的作用域
Matlab的作用域包括一个全局域和各个函数的局部域。Julia相对更繁琐,有一个全局域和许多类型的局部域。循环体就是一种局部域,例如:
a = 1
for i=1:2
a += 1
end
会报错ERROR: LoadError: UndefVarError: a not defined
,证明全局域的变量没有自动继承到局部域中。实际上,Julia会智(zhi)能(zhang)地判断哪些变量自动继承,规则是:
- 假如全局变量在局部域中没有被修改,即从不出现在等号左侧,那它会被自动继承。你可以尽情地使用全局变量做任何事情,只要不修改它。
- 反之,如果全局变量被修改,会创建一个同名局部变量取代之。这个局部变量的任何变化都不会影响到全局变量。
为解决报错,增加一个global
标识:
a = 1
for i=1:2
global a
a += 1
end
得到了a = 3
。有趣的是global
语句放在局部域的任一位置都可以。
多层循环只需要在最外层标识,例如:
a = 1
for i=1:2
global a
for j=1:3
a += 1
end
end
但global
绝不能放在循环体外面,像这样就会报错:
a = 1
global a
for i=1:2
for j=1:3
a += 1
end
end
这么一看的确挺迷的,反正记住就行。如果要强行归纳的话,大概就是:global
能使循环体的局部域”向外看“,也能使外层循环”向里看“,但不能使全局域”向里看“。想刨根问底的话,请参阅let
语法的相关文档。
如果只是把全局变量当作循环变量,那就不必加global
标识,原因在于我们没有修改它。例如:
m = 3; n = 0
for i=1:m
global n
n += 1
end
n
函数的作用域
函数的局部域比循环体更复杂一点点,包括两种情况:(1)函数与循环体遵循相同的自动继承规则,可以直接继承全局变量,只要你不在函数里修改它。例如:
n = 1
function g()
return n+1
end
g()
这时候要注意,如果函数里有循环体,循环体会在函数的局部域中划分出一个新的局部域。例如:
m = 3; n = 0
function g(m)
for i=1:m
global n
n += 1
end
return n
end
g(m)
假如去掉global n
就会报错。所以要时刻提防循环体。
(2)如果某个变量被当作参数传递,那么函数会在局部域中创建一个同名局部变量,即按照”传值“的方式进行传递。这样创建的局部变量不会影响同名的全局变量,与上面的”放弃自动继承“是一个意思。例如:
t = 1; w = 0
function y(w)
w = t
end
println("y=",y(w)," w=",w)
输出是:
julia> include("Demo.jl")
y=1 w=0
当然,global
标识也可以用在这里,例如:
t = 1; w = 0
function y()
global w
w = t
end
println("y=",y()," w=",w)
输出是:
julia> include("Demo.jl")
y=1 w=1
但是!!!如果传递的参数较为复杂,那么函数会智(zhi)能(zhang)地改变传递策略,从”传值“变为”传址“,即传递一个指针,任何修改都会直接作用到原地址上。例如传入一个结构体数组:
struct atom
u
v
end
w = Array{atom}(undef,2,2)
function y(w)
w[1,2] = atom(0.1,0.2)
w
end
println("y[1]=",y(w)[1,2]," w[1]=",w[1,2])
w
输出是:
julia> include("DemoParallel_1.jl")
y[1]=atom(0.1, 0.2) w[1]=atom(0.1, 0.2)
2×2 Array{atom,2}:
#undef atom(0.1, 0.2)
#undef #undef
可见确实是引用而不是传值。有趣的是,当采取引用策略时,会无视规则强行继承,例如:
struct atom
u
v
end
n = 10
w = Array{atom}(undef,2,2)
function y()
w[1,2] = atom(0.1,0.2)
w
end
println("y[1]=",y()[1,2]," w[1]=",w[1,2])
w
经过测试,Julia会把结构体、结构体数组视为”复杂的“,而把普通的数值、数值数组、共享数组视为”简单的“,无论规模大小。举个结构体的例子:
mutable struct atom
u
v
end
w = atom(0.1,0.2)
function y(w)
w.u = 0.5
w
end
println("y[1]=",y(w).u," w[1]=",w.u)
begin…end的作用域
有个特别的语法begin...end
可以把若干表达式凑在一起。例如:
w = begin
w = 1
w += 0.1
end
它实际上等价于w = ( w = 1; w += 0.1)
,是后者的分行写法。这种结构是没有局部域的。
模块(module)的作用域
这个也不废话了,跟别的语言一个意思。