3.4 常量

在 Julia 中,常量是一种特殊的变量。我们可以使用关键字const来定义一个常量:

  1. const A = 2020

当需要同时定义多个常量时,我们还可以使用平行赋值法:

  1. const W, U, V = 2020, 2030, 2050

在这里,常量WUV的值分别为202020302050

我们已经知道,为全局变量附加类型标注目前是行不通的。实际上,Julia 官方现在不建议我们在程序中使用全局变量。因为全局变量总是会给程序带来不小的性能负担。这正是用于全局变量的值及其类型都是可以被随时改变的。而全局常量可以让这个问题迎刃而解。所以,我们应该使用全局常量,而不是全局变量。这也是 Julia 中很重要的一条编码建议。

相反,在局部,我们应该使用局部变量,而不是局部常量。因为定义一个局部常量完全是没有必要的。Julia 一旦在局部碰到一个不变的变量就会把它优化成常量。你其实用不着专门去记这条编码建议。因为定义局部常量根本就不符合 Julia 的语法规则,例如:

  1. julia> function get_uint32(x)
  2. const y::UInt32 = x
  3. y
  4. end
  5. ERROR: syntax: unsupported `const` declaration on local variable around REPL[1]:2
  6. # 省略了一些回显的内容。
  7. julia>

不过,这个语法规则的制定原因我们还是应该了解的。

另外,与其他的一些编程语言不同,Julia 中的常量的值不仅可以是数值和字符串,还可以是像数组这样的可变对象。也正因为如此,Julia 常量只能保证名称与值之间的绑定是不可变的,但是并不能防止值本身的变化,比如数组中的某个元素值的改变。所以,我们尽量不要把本身可变的值赋给全局常量。

下面,我们来说 Julia 常量的最重要的 3 个特性。其中的一个比较反直觉的特性是:我们似乎可以改变一个常量的值。更明确地说,我们对常量的重新赋值只会引发一个警告,而不会得到一个错误。例如,我们看似可以对前面定义过的常量A进行重新赋值:

  1. julia> A = 2050
  2. WARNING: redefining constant A
  3. 2050
  4. julia>

根据警告的内容可知,Julia 称其为对常量的重新定义。这相当于放弃了以前的常量定义,而采用了新的定义。那么,常量的重新定义与我们之前说的重新赋值到底有什么不一样呢?我们可以通过下面的示例进行理解。

  1. julia> const C = 2020
  2. 2020
  3. julia> f() = C + 30
  4. f (generic function with 1 method)
  5. julia> f()
  6. 2050

我先定义了一个名称为C、值为2020的常量,紧接着又用一种简洁的方式定义了一个名称为f的函数。这个函数的功能很简单,即:在常量C代表的值之上再加30并将结果返回。现在,我们重新定义常量C

  1. julia> C = 2030
  2. WARNING: redefining constant C
  3. 2030

然后,再次调用函数f

  1. julia> f()
  2. 2050
  3. julia>

可以看到,调用f函数后得到的结果依然为2050。这是因为函数f使用的仍然是在它之前定义的那个常量C,而不是我们重新定义的常量C

图 3-2 常量的重新定义 图 3-2 常量的重新定义

我们可以想象,如果真有一种方法可以对常量C进行重新赋值的话,那么再次调用f函数肯定不会得到这样的结果。

因此,在 Julia 中,我们只能对常量进行重新定义,而不能进行重新赋值。正因为常量的重新定义所带来的效果有些反直觉,所以我们最好还是不要重新定义常量。为此,我们还应该让常量的名称看起来特别一些,比如全大写,以避免之后的误操作。我们还要注意程序输出中的此类警告,并及时纠正这种不好的做法。

Julia 常量的第二个重要特性是,如果我们在重新定义常量的时候试图赋予它一个不同类型的值,那么就会得到一个错误。例如:

  1. julia> C = "2020"
  2. ERROR: invalid redefinition of constant C
  3. # 省略了一些回显的内容。
  4. julia>

注意,这里报出的错误是“对常量C的重新定义无效”,而不是“不能改变常量C的类型”。所以,这里的规则是,在对常量进行重新定义时只能赋予一个与前值类型相同的值。

基于此,常量的第三个重要特性就比较好理解了。这个特性就是,如果在重新定义常量时赋予它相同的值,那么既不会引发警告也不会导致错误报告。比如:

  1. julia> const D = "2020"
  2. "2020"
  3. julia> D = "2020"
  4. "2020"
  5. julia> d = "2020"
  6. "2020"
  7. julia> D = d
  8. "2020"
  9. julia>

不过,需要注意,这只是针对不可变对象(或者说本身不可变的值)而言的。对于可变对象(比如数组),即使前后两个值看起来相同,Julia 也照样会发出警告。例如:

  1. julia> const E = ["2020"]
  2. 1-element Array{String,1}:
  3. "2020"
  4. julia> E = ["2020"]
  5. WARNING: redefining constant E
  6. 1-element Array{String,1}:
  7. "2020"
  8. julia>

所以,还是那句话,我们尽量不要把本身可变的值赋给常量。顺便说一下,上述代码中的字面量["2020"]表示的是只包含了一个元素值(即"2020")的一维数组。倘若一个一维数组中有多个元素值,那么我们就需要用英文逗号把这些元素值分隔开,如["2020", "2050"]

由以上特性可知,常量看似可以被当做类型固定的全局变量来使用。但实际上,对常量的重新定义会埋下隐患,而且由此引发的程序错误将会很难排查和定位。所以,我们可以使用常量来存储全局性的对象,但最好不要对它进行重新定义。另外,我们尽量不要把可变对象赋给常量。