现在位置: 首页 > Julia 教程 > 正文

Julia 流程控制

流程控制语句通过程序设定一个或多个条件语句来实现。在条件为 true 时执行指定程序代码,在条件为 false 时执行其他指定代码。

Julia 提供了大量的流程控制语句:

  • 复合表达式begin;

  • 条件表达式:if-elseif-else?: (三元运算符)。

  • 短路运算:逻辑运算符 &&(与)和 ||(或),以及链式比较。

  • 循环语句:whilefor

  • 异常处理:try-catcherrorthrow

  • Task(协程):非局部控制流,详见 Task 与并发


复合表达式

begin ... end 表达式可以按顺序计算若干子表达式,并返回最后一个子表达式的值:

实例

julia> z = begin
           x = 1
           y = 2
           x + y
       end
3

因为这些是非常简短的表达式,它们可以简单地被放到一行里,这也是 ; 链的由来:

实例

julia> z = (x = 1; y = 2; x + y)
3

julia> begin x = 1; y = 2; x + y end
3

条件表达式

条件表达式可以根据布尔表达式的值来决定执行哪一个代码块。

if-elseif-else 的语法结构如下:

if 条件表达式1
    # 条件表达式1 为 true 时执行
elseif 条件表达式2
    # 条件表达式2 为 true 时执行
else
    # 上面条件都不为 true 时执行
end

当使用 if...elseif...else 语句时,以下几点需要注意:

  • 一个 if 后可跟零个或一个 else,else 必须在所有 elseif 之后。
  • 一个 if 后可跟零个或多个 elseif,elseif 必须在 else 之前。
  • 一旦某个条件匹配成功,其他的 elseif 或 else 将不会被测试。
  • 每个 if 块必须以 end 结束。

如果条件表达式不是 Bool 类型,Julia 会直接报错,这点和 C、Python 不同(C 语言中非 0 即真):

实例

julia> if 1
           println("true")
       end
ERROR: TypeError: non-boolean (Int64) used in boolean context

下面是对 if-elseif-else 条件语法的分析:

实例

julia> function test(x, y)
           if x < y
               println("x is less than y")
           elseif x > y
               println("x is greater than y")
           else
               println("x is equal to y")
           end
       end
test (generic function with 1 method)

julia> test(1, 2)
x is less than y

julia> test(2, 1)
x is greater than y

julia> test(1, 1)
x is equal to y

注意:if 块本身不会引入新的作用域,在 if 分支内定义的变量在外部也可以使用,但要保证所有分支都对该变量赋值,否则会报 UndefVarError。

三元运算符

三元运算符 ?: 类似 if-elseif-else 语法:

a ? b : c

? 之前的表达式 a 是一个条件表达式,如果条件 a 是 true,三元运算符计算在 : 之前的表达式 b;如果条件 a 是 false,则执行 : 后面的表达式 c。

注意:?: 旁边的空格是强制的,像 a?b:c 这种表达式不是有效的三元表达式。

实例

julia> x = 1; y = 2;

julia> println(x < y ? "less than" : "not less than")
less than

链式嵌套使用三元运算符:

实例

julia> test(x, y) = println(x < y ? "x is less than y"    :
                            x > y ? "x is greater than y" : "x is equal to y")
test (generic function with 1 method)

julia> test(1, 2)
x is less than y

与 if-elseif-else 类似,: 之前和之后的表达式只有在条件表达式为 true 或者 false 时才会被相应地执行(惰性求值)。


短路运算

Julia 中的 &&|| 运算符分别对应于逻辑"与"和"或"操作。

  • 在表达式 a && b 中,子表达式 b 仅当 atrue 的时候才会被执行。
  • 在表达式 a || b 中,子表达式 b 仅在 afalse 的时候才会被执行。

&& 实例

julia> isodd(3) && @warn("An odd Number!")
┌ Warning: An odd Number!
└ @ Main REPL[5]:1

julia> isodd(4) && @warn("An odd Number!")
false

|| 实例

julia> isodd(3) || @warn("An odd Number!")
true

julia> isodd(4) || @warn("An odd Number!")
┌ Warning: An odd Number!
└ @ Main REPL[8]:1

Julia 中经常用短路运算替代极短的 if 语句:cond && statement 读作"如果 cond 成立就执行 statement";cond || statement 读作"如果 cond 不成立就执行 statement"。例如用 || 做参数检查:

实例

# 递归阶乘:n 为负数时直接抛出错误
julia> function fact(n::Int)
           n >= 0 || error("n must be non-negative")
           n == 0 && return 1
           n * fact(n-1)
       end
fact (generic function with 1 method)

julia> fact(5)
120

julia> fact(-1)
ERROR: n must be non-negative
Stacktrace:
 [...]

循环语句

循环语句使用 whilefor 两个关键字来实现。

下面是一个 while 循环的例子:

实例

julia> i = 1;

julia> while i <= 3
           println(i)
           global i += 1
       end
1
2
3

注意:while 循环体内修改外层全局变量时需要加 global 关键字。把代码放进函数里就不需要 global,这也是更好的写法。

for 循环使用起来会更加方便,以上实例使用 for 循环实现如下:

实例

julia> for i = 1:3
           println(i)
       end
1
2
3

这里的 1:3 是一个范围对象,代表数字 1、2、3 的序列。

for 循环变量只在循环内部可见,循环结束后变量就不存在了,也不会影响外层的同名变量:

实例

julia> for j = 1:3
           println(j)
       end
1
2
3

julia> j
ERROR: UndefVarError: `j` not defined in `Main`

一般来说,for 循环可以用于迭代任一个容器。在这种情况下,关键字 in 或者 则更常用,因为它使得代码更清晰:

实例

