这是一个真正的初学者问题......
在for
循环中,何时评估循环参数?
在这里,循环会永远运行,所以显然c
正在检查'每次循环开始时:
c= [1]
for i in c
push!(c, i)
@show c
end
c = [1,1]
c = [1,1,1]
c = [1,1,1,1]
...
但是这个循环只评估一次:
c= [1]
for i in 1:length(c)
push!(c, i)
@show c
end
c = [1,1]
这个看起来像每个循环评估enumerate(c)
:
c= [1]
for (i, _) in enumerate(c)
push!(c, i)
@show c
end
c = [1,1]
c = [1,1,1]
c = [1,1,1,1]
...
但是这个循环显然不是:
c= [1]
for i in eachindex(c)
push!(c, i)
@show c
end
c = [1,1]
这样做:
c= [1]
foreach(a -> (push!(c, a); @show c), c)
c = [1,1]
c = [1,1,1]
c = [1,1,1,1]
...
正如我所说,这是一个真正的初学者问题。但我错过了一般模式吗?
答案 0 :(得分:6)
我认为主要的一点是你的各种循环在两种不同类型的对象上调用Julia的迭代器接口:
数组对象c
本身
AbstractUnitRange
个对象(或其子类型之一)
当您使用for i in c
循环时,Julia不知道c
有多大。所有Julia需要的是迭代的当前状态(它已达到的索引)以及c
中要访问的下一个索引应该是什么。如果下一个索引超出范围,它可以测试是否完成迭代c
。
从Julia的iterator interface文档中复制,这样的循环基本归结为:
state = start(c)
while !done(c, state)
(i, state) = next(c, state)
# body
end
如果您在循环体中附加c
,则始终会有下一个要访问的索引(即while !done(c, state)
始终为true
)。数组c
可以增长,直到你的记忆已满。
使用enumerate
和foreach
的循环都以相同的方式依赖于数组c
的迭代器接口,因此在修改c
期间会看到类似的行为这些循环。
另一方面,使用for i in 1:length(c)
和for i in eachindex(c)
的循环不会迭代c
本身,而是迭代支持迭代器接口的不同对象。
关键是这些对象是在迭代开始之前创建的,并且在修改循环体中的c
时不会受到影响。
在第一种情况下,计算length(c)
,然后将1:length(c)
创建为UnitRange
类型的对象。在您的示例中,它从1开始并在1处停止,因此我们在迭代期间仅push!
到c
一次。
在第二种情况下,调用eachindex([1])
会返回Base.OneTo
个对象;就像一个UnitRange
对象一样,除了保证从1开始。在你的示例中创建了Base.OneTo(1)
并从1开始并在1处停止。
这两个对象都是AbstractUnitRange
的子类型,最终是AbstractArray
的子类型。迭代器接口允许您按顺序访问这些对象持有的值。