轻松掌握Julia的作用域

从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)的作用域

这个也不废话了,跟别的语言一个意思。

猜你喜欢

转载自blog.csdn.net/iamzhtr/article/details/93388930
今日推荐