Julia 流程控制
流程控制语句通过程序设定一个或多个条件语句来实现。在条件为 true 时执行指定程序代码,在条件为 false 时执行其他指定代码。
Julia 提供了大量的流程控制语句:
复合表达式:begin 和 ;。
条件表达式:if-elseif-else 和 ?: (三元运算符)。
短路运算:逻辑运算符 &&(与)和 ||(或),以及链式比较。
循环语句:while 和 for。
异常处理:try-catch、error 和 throw。
-
Task(协程):非局部控制流,详见 Task 与并发。
复合表达式
begin ... end 表达式可以按顺序计算若干子表达式,并返回最后一个子表达式的值:
实例
x = 1
y = 2
x + y
end
3
因为这些是非常简短的表达式,它们可以简单地被放到一行里,这也是 ; 链的由来:
实例
3
julia> begin x = 1; y = 2; x + y end
3
条件表达式
条件表达式可以根据布尔表达式的值来决定执行哪一个代码块。
if-elseif-else 的语法结构如下:
# 条件表达式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 即真):
实例
println("true")
end
ERROR: TypeError: non-boolean (Int64) used in boolean context
下面是对 if-elseif-else 条件语法的分析:
实例
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> println(x < y ? "less than" : "not less than")
less than
链式嵌套使用三元运算符:
实例
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仅当a为true的时候才会被执行。 - 在表达式
a || b中,子表达式b仅在a为false的时候才会被执行。
&& 实例
┌ Warning: An odd Number!
└ @ Main REPL[5]:1
julia> isodd(4) && @warn("An odd Number!")
false
|| 实例
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"。例如用 || 做参数检查:
实例
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:
[...]
循环语句
循环语句使用 while 和 for 两个关键字来实现。
下面是一个 while 循环的例子:
实例
julia> while i <= 3
println(i)
global i += 1
end
1
2
3
注意:while 循环体内修改外层全局变量时需要加 global 关键字。把代码放进函数里就不需要 global,这也是更好的写法。
for 循环使用起来会更加方便,以上实例使用 for 循环实现如下:
实例
println(i)
end
1
2
3
这里的 1:3 是一个范围对象,代表数字 1、2、3 的序列。
for 循环变量只在循环内部可见,循环结束后变量就不存在了,也不会影响外层的同名变量:
实例
println(j)
end
1
2
3
julia> j
ERROR: UndefVarError: `j` not defined in `Main`
一般来说,for 循环可以用于迭代任一个容器。在这种情况下,关键字 in 或者 ∈ 则更常用,因为它使得代码更清晰:
实例
println(i)
end
1
4
0
julia> for s ∈ ["foo", "bar", "baz"]
println(s)
end
foo
bar
baz
break 与 continue
我们可能会在测试条件不成立之前终止循环,这可以用关键字 break 来完成:
实例
println(j)
if j >= 3
break
end
end
1
2
3
在某些场景下,需要直接结束此次迭代并立刻进入下次迭代,continue 关键字可以用来完成此功能:
实例
if i % 3 != 0
continue
end
println(i)
end
3
6
9
多重循环与笛卡尔积
多个嵌套的 for 循环可以合并到一个外部循环,用来创建其迭代对象的笛卡尔积:
实例
println((i, j))
end
(1, 3)
(1, 4)
(2, 3)
(2, 4)
在这种写法中,内层循环可以使用外层循环变量(例如 for i = 1:n, j = 1:i),但 break 语句会跳出整个嵌套循环,而不仅仅是内层循环。
可以使用 zip 在单个 for 循环中同时迭代多个容器,一旦任何一个迭代器耗尽,循环就会停止:
实例
println((j, k))
end
(1, 4)
(2, 5)
(3, 6)
推导式
推导式是一种简洁的数据处理方式,可以从一个数据序列构建另一个新的数据序列。
格式如下:
[表达式 for 变量 in 列表]
# 带过滤条件
[表达式 for 变量 in 列表 if 条件]
实例
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)
我们还可以指定想要生成的元素类型:
实例
3-element Vector{Float32}:
1.0
4.0
9.0
遍历数组:enumerate
Julia 提供了 enumerate(iter) 函数,将生成索引号以及每个索引号对应的值:
实例
julia> for (index, value) in enumerate(a)
println("$index $value")
end
1 a
2 b
3 c
异常处理
程序执行过程中,如果发生意外条件,一个函数可能无法向调用者返回一个合理的值。通过 try / catch 语句,可以方便地处理异常情况。
实例
sqrt("ten")
catch e
println("你需要输入一个数字")
end
你需要输入一个数字
catch 后面的变量 e 保存了捕获到的异常对象,可以用 isa 判断异常类型并分别处理:
实例
sqrt(-1)
catch e
if isa(e, DomainError)
println("捕获到 DomainError,负数不能开平方")
else
rethrow() # 其他异常继续向上抛出
end
end
捕获到 DomainError,负数不能开平方
注意:Julia 的异常处理比条件判断慢得多,try/catch 只用于处理真正的异常情况,不要把它当作常规的流程控制使用。
finally 子句
finally 关键字提供了一种方式,无论代码块是如何退出的,都能够让代码块在退出时运行某段代码。典型用途是确保打开的文件被关闭:
实例
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:
实例
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 对象:
实例
true
julia> typeof(DomainError) <: Exception
false
error("message") 函数是抛出 ErrorException 的快捷方式,适合简单的错误提示:
实例
自定义异常类型
我们可以仿照 UndefVarError 的写法,自定义异常类型:
实例
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
