ranges 是从不分配的“惰性”向量。它可能是最有用的迭代器之一。
julia> AbstractRange <: AbstractVector
true
julia> @allocated [1,2,3,4,5,6,7,8,9,10]
160
julia> @allocated 1:10
0
范围运算符: 用于创建范围:
julia> 1:10 |> dump
UnitRange{Int64}
start: Int64 1
stop: Int64 10
您已经知道如何使用 collect 将范围转换为向量,但如果您可以更深入地研究代码,您会发现 collect 实际上在后台调用了 vcat:
julia> @less collect(1:10)
collect(r::AbstractRange) = vcat(r)
这就是vcat 处理AbstractRange 输入的方式:
@less vcat(1:10)
function vcat(rs::AbstractRange{T}...) where T
n::Int = 0
for ra in rs
n += length(ra)
end
a = Vector{T}(undef, n)
i = 1
for ra in rs, x in ra
@inbounds a[i] = x
i += 1
end
return a
end
实现非常简单,只需循环输入(注意rs 是可变参数输入),并将输入范围一个接一个地连接到一个向量中。显然,即使只有一个输入范围([1:10;])也能正常工作。
还有另一种从范围创建向量的方法:直接调用Vector 构造函数Vector(1:10)。但是引擎盖下会发生什么?简单地调用@less Vector(1:10) 不会直接跳转到原始实现,这就是花哨的调试器的用武之地:
julia> using Debugger
julia> @enter Vector(1:10)
In Type(x) at boot.jl:424
>424 (::Type{Array{T,N} where T})(x::AbstractArray{S,N}) where {S,N} = Array{S,N}(x)
About to run: (Core.apply_type)(Array, Int64, 1)
1|debug> s
In Type(x) at boot.jl:424
>424 (::Type{Array{T,N} where T})(x::AbstractArray{S,N}) where {S,N} = Array{S,N}(x)
About to run: (Array{Int64,1})(1:10)
1|debug> s
[ Info: tracking Base
In Type(r) at range.jl:943
>943 Array{T,1}(r::AbstractRange{T}) where {T} = vcat(r)
About to run: (vcat)(1:10)
1|debug> s
In vcat(rs) at range.jl:930
>930 n::Int = 0
931 for ra in rs
932 n += length(ra)
933 end
934 a = Vector{T}(undef, n)
About to run: Core.NewvarNode(:(_5))
如您所见,Vector 也调用了vcat。
我认为这个示例已经为您提供了一些关于如何使用这些非常方便的内置反射工具以交互方式在 Julia REPL 中找到答案的想法。还有其他有用的工具,例如@code_lowered、@code_typed、@macroexpand 等,可以帮助您找出诸如“这个表达式有什么作用?”之类的问题,例如,
julia> f() = [1:10;]
f (generic function with 1 method)
julia> @code_lowered f()
CodeInfo(
1 ─ %1 = 1:10
│ %2 = (Base.vcat)(%1)
└── return %2
)
“降低”的代码告诉我们,Julia 首先创建一个范围%1 = 1:10,然后调用Base.vcat(%1),这正是文档所说的。
X-ref:What is the difference between @code_native, @code_typed and @code_llvm in Julia?