前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Julia(变量范围)

Julia(变量范围)

作者头像
云深无际
发布2021-04-14 14:02:47
3K1
发布2021-04-14 14:02:47
举报
文章被收录于专栏:云深之无迹

变量的范围是在其中可见变量的代码区域。变量作用域有助于避免变量命名冲突。这个概念很直观:两个函数都可以具有被调用x的参数,而两个函数都没有x引用相同的东西。同样,在许多其他情况下,不同的代码块可以使用相同的名称而无需引用相同的内容。相同变量名称何时引用或不引用相同事物的规则称为作用域规则。本节详细说明了它们。

该语言中的某些构造引入了作用域块,它们是有资格成为某些变量集范围的代码区域。变量的范围不能是源代码行的任意集合;相反,它将始终与这些块之一对齐。Julia中有两种主要的作用域类型:全局作用域局部作用域,后者可以嵌套。引入作用域块的构造为:

代码语言:javascript
复制
module
,,
baremodule
在交互式提示(REPL)下

该表中明显缺少开始块和if块,它们没有引入新的作用域块。所有这三种类型的作用域都遵循略有不同的规则,下面将对这些规则以及某些块的一些额外规则进行说明。

Julia使用词法作用域,即函数的作用域不是从调用者的作用域继承,而是从定义函数的作用域继承。例如,在以下代码中,x内部在其模块的全局范围内foo引用:xBar

代码语言:javascript
复制
julia> module Bar
           x = 1
           foo() = x
       end;

而不是x在使用范围内foo

代码语言:javascript
复制
julia> import .Bar

julia> x = -1;

julia> Bar.foo()
1

因此,词法作用域意味着可以仅从源代码推断变量的范围。

全球范围

每个模块都引入了一个新的全局范围,与所有其他模块的全局范围分开;没有涵盖所有领域的全球范围。模块可以通过using或import语句或通过使用点符号的限定访问将其他模块的变量引入其作用域,即,每个模块都是所谓的命名空间。请注意,变量绑定只能在其全局范围内更改,而不能从外部模块更改。

代码语言:javascript
复制
julia> module A
           a = 1 # a global in A's scope
       end;

julia> module B
           module C
               c = 2
           end
           b = C.c    # can access the namespace of a nested global scope
                      # through a qualified access
           import ..A # makes module A available
           d = A.a
       end;

julia> module D
           b = a # errors as D's global scope is separate from A's
       end;
ERROR: UndefVarError: a not defined

julia> module E
           import ..A # make module A available
           A.a = 2    # throws below error
       end;
ERROR: cannot assign variables in other modules

请注意,交互式提示(aka REPL)在模块的全局范围内Main

当地范围

大多数代码块都引入了新的本地范围,有关完整列表,请参见上表。本地范围通常从其父范围继承所有变量,以进行读取和写入。局部作用域有两种子类型,硬性和软性,关于继承什么变量的规则略有不同。与全局作用域不同,局部作用域不是名称空间,因此内部作用域中的变量无法通过某种合格的访问从父作用域中检索。

以下规则和示例同时适用于硬本地作用域和软本地作用域。在本地范围内新引入的变量不会反向传播到其父范围。例如,此处z未引入顶级范围:

代码语言:javascript
复制
julia> for i = 1:10
           z = i
       end

julia> z
ERROR: UndefVarError: z not defined

(请注意,在此示例和以下所有示例中,假定它们的顶级是具有干净工作空间的全局范围,例如新启动的REPL。)

在局部范围内,可以使用local关键字将变量强制为局部变量:

代码语言:javascript
复制
julia> x = 0;

julia> for i = 1:10
           local x
           x = i + 1
       end

julia> x
0

在局部范围内,可以使用关键字定义新的全局变量global

代码语言:javascript
复制
julia> for i = 1:10
           global z
           z = i
       end

julia> z
10

两者的位置localglobal关键字的范围块内是无关紧要的。以下等价于最后一个示例(尽管从风格上讲更糟):

代码语言:javascript
复制
julia> for i = 1:10
           z = i
           global z
       end

julia> z
10

软本地范围

在软本地范围内,所有变量均从其父范围继承,除非使用关键字专门标记了变量local

软局部作用域由for循环,while循环,理解,try-catch-finally块和let块引入。对于Let块和For循环和理解有一些额外的规则。

在以下示例中,xy始终引用相同的变量,因为软本地作用域继承了读取和写入变量:

代码语言:javascript
复制
julia> x, y = 0, 1;

julia> for i = 1:10
           x = i + y + 1
       end

julia> x
12

在软作用域内,尽管允许使用global关键字,但从不需要。唯一会改变语义的情况是(当前)语法错误:

代码语言:javascript
复制
julia> let
           local j = 2
           let
               global j = 3
           end
       end
ERROR: syntax: `global j`: j is local variable in the enclosing scope

硬本地范围

硬局部作用域由函数定义(以其所有形式),结构类型定义块和宏定义引入。

在硬本地作用域中,所有变量均从其父作用域继承,除非:

  • 分配将导致修改的全局变量,或者
  • 变量专门用关键字标记local

因此,全局变量仅继承用于读取,而不能继承:

代码语言:javascript
复制
julia> x, y = 1, 2;

julia> function foo()
           x = 2        # assignment introduces a new local
           return x + y # y refers to the global
       end;

julia> foo()
4

julia> x
1

global需要一个显式分配给全局变量:

代码语言:javascript
复制
julia> x = 1;

julia> function foobar()
           global x = 2
       end;

julia> foobar();

julia> x
2

请注意,嵌套函数可以修改其父范围的局部变量,因此它们的行为可能不同于全局范围中定义的函数:

代码语言:javascript
复制
julia> x, y = 1, 2;

julia> function baz()
           x = 2 # introduces a new local
           function bar()
               x = 10       # modifies the parent's x
               return x + y # y is global
           end
           return bar() + x # 12 + 10 (x is modified in call of bar())
       end;

julia> baz()
22

julia> x, y
(1, 2)

继承全局变量和局部变量进行分配之间的区别可能导致在局部作用域和全局作用域中定义的函数之间存在一些细微差异。通过转到bar全局范围,考虑对最后一个示例的修改:

代码语言:javascript
复制
julia> x, y = 1, 2;

julia> function bar()
           x = 10 # local
           return x + y
       end;

julia> function quz()
           x = 2 # local
           return bar() + x # 12 + 2 (x is not modified)
       end;

julia> quz()
14

julia> x, y
(1, 2)

请注意,上述细微之处与类型和宏定义无关,因为它们只能出现在全局范围内。关于默认值和关键字函数自变量的评估,有一些特殊的作用域规则,这些规则在“ 功能”部分中进行了介绍。

引入一个在函数,类型或宏定义内部使用的变量的赋值不必先于其内部使用:

代码语言:javascript
复制
julia> f = y -> y + a
(::#1) (generic function with 1 method)

julia> f(3)
ERROR: UndefVarError: a not defined
Stacktrace:
 [1] (::##1#2)(::Int64) at ./none:1

julia> a = 1
1

julia> f(3)
4

对于普通变量来说,这种行为似乎有些奇怪,但是允许在命名函数使用之前就使用命名函数,这些函数只是保存函数对象的普通变量。这允许以直观且方便的顺序定义函数,而不是强制自下而上排序或要求向前声明,只要它们在实际调用时定义即可。例如,这是一种无效的,相互递归的方法来测试正整数是偶数还是奇数:

代码语言:javascript
复制
julia> even(n) = n == 0 ? true : odd(n-1);

julia> odd(n) = n == 0 ? false : even(n-1);

julia> even(3)
false

julia> odd(3)
true

Julia提供了内置的高效函数来测试所调用的奇数和偶数iseven()isodd()因此上述定义仅应作为示例。

硬与软本地范围

引入软局部作用域的块(例如循环)通常用于在其父作用域中操作变量。因此,它们的默认值是完全访问其父作用域中的所有变量。

相反,引入硬本地作用域(功能,类型和宏定义)的块内代码可以在程序中的任何位置执行。远程更改其他模块中全局变量的状态时应格外小心,因此这是一个需要global关键字的选择功能。

允许在嵌套函数中修改父作用域的局部变量的原因是允许构造具有私有状态的闭包,例如state以下示例中的变量:

代码语言:javascript
复制
julia> let
           state = 0
           global counter
           counter() = state += 1
       end;

julia> counter()
1

julia> counter()
2

另请参见下两节示例中的闭包。

让块

与分配给局部变量不同,let语句每次运行时都会分配新的变量绑定。分配会修改现有的价值地点,并let创建新的地点。这种差异通常并不重要,只有在通过闭包超出其作用域的变量的情况下才可以检测到。该let语法接受一系列以逗号分隔的赋值和变量名称:

代码语言:javascript
复制
julia> x, y, z = -1, -1, -1;

julia> let x = 1, z
           println("x: $x, y: $y") # x is local variable, y the global
           println("z: $z") # errors as z has not been assigned yet but is local
       end
x: 1, y: -1
ERROR: UndefVarError: z not defined

分配是按顺序进行评估的,在引入左侧的新变量之前,将在范围中对每个右侧进行评估。因此写类似的东西是有道理的,let x = x因为两个x变量是不同的并且具有独立的存储。这是一个let需要行为的示例:

代码语言:javascript
复制
julia> Fs = Array{Any}(2); i = 1;

julia> while i <= 2
           Fs[i] = ()->i
           i += 1
       end

julia> Fs[1]()
3

julia> Fs[2]()
3

在这里,我们创建并存储两个返回变量的闭包i。但是,它始终是相同的变量i,因此两个闭包的行为相同。我们可以用来let为创建新的绑定i

代码语言:javascript
复制
julia> Fs = Array{Any}(2); i = 1;

julia> while i <= 2
           let i = i
               Fs[i] = ()->i
           end
           i += 1
       end

julia> Fs[1]()
1

julia> Fs[2]()
2

由于该begin构造不会引入新的作用域,因此使用零参数let仅引入一个新的作用域块而不创建任何新的绑定可能会很有用:

代码语言:javascript
复制
julia> let
           local x = 1
           let
               local x = 2
           end
           x
       end
1

由于let引入了新的作用域块,因此内部局部x变量与外部局部变量是不同的变量x

对于循环和理解

for循环和理解具有以下行为:在其主体作用域中引入的任何新变量都为每次循环迭代新鲜分配。这与while循环将变量用于所有迭代的循环形成对比。因此,这些构造类似于内部while带有let块的循环:

代码语言:javascript
复制
julia> Fs = Array{Any}(2);

julia> for j = 1:2
           Fs[j] = ()->j
       end

julia> Fs[1]()
1

julia> Fs[2]()
2

for 循环将现有变量重用于其迭代变量:

代码语言:javascript
复制
julia> i = 0;

julia> for i = 1:3
       end

julia> i
3

但是,理解并不能做到这一点,而是总是重新分配其迭代变量:

代码语言:javascript
复制
julia> x = 0;

julia> [ x for x = 1:3 ];

julia> x
0

常数

变量的常见用法是为特定的不变值命名。此类变量仅分配一次。可以使用const关键字将该意图传达给编译器:

代码语言:javascript
复制
julia> const e  = 2.71828182845904523536;

julia> const pi = 3.14159265358979323846;

const声明是允许全球和局部变量,但对全局尤其有用。编译器很难优化涉及全局变量的代码,因为它们的值(甚至它们的类型)几乎可以随时更改。如果全局变量不变,则添加const声明可以解决此性能问题。

局部常数有很大的不同。编译器能够自动确定局部变量何时为常量,因此出于性能目的,不需要局部常量声明。

默认情况下,特殊的顶级分配(例如,functionstruct关键字执行的顶级分配)是恒定的。

注意,这const仅影响变量绑定;变量可以绑定到可变对象(例如数组),并且该对象仍可以被修改。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-06-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 云深之无迹 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 全球范围
  • 当地范围
    • 软本地范围
      • 硬本地范围
        • 硬与软本地范围
          • 让块
            • 对于循环和理解
            • 常数
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档