控制流程
Julia提供了多种控制流构造:
* 复合表达式: 開始啦。 和 ;.
* 条件评价: 如果-埃尔塞夫-其他 和 ?: (三元运算符)。
* 短路评估:逻辑运算符 && (“和”)和 || (“或”),并且还链接比较。
* 重复评价:循环: 而 和 为.
* 异常处理: 试试-渔获, 错误和 投掷.
* 任务(又名协程): 耶尔德托.
前五个控制流机制是高级编程语言的标准。 任务s不是那么标准:它们提供非本地控制流,使得可以在临时暂停的计算之间切换。 这是一个强大的构造:异常处理和合作多任务都是在Julia中使用任务实现的。 日常编程不需要直接使用任务,但是使用任务可以更容易地解决某些问题。
复合表达式
有时,有一个表达式可以按顺序计算多个子表达式,并返回最后一个子表达式的值作为其值。 有两个Julia构造可以实现这一点: 開始啦。 街区和 ; 锁链。 两个复合表达构建体的值都是最后一个子表达的值。 这是一个示例 開始啦。 座:
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
julia> (x = 1;
y = 2;
x + y)
3
条件评估
条件求值允许对部分代码进行求值或不求值,具体取决于布尔表达式的值。 以下是 如果-埃尔塞夫-其他 条件语法:
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
如果条件表达式 x<y 是 真的,则对相应的块进行求值;否则条件表达式 x>y 被评估,如果是 真的,计算相应的块;如果两个表达式都不为真,则 其他 块进行评估。 这里是在行动:
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
该 埃尔塞夫 和 其他 块是可选的,并且尽可能多 埃尔塞夫 可以使用所需的块。 中的条件表达式 如果-埃尔塞夫-其他 构造被评估,直到第一个评估为 真的,之后计算关联的块,并且不计算进一步的条件表达式或块。
如果 块是"泄漏的",即它们不引入本地范围。 这意味着在 如果 子句可以在 如果 块,即使它们以前没有定义。 所以,我们可以定义 测试 函数以上为
julia> function test(x,y)
if x < y
relation = "less than"
elseif x == y
relation = "equal to"
else
relation = "greater than"
end
println("x is ", relation, " y.")
end
test (generic function with 1 method)
julia> test(2, 1)
x is greater than y.
变量 关系 在 如果 块,而是在外面使用。 但是,当依赖于此行为时,请确保所有可能的代码路径都为变量定义一个值。 对上述函数的以下更改会导致运行时错误
julia> function test(x,y)
if x < y
relation = "less than"
elseif x == y
relation = "equal to"
end
println("x is ", relation, " y.")
end
test (generic function with 1 method)
julia> test(1,2)
x is less than y.
julia> test(2,1)
ERROR: UndefVarError: `relation` not defined in local scope
Stacktrace:
[1] test(::Int64, ::Int64) at ./none:7
如果 块还返回一个值,对于来自许多其他语言的用户来说,这可能看起来不直观。 这个值只是选择的分支中最后一个执行语句的返回值,所以
julia> x = 3
3
julia> if x > 0
"positive!"
else
"negative..."
end
"positive!"
请注意,非常短的条件语句(单行语句)经常使用Julia中的短路评估来表达,如下一节所述。
与C,MATLAB,Perl,Python和Ruby不同-但与Java和其他一些更严格的类型化语言一样-如果条件表达式的值不是任何东西,这是一个错误 真的 或 错误:
julia> if 1
println("true")
end
ERROR: TypeError: non-boolean (Int64) used in boolean context
所谓"三元算子", ?:,与 如果-埃尔塞夫-其他 语法,但在需要在单个表达式值之间进行条件选择的情况下使用,而不是有条件地执行较长的代码块。 它的名字来自于在大多数语言中唯一一个使用三个操作数的运算符:
a ? b : c
表达式 a,在 ?,是条件表达式,三元运算求值表达式 b,在 :,如果条件 a 是 真的 或表达式 c,后 :,如果是 错误. 请注意,周围的空格 ? 和 : 是强制性的:一个表达式,如 a?b:c 不是一个有效的三元表达式(但在 ? 而 :).
理解这种行为的最简单方法是看一个例子。 在前面的例子中, 打印,打印 调用由所有三个分支共享:唯一真正的选择是打印哪个字面字符串。 这可以使用三元运算符更简洁地编写。 为了清楚起见,让我们先尝试一个双向版本:
julia> x = 1; y = 2;
julia> println(x < y ? "less than" : "not less than")
less than
julia> x = 1; y = 0;
julia> println(x < y ? "less than" : "not less than")
not less than
如果表达式 x<y 为true,整个三元运算符表达式计算为字符串 "小于" 否则它将计算为字符串 "不少于". 原始的三向示例需要将三元运算符的多个用法链接在一起:
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
julia> test(2, 1)
x is greater than y
julia> test(1, 1)
x is equal to y
为了方便链接,操作员从右到左关联。
这很重要,比如 如果-埃尔塞夫-其他,之前和之后的表达式 : 仅当条件表达式计算为 真的 或 错误,分别:
julia> v(x) = (println(x); x)
v (generic function with 1 method)
julia> 1 < 2 ? v("yes") : v(no)
yes
"yes"
julia> 1 > 2 ? v("yes") : v(no)
no
no
短路评估
该 && 和 || Julia中的运算符对应于逻辑"`和`"和"`或`"操作,分别和通常用于此目的。 但是,它们具有_short-circuit_evaluation的附加属性:它们不一定评估它们的第二个参数,如下所述。 (也有按位 & 和 | 可用作逻辑的运算符"`和`"和"`或`"_without_短路行为,但要注意 & 和 | 优先级高于 && 和 || 为评估订单。)
短路评估与条件评估非常相似。 这种行为可以在大多数命令式编程语言中找到,这些语言具有 && 和 || 布尔运算符:在由这些运算符连接的一系列布尔表达式中,只有最小数量的表达式被评估为确定整个链的最终布尔值所必需的。 有些语言(如Python)将它们称为 和 (&&)和 或 (||). 明确地说,这意味着:
*在表达式中 a&b,子表达式 b 仅在以下情况下进行评估 a 评估至 真的.
*在表达式中 a//b,子表达式 b 仅在以下情况下进行评估 a 评估至 错误.
理由是 a&b 必须是 错误 如果 a 是 错误,不论 b,同样, a//b 如果 a 是 真的,不论 b. 两者兼而有之 && 和 || 与右派联系,但 && 优先级高于 || 确实如此。 尝试这种行为很容易:
julia> t(x) = (println(x); true)
t (generic function with 1 method)
julia> f(x) = (println(x); false)
f (generic function with 1 method)
julia> t(1) && t(2)
1
2
true
julia> t(1) && f(2)
1
2
false
julia> f(1) && t(2)
1
false
julia> f(1) && f(2)
1
false
朱莉娅>t(1)//t(2)
1
真的
朱莉娅>t(1)//f(2)
1
真的
julia>f(1)//t(2)
1
2
真的
julia>f(1|//f(2)
1
2
错误
你可以很容易地以同样的方式与各种组合的结合性和优先级进行实验。 && 和 || 运营商。
这种行为经常在Julia中使用,以形成非常短的替代方案 如果 发言。 而不是 如果<cond><statement>结束,一个可以写 <cond>&&<语句> (可以读作:如果! ,一个可以写 (可以读作:
例如,递归阶乘例程可以这样定义:
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(0)
1
julia> fact(-1)
ERROR: n must be non-negative
Stacktrace:
[1] error at ./error.jl:33 [inlined]
[2] fact(::Int64) at ./none:2
[3] top-level scope
布尔运算_without_短路评估可以用在 数学运算和初等函数: & 和 |. 这些是正常的函数,恰好支持中缀运算符语法,但总是评估它们的参数:
julia> f(1) & t(2)
1
2
false
julia> t(1) | t(2)
1
2
true
就像在 如果, 埃尔塞夫 或三元运算符,操作数 && 或 || 必须是布尔值(真的 或 错误). 除了条件链中的最后一个条目之外,在任何地方使用非布尔值都是错误的:
julia> 1 && true
ERROR: TypeError: non-boolean (Int64) used in boolean context
另一方面,任何类型的表达式都可以在条件链的末端使用。 它将根据前面的条件进行评估和返回:
julia> true && (x = (1, 2, 3))
(1, 2, 3)
julia> false && (x = (1, 2, 3))
false
重复评估:循环
表达式的重复求值有两种构造: 而 循环和 为 循环。 下面是一个示例 而 循环:
julia> i = 1;
julia> while i <= 3
println(i)
global i += 1
end
1
2
3
该 而 循环计算条件表达式(i<=3 在这种情况下),并且只要它仍然存在 真的,也不断评估 而 循环。 如果条件表达式为 错误 当 而 循环首先达到,身体永远不会被评估。
该 为 循环使常见的重复评价成语更容易写。 由于像上面那样上下计数 而 循环是如此普遍,它可以用一个更简洁的表达 为 循环:
julia> for i = 1:3
println(i)
end
1
2
3
在这里 1:3 是一个 范围对象,表示数字1、2、3的序列。 该 为 循环遍历这些值,依次将每个值分配给变量 i. 一般来说, 为 construct可以循环遍历任何"可迭代"对象(或"容器"),从类似的范围 1:3 或 1:3:13 (一 步长,步长表示每个第3个整数1,4,7,。..,13)到更通用的容器,如数组,包括 由用户代码定义的迭代器或外部包。 对于范围以外的容器,alternative(但完全等价)关键字 在 或 ∈ 通常用于代替 =,因为它使代码阅读更清晰:
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
各种类型的可迭代容器将在手册的后面部分介绍和讨论(参见,例如, 多维数组)。
前的一个相当重要的区别 而 循环形式和 为 循环形式是变量可见的范围。 A 为 循环总是在其主体中引入一个新的迭代变量,而不管封闭作用域中是否存在同名变量。 这意味着一方面 i 不需要在循环之前声明。 另一方面,它在循环外不可见,同名的外部变量也不会受到影响。 您将需要一个新的交互式会话实例或一个不同的变量名称来测试这一点:
julia> for j = 1:3
println(j)
end
1
2
3
茱莉亚>j
错误:UndefVarError: `j` 未定义于 `主要`
julia> j = 0;
julia> for j = 1:3
println(j)
end
1
2
3
julia> j
0
使用方法 为外 以修改后一种行为并重新使用现有的局部变量。
见 外,外,以及它在朱莉娅的工作原理。
有时终止重复a是很方便的 而 在测试条件被伪造或停止迭代之前 为 在可迭代对象结束之前循环。 这可以通过 休息 关键字:
julia> i = 1;
julia> while true
println(i)
if i >= 3
break
end
global i += 1
end
1
2
3
julia> for j = 1:1000
println(j)
if j >= 3
break
end
end
1
2
3
没有 休息 关键字,以上 而 循环永远不会自行终止, 为 循环将迭代到1000。 这些循环都是通过使用提前退出 休息.
在其他情况下,能够停止迭代并立即进入下一个迭代是很方便的。 该 继续 关键字完成此:
julia> for i = 1:10
if i % 3 != 0
continue
end
println(i)
end
3
6
9
这是一个有点做作的例子,因为我们可以通过否定条件并放置 打印,打印 在 如果 块。 在现实的使用中,有更多的代码需要在 继续,并且经常有多个点从其中一个调用 继续.
多重嵌套 为 循环可以组合成一个外部循环,形成其迭代器的笛卡尔积:
julia> for i = 1:2, j = 3:4
println((i, j))
end
(1, 3)
(1, 4)
(2, 3)
(2, 4)
使用这种语法,iterables仍然可以引用外部循环变量;例如 对于i=1:n,j=1:i 是有效的。 然而一个 休息 这样一个循环内的语句会退出整个循环巢,而不仅仅是内部的。 两个变量(i 和 j)被设置为每次内循环运行时的当前迭代值。 因此,分配给 i 对后续迭代不可见:
julia> for i = 1:2, j = 3:4
println((i, j))
i = 0
end
(1, 3)
(1, 4)
(2, 3)
(2, 4)
如果这个例子被重写为使用 为 每个变量的关键字,那么输出将是不同的:第二个和第四个值将包含 0.
多个容器可以在同一时间在一个单一的迭代 为 循环使用 拉链:
julia> for (j, k) in zip([1 2 3], [4 5 6 7])
println((j,k))
end
(1, 4)
(2, 5)
(3, 6)
使用 拉链将创建一个迭代器,它是一个元组,包含传递给它的容器的子iterators。 该 拉链 迭代器将按顺序迭代所有子iterator,选择 在每个子单元的th元素 第三次迭代 为 循环。 一旦任何分给者用完, 为 循环将停止。
异常处理
发生意外情况时,函数可能无法向其调用方返回合理的值。 在这种情况下,最好的例外情况是在打印诊断错误消息时终止程序,或者如果程序员提供了处理这种例外情况的代码,则允许该代码采取适当的操作。
内置式 例外情况s
例外情况发生意外情况时会引发s。 内置的 例外情况下面列出的所有中断正常的控制流.
例外情况 |
|---|
|
|
|
julia> sqrt(-1)
ERROR: DomainError with -1.0:
sqrt was called with a negative real argument but will only return a complex result if called with a complex argument. Try sqrt(Complex(x)).
Stacktrace:
[...]
您可以通过以下方式定义自己的异常:
julia> struct MyCustomException <: Exception end
该 投掷功能
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:
[1] f(::Int64) at ./none:1
请注意 N.域名,域名没有括号不是异常,而是一种异常类型。 它需要被调用以获得 例外情况 对象:
julia> typeof(DomainError(nothing)) <: Exception
true
julia> typeof(DomainError) <: Exception
false
此外,某些异常类型采用一个或多个用于错误报告的参数:
julia> throw(UndefVarError(:x))
ERROR: UndefVarError: `x` not defined
这种机制可以通过以下方式的自定义异常类型轻松实现 不败者是写的:
julia> struct MyUndefVarError <: Exception
var::Symbol
end
julia> Base.showerror(io::IO, e::MyUndefVarError) = print(io, e.var, " not defined")
|
注意编写错误消息时,首选使第一个单词小写。 例如,
优先于
但是,有时保持大写的第一个字母是有意义的,例如,如果函数的参数是大写字母:
|
错误
该 错误函数用于产生一个 xref:base/base.adoc#Core.ErrorException[ErrorException异常 中断正常的控制流程。
假设我们要立即停止执行,如果取负数的平方根。 要做到这一点,我们可以定义一个挑剔的版本 sqrt,sqrt如果参数为负,则引发错误的函数:
julia> fussy_sqrt(x) = x >= 0 ? sqrt(x) : error("negative x not allowed")
fussy_sqrt (generic function with 1 method)
julia> fussy_sqrt(2)
1.4142135623730951
julia> fussy_sqrt(-1)
ERROR: negative x not allowed
Stacktrace:
[1] error at ./error.jl:33 [inlined]
[2] fussy_sqrt(::Int64) at ./none:1
[3] top-level scope
如果 fussy_sqrt 从另一个函数中以负值调用,而不是尝试继续执行调用函数,它立即返回,在交互式会话中显示错误消息:
julia> function verbose_fussy_sqrt(x)
println("before fussy_sqrt")
r = fussy_sqrt(x)
println("after fussy_sqrt")
return r
end
verbose_fussy_sqrt (generic function with 1 method)
julia> verbose_fussy_sqrt(2)
before fussy_sqrt
after fussy_sqrt
1.4142135623730951
julia> verbose_fussy_sqrt(-1)
before fussy_sqrt
ERROR: negative x not allowed
Stacktrace:
[1] error at ./error.jl:33 [inlined]
[2] fussy_sqrt at ./none:1 [inlined]
[3] verbose_fussy_sqrt(::Int64) at ./none:3
[4] top-level scope
该 尝试/捕捉 声明
该 尝试/捕捉 声明允许 例外情况这是要测试的,以及如何优雅地处理通常可能会破坏你的应用程序的东西。 例如,在下面的代码中,平方根函数通常会抛出异常。 通过放置一个 尝试/捕捉 封锁它,我们可以在这里减轻它。 您可以选择如何处理此异常,无论是记录它,返回一个占位符值,还是像下面我们刚刚打印出一个语句的情况一样。 在决定如何处理意外情况时要考虑的一件事是,使用 尝试/捕捉 块比使用条件分支来处理这些情况要慢得多。 下面有更多处理异常的例子 尝试/捕捉 座:
julia> try
sqrt("ten")
catch e
println("You should have entered a numeric value")
end
You should have entered a numeric value
尝试/捕捉 声明还允许 例外情况 要保存在变量中。 下面的人为示例计算的第二个元素的平方根 x 如果 x 是可索引的,否则假定 x 是一个实数,并返回其平方根:
julia> sqrt_second(x) = try
sqrt(x[2])
catch y
if isa(y, DomainError)
sqrt(complex(x[2], 0))
elseif isa(y, BoundsError)
sqrt(x)
end
end
sqrt_second (generic function with 1 method)
julia> sqrt_second([1 4])
2.0
julia> sqrt_second([1 -4])
0.0 + 2.0im
julia> sqrt_second(9)
3.0
julia> sqrt_second(-9)
ERROR: DomainError with -9.0:
sqrt was called with a negative real argument but will only return a complex result if called with a complex argument. Try sqrt(Complex(x)).
Stacktrace:
[...]
注意下面的符号 渔获 将始终被解释为异常的名称,因此在编写时需要小心 尝试/捕捉 单行上的表达式。 下面的代码将_not_工作返回值 x 在发生错误的情况下:
try bad() catch x end
相反,使用分号或在之后插入换行符 渔获:
try bad() catch; x end
try bad()
catch
x
end
其他 条款
|
兼容性
Julia1.8此功能至少需要Julia1.8。 |
在某些情况下,人们可能不仅希望适当地处理错误情况,而且还希望仅在 试试 块成功。 为此,一个 其他 子句可以在 渔获 以前没有抛出错误时运行的块。 将此代码包含在 试试 相反,块是任何进一步的错误都不会被 渔获 条款。
local x
try
x = read("file", String)
catch
# handle read errors
else
# do something with x
end
|
注意
使用[ |
最后 条款
在执行状态更改或使用文件等资源的代码中,通常需要在代码完成时完成清理工作(例如关闭文件)。 异常可能会使此任务复杂化,因为它们可能导致代码块在到达其正常结束之前退出。 该 最后 关键字提供了一种在给定代码块退出时运行某些代码的方法,而不管它如何退出。
例如,以下是我们如何保证打开的文件被关闭的方法:
f = open("file")
try
# operate on file f
finally
close(f)
end
当控制离开 试试 块(例如由于一 回来吧,或者只是正常完成), 关闭(f) 将被执行。 如果 试试 块由于异常而退出,异常将继续传播。 A 渔获 块可与 试试 和 最后 ""好吧。 在这种情况下, 最后 块将运行后 渔获 已经处理了错误。
任务(又名协程)
任务是一种控制流功能,允许以灵活的方式暂停和恢复计算。 我们在这里提到它们只是为了完整性;有关完整的讨论,请参阅 异步编程。