julia> for i in [1, 4, 0]
           println(i)
       end
1
4
0

julia> for s ∈ ["foo", "bar", "baz"]
           println(s)
       end
foo
bar
baz

break 与 continue

我们可能会在测试条件不成立之前终止循环,这可以用关键字 break 来完成:

实例

julia> for j = 1:1000
           println(j)
           if j >= 3
               break
           end
       end
1
2
3

在某些场景下,需要直接结束此次迭代并立刻进入下次迭代,continue 关键字可以用来完成此功能:

实例

julia> for i = 1:10
           if i % 3 != 0
               continue
           end
           println(i)
       end
3
6
9

多重循环与笛卡尔积

多个嵌套的 for 循环可以合并到一个外部循环,用来创建其迭代对象的笛卡尔积:

实例

julia> for i = 1:2, j = 3:4
           println((i, j))
       end
(1, 3)
(1, 4)
(2, 3)
(2, 4)

在这种写法中,内层循环可以使用外层循环变量(例如 for i = 1:n, j = 1:i),但 break 语句会跳出整个嵌套循环,而不仅仅是内层循环。

可以使用 zip 在单个 for 循环中同时迭代多个容器,一旦任何一个迭代器耗尽,循环就会停止:

实例

julia> for (j, k) in zip([1 2 3], [4 5 6 7])
           println((j, k))
       end
(1, 4)
(2, 5)
(3, 6)

推导式

推导式是一种简洁的数据处理方式,可以从一个数据序列构建另一个新的数据序列。

格式如下:

# 基本格式
[表达式 for 变量 in 列表]

# 带过滤条件
[表达式 for 变量 in 列表 if 条件]

实例

julia> [x^2 for x in 1:5]
5-element Vector{Int64}:
  1
  4
  9
 16
 25

# 带 if 过滤条件
julia> [x for x in 1:10 if x % 2 == 0]
5-element Vector{Int64}:
  2
  4
  6
  8
 10

# 多变量嵌套
julia> [(i, j) for i in 1:2, j in 1:2]
2×2 Matrix{Tuple{Int64, Int64}}:
 (1, 1)  (1, 2)
 (2, 1)  (2, 2)

我们还可以指定想要生成的元素类型:

实例

julia> Float32[x^2 for x in 1:3]
3-element Vector{Float32}:
 1.0
 4.0
 9.0

遍历数组:enumerate

Julia 提供了 enumerate(iter) 函数,将生成索引号以及每个索引号对应的值:

实例

julia> a = ["a", "b", "c"];

julia> for (index, value) in enumerate(a)
           println("$index $value")
       end
1 a
2 b
3 c

异常处理

程序执行过程中,如果发生意外条件,一个函数可能无法向调用者返回一个合理的值。通过 try / catch 语句,可以方便地处理异常情况。

实例

julia> try
           sqrt("ten")
       catch e
           println("你需要输入一个数字")
       end
你需要输入一个数字

catch 后面的变量 e 保存了捕获到的异常对象,可以用 isa 判断异常类型并分别处理:

实例

julia> try
           sqrt(-1)
       catch e
           if isa(e, DomainError)
               println("捕获到 DomainError,负数不能开平方")
           else
               rethrow()   # 其他异常继续向上抛出
           end
       end
捕获到 DomainError,负数不能开平方

注意:Julia 的异常处理比条件判断慢得多,try/catch 只用于处理真正的异常情况,不要把它当作常规的流程控制使用。

finally 子句

finally 关键字提供了一种方式,无论代码块是如何退出的,都能够让代码块在退出时运行某段代码。典型用途是确保打开的文件被关闭:

实例

f = open("file")
try
    # 操作文件 f
finally
    close(f)
end

当控制流离开 try 代码块(例如遇到 return,或者正常结束),close(f) 就会被执行。如果 try 代码块由于异常退出,这个异常会继续传递。catch 代码块可以和 try、finally 配合使用,这时 finally 代码块会在 catch 处理错误之后才运行。

内建异常类型

Julia 内置了大量异常类型,常见的有:

异常类型触发场景
ArgumentError传给函数的参数值不合法
BoundsError数组索引越界
DivideError整数除零
DomainError参数超出函数定义域(如对负数开方)
InexactError类型转换丢失精度
KeyError字典中不存在该键
MethodError没有匹配的方法可以调用
TypeError类型不匹配(如非布尔值用于条件)
UndefVarError变量未定义

throw 函数

我们可以用 throw 显式地创建异常。例如,若一个函数只对非负数有定义,当输入参数是负数的时候,可以用 throw 抛出一个 DomainError:

实例

julia> f(x) = x>=0 ? exp(-x) : throw(DomainError(x, "argument must be non-negative"))
f (generic function with 1 method)

julia> f(1)
0.36787944117144233

julia> f(-1)
ERROR: DomainError with -1:
argument must be non-negative
Stacktrace:
 [...]

注意 DomainError 后面不接括号的话不是一个异常,而是一个异常类型。我们需要调用它来获得一个 Exception 对象:

实例

julia> typeof(DomainError(nothing)) <: Exception
true

julia> typeof(DomainError) <: Exception
false

error("message") 函数是抛出 ErrorException 的快捷方式,适合简单的错误提示:

实例

julia> error("出错了") ERROR: 出错了 Stacktrace: [...] [/mycode4]

自定义异常类型

我们可以仿照 UndefVarError 的写法,自定义异常类型:

实例

# 定义异常结构体,必须继承 Exception
julia> struct MyUndefVarError <: Exception
           var::Symbol
       end

# 自定义错误的显示方式
julia> Base.showerror(io::IO, e::MyUndefVarError) = print(io, e.var, " not defined")

julia> throw(MyUndefVarError(:x))
ERROR: x not defined