2026a

# Julia 语言概览

一份简单而粗略的语言概览

# 什么是Julia

Julia (opens new window) 是一种为科学计算而生的,开源、多平台、高性能的高级编程语言。

Julia 有一个基于 LLVM (opens new window)JIT (opens new window) 编译器,这让使用者无需编写底层的代码也能拥有像 C 与 FORTRAN 那样的性能。因为代码在运行中编译,你可以在 shell 或 REPL(Read-Eval-Print-Loop,即交互式命令行) (opens new window) 中运行代码,这也是一种推荐的工作流程 (opens new window)

Julia 是动态类型的。并且提供了为并行计算和分布式运算设计的多重派发 (opens new window)机制。

Julia 允许你通过类似 Lisp 的宏来自动生成代码。

Julia 诞生于 2012 年。

# 交互式命令行

上一次运算的结果 ans
中断命令执行 [Ctrl] + [C]
清屏 [Ctrl] + [L]
运行程序文件 include("filename.jl")
查找 func 相关的帮助 ?func
查找 func 的所有定义 apropos("func")
命令行模式 ;
包管理模式 ]
帮助模式 ?
查找特殊符号输入方式 ?☆ # "☆" can be typed by \bigwhitestar<tab>
退出特殊模式 返回到 REPL 在空行上按 [Backspace]
退出 REPL exit() 或 [Ctrl] + [D]

常用特殊符号:

# 在Julia命令行中输入"?☆",显示如下符号:  
!, %, &, ', *, +, -, /, :, <, >, \, ^, |, ~, ÷, π, ,,,,,, , ,,,,, ,,,  or 

# 基础语法

赋值语句 answer = 42
x, y, z = 1, [1:10; ], "A string"
x, y = y, x # 交换 x, y
常量定义 const DATE_OF_BIRTH = 2012
行尾注释 i = 1 # 这是一行注释
多行注释 #= 这是另一行注释 =#
链式操作 x = y = z = 1 # 从右向左
0 < x < 3 # true
5 < x != y < 5 # false
函数定义 function add_one(i)
    return i + 1
end
插入 LaTeX 符号 \delta + [Tab] # δ

# 运算符

基本算数运算 +-*/
幂运算 2^3 # 8
除法 3/12 # 0.25
反向除法 7\3 == 3/7 # true
整除 x ÷ y # 取 x/y 的整数部分
取余 x % yrem(x,y)
否定 !true # false
等于 a == b
-0.0 == 0.0 # true 而不是 isequal(-0.0, 0.0) # false
不等于 a != ba ≠ b
小于与大于 <>
小于等于 <=
大于等于 >=
短路与 x && y # 仅当x为true的时候才会执行y
短路或 x || y # 仅当x为false的时候才会执行y
按位取反 ~x
按位与 x & y
按位或 x | y
按位异或(逻辑异或) x ⊻ y 或 xor(x, y)
按位与(非与) x ⊼ y 或 nand(x, y)
按位或(非或) x ⊽ y 或 nor(x, y)
逻辑右移 x >>> y
算术右移 x >> y
逻辑/算术左移 x << y
逐元素运算(点运算) [1, 2, 3] .+ [1, 2, 3] == [2, 4, 6] # true
[1, 2, 3] .* [1, 2, 3] == [1, 4, 9] # true
检测无穷值 isinf(x)
检测有限值 isfinite(x)
检测非数值(NaN) isnan(NaN) # true 而不是 NaN == NaN # false
三元运算符 a == b ? "Equal" : "Not equal"
对象等价 a === b
赋值运算符 += -= *= /= \= ÷= %= ^= &= |= ⊻= >>>= >>= <<=

# 字符与字符串

字符 chr = 'C'
字符串 str = "A string"
字符 => 编码 Int('J') # 74
编码 => 字符 Char(74) # 'J'
任意的 UTF 字符 chr = '\uXXXX' # 4 位 HEX
chr = '\UXXXXXXXX' # 8 位 HEX
逐字符迭代 for c in str
    println(c)
end
字符串拼接 str = "Learn" * " " * "Julia"
字符插值 a = b = 2
println("a * b = $(a*b)")
字符串截取 str = "Hello, world"; str[begin:end-2] # "Hello, wor"
子串包含判断 occursin("ab", "aba") # true
occursin(r"a.a", "aba") # true
occursin(r"a.a", "abba") # false
occursin(r"a*a", "abba") # true
子串开始判断 startswith("abc", "ab") # true
子串结束判断 endswith("abc", "bc") # true
第一个匹配的子串或正则表达式 findfirst(isequal('i'), "Julia") # 4
替换字串或正则表达式 replace("Julia", "a" => "us") # "Julius"
replace("The quick foxes run quickly.", "quick" => "", count=1)
收集的最后一个索引值 lastindex("Hello") # 5
字符串的长度 length("Hello") # 5
正则表达式 pattern = r"l[aeiou]"
子字符串 str = "+1 234 567 890"
pat = r"\+([0-9]) ([0-9]+)"
m = match(pat, str)
m.captures # ["1", "234"]
所有匹配 [m.match for m = eachmatch(pat, str)]
所有匹配的迭代器 eachmatch(pat, str)
任意值转字符串 string("a", 1, true) # "a1true"
string([1,2,3]) # "[1, 2, 3]"
string(255, base=16, pad=4) # "00ff"
格式化输出 using Printf
str = (@sprintf "%s is %5.1f" "value" 34.567) # "value is 34.6"
str = (@sprintf "value is %.3e" 1.23456) # "value is 1.235e+00"
str = (@sprintf "value is %.2f" 1.23456) # "value is 1.23"
str = (@sprintf "value is %06i" 123) # "value is 000123"
str = (@sprintf "%g %g" 1.23 12300000.0) # "1.23 1.23e+07"
原始字符串 raw"C:\Users\TR" # 支持原始字符串
"C:\\Users\\TR" # windows 路径
"C:/Users/TR" # 适用于 windows 和 linux 系统
多行字符串 """ 这是一段多行字符串 """
"""Contains "quote" characters""" # "Contains \"quote\" characters"

要当心 UTF-8 中的多字节 Unicode 编码:

Unicode_string = "Ångström"
ncodeunits(Unicode_string) # 10,即UTF-8字符串的实际字节数
lastindex(Unicode_string) # 10
length(Unicode_string)    # 8

Unicode_string[10]  # 'm': ASCII/Unicode U+006d 
Unicode_string[9]   # ERROR: StringIndexError("Ångström", 9)
Unicode_string[8]   # 'ö': Unicode U+00f6

# 遍历UTF-8字符串
for c in Unicode_string
       print(c)
end
# 运行结果为:Ångström

# 遍历UTF-8字符串
for i in eachindex(Unicode_string)
      println("[$i]: ", Unicode_string[i])
end
#= 运行结果为:
[1]: Å
[3]: n
[4]: g
[5]: s
[6]: t
[7]: r
[8]: ö
[10]: m
=#

字符串是不可变的。

# 数值

整数类型 IntNUIntN, 且 N ∈ {8, 16, 32, 64, 128}, BigInt
浮点类型 FloatNN ∈ {16, 32, 64}
BigFloat
类型的最大和最小值 typemin(Int8)
typemax(Int64)
无穷大 Inf 或 Inf32 或 Inf16
负无穷大 -Inf 或 -Inf32 或 -Inf16
进制数 0b1000 # 二进制
0o10 # 八进制
0xff # 十六进制
Int(0xff) # 255
有理数 2//3 #
Float64(2//3) # 0.6666666666666666
复数类型 Complex{T<:Real}
Complex{Float64} 与 ComplexF64 等价
复数 2.0 + 3im
complex(2.0, 3) # 2.0 + 3.0im
im * im == -1 # true
复数实部 real(2.0+3im) # 2.0
复数虚部 imag(2.0+3im) # 3.0
复共轭 conj(1 + 2im) # 1 - 2im
复数的模 abs(1 + 2im) # 2.23606797749979
相位角 rad2deg(angle(1 + im)) # 45.0
机器精度 eps() # 等价于 eps(Float64)
整数转浮点 Float64(1) # 1.0
浮点转整数 floor(Int, 2.3) # 向下取整,返回2
ceil(Int, 2.3) # 向上取整,返回3
圆整 round() # 浮点数圆整
round(Int,2.5) # 整数圆整,返回2
round(Int, 2.5, RoundNearestTiesAway) # 与 matlab 等价,四舍五入,返回 3
类型转换 convert(TypeName, val) # 尝试进行转换/可能会报错
TypeName(val) # 调用类型构造器转换
Int(2.0) # 2
Int(2.5) # 报错
Int(round(2.5)) # 2
数值转字符串 string(0xff) # "255"
字符串转数值 parse(Int, "0b1000") # 8
parse(Int, "0o10") # 8
parse(Int, "0xff") # 255
parse(Int, "ff", base=16) # 255
parse(Float64, "0xff") # 255.0
近似判等 ≈ 或 isapprox(x, y)
isapprox(1e-10, 0, atol=1e-8) # true
isapprox(0.1, 0.15; rtol=0.34) # true
isapprox(0.1, 0.15; rtol=0.33) # false
全局常量 pi # 3.1415...
π # 3.1415...
ℯ # 2.7182818284590...
im # real(im * im) == -1
更多常量 using Base.MathConstants

Julia 不会自动检测数值溢出。 使用 SaferIntegers (opens new window) 包可以得到带溢出检查的整数。

# 随机数

函数库 using TyRandom
随机数流 using TyRandom
rng = MT19937ar(5489) # 梅森旋转算法,等同于 rng
rand(rng, 2,3) # 2x3 矩阵,与 MATLAB 的 rand(2,3) 结果一致
均匀分布的随机数 using TyRandom
rand(2,3) # 2x3 矩阵
r = a .+ (b-a)*rand(N) # 生成区间 (a,b) 内的 N 个随机数的公式
均匀分布的伪随机整数 using TyRandom
randi((-5,5), 10) # 生成区间 (-5, 5) 的 10 个整数
正态分布 using TyRandom
r = randn(5, 5) # 5x5 矩阵
整数的随机排列 using TyRandom
randperm(6) # 生成一个从 1 到 6 的整数的随机排列
随机重排 A 中的元素 shuffle(A)

# 数组

声明数组 arr = Float64[]
arr=[1,2,3] # 向量
arr=[1 2 3] # 1x3矩阵
arr=[1 2 3]' # 3x1转置矩阵
arr=[1 2 3; 4 5 6] # 2x3矩阵
预分配内存 sizehint!(arr, 10^4)
访问与赋值 arr = Any[1,2]
arr[1] = "Some text"
数组比较 a = [1:10;]
b = a # b 指向 a
a[1] = -99
a == b # true
复制元素(而不是地址)/深拷贝 b = copy(a)
b = deepcopy(a)
从 m 到 n 的子数组 arr[m:n]
n 个 0.0 填充的数组 zeros(3) # [0.0, 0.0, 0.0]
zeros(Int64,n) # [0, 0, 0]
zeros(3,3) # 3x3矩阵
n 个 1.0 填充的数组 ones(3) # [1.0, 1.0, 1.0]
ones(Int64,3) # [1, 1, 1]
ones(3,3) # 3x3矩阵
n 个 #undef 填充的数组 Vector{Type}(undef,n)
n 个从 start 到 stop 的等间距数 range(1, step=5, stop=100) # 1:5:496
range(1, step=5, length=3) # 1:5:11
用值 val 填充数组 fill(value, dims...) # 创建数组
fill!(arr, val) # 填充数组
弹出最后一个元素 pop!(arr)
弹出第一个元素 popfirst!(a)
将值 val 作为最后一个元素压入数组 push!(arr, val)
将值 val 作为第一个元素压入数组 pushfirst!(arr, val)
删除指定索引值的元素 deleteat!(arr, idx)
清空数组 empty!(collection)
数组排序 sort!(arr)
将 b 连接到 a 后 append!(a,b)
检查值 val 是否在数组 arr 中 in(val, arr)val in arr
改变维数 reshape(1:6, 2, 3) # 将向量改变为 2x3 矩阵
转化为字符串,并以 delim 分隔 join([1 2 3], ",") # "1,2,3"
使用范围函数来创建数组 [1:10...] # 10个元素的向量
collect(1:10) # 10个元素的向量
使用推导式和生成器创建数组 [n^2 for n in 1:10] # 10个元素的向量
数组元素类型 eltype(A)
数组长度 length(A)
数组维数 ndims(A)
数组大小 size(A) # 各个维度的元素数量
size(A,n) # 第n维的元素数量
高效迭代器 for i in eachindex(A) # linear indexing
     println("A[", i, "] == ", A[i])
end
水平连接 hcat(A...) 或 cat(A...; dims=2)
hcat([1,2], [3,4], [5,6]) # 2x3矩阵
垂直连接 vcat(A...) 或 cat(A...; dims=1)
vcat([1,2], [3,4]) # [1, 2, 3, 4]
vcat([10, 20, 30]', Float64[4 5 6; 7 8 9]) # 3x3数组

# 元组

Julia的元组与数组类似,都是有序的元素集合,不同之处在于元组不能修改。

元组使用小括号(...),数组使用方括号[...]。

创建元组 tupl=(5,10,15,20,25,30)
访问元素 tupl[3:end] # (15, 20, 25, 30)
命名元组 x = (a = 1, b = 2, c = 3)

t_names = (:a, :b, :c);
t_values = (1, 2, 3);
x = NamedTuple{t_names}(t_values) # 构造命名元组,与前面 x 等同
使用.访问元组 x.a # 返回 1
x[:a] # 返回1
使用 [ ] 访问元组 tupl[2] # 10
x[2] # 2
判断值是否在元组 10 in tupl # true
2 in x # true
元组转向量 collect(x) #[1,2]
向量转元组 Tuple(Real[1, 2, pi]) # (1, 2, π)
tuple(Real[1, 2, pi]...) # (1, 2, π)
遍历元组 for i in x
     println(i)
end
合并两个命名元组 merge((a=1, b=2, c=3), (b=4, d=5))
元组作为函数参数 function testFunc(x, y, z; a=10, b=20, c=30)
    println("x = $x, y = $y, z = $z; a = $a, b = $b, c = $c")
end
options = (b = 200, c = 300) # 创建元组
testFunc(1, 2, 3; options...) # 元组作为参数传入

# 线性代数

想要使用线性代数相关的工具,请用:using LinearAlgebra

单位矩阵 I # 直接用 I 就好。会自动转换到所需的维数。
定义矩阵 M = [1 0; 0 1]
矩阵维数 size(M)
选出第 i M[i, :]
选出第 j M[:, j]
水平拼接 M = [a b]M = hcat(a, b)
竖直拼接 M = [a ; b]M = vcat(a, b)
矩阵转置 transpose(M)
共轭转置 M'adjoint(M)
迹(trace) tr(M)
行列式 det(M)
秩(rank) rank(M)
特征值 eigvals(M)
特征向量 eigvecs(M)
矩阵求逆 inv(M)
解矩阵方程 M*x == v M\vinv(M)*v 更好 (opens new window)
求 Moore-Penrose 伪逆 pinv(M)

Julia 有内置的矩阵分解函数 (opens new window)

Julia 会试图推断矩阵是否为特殊矩阵(对称矩阵、厄米矩阵等),但有时会失败。 为了帮助 Julia 分派最优的算法,可以声明矩阵具有特殊的结构。如:对称矩阵、厄密矩阵(Hermitian)、上三角矩阵、下三角矩阵、对角矩阵等。

# 控制流与循环

条件语句 if-elseif-else-end
for 循环 for i in 1:10
    println(i)
end
嵌套循环 for i in 1:10, j = 1:5
    println(i*j)
end
枚举 for (idx, val) in enumerate(arr)
    println("the $idx-th element is $val")
end
while 循环 while bool_expr
    # 做点啥
end
退出循环 break
退出本次循环 continue
复合表达式 z = begin
    x = 1
    y = 2
    x + y
end

# 函数

函数的详细介绍,请参见 function

# 向量化

点运算符 [1,2,3] .^ 3 # [1, 8 ,27]
广播 a = [0 10 20 30]'
b = [1 2 3]
a .+ b # 返回 4x3 矩阵
broadcast(+,a,b) # 等价于 a .+ b
f.(args...) A = [1.0, 2.0, 3.0]
sin.(A) # 对 A 中每个元素执行 sin 运算
@. Y = [1.0, 2.0, 3.0, 4.0];
X = similar(Y); # 预分配
@. X = sin(cos(Y)) # 等价于 X .= sin.(cos.(Y))

# 字典与集合

字典是一种可变容器模型,且可存储任意类型对象。

创建字典 d = Dict(key1 => val1, key2 => val2, ...)
d = Dict(:key1 => val1, :key2 => val2, ...)
d = Dict{String, Float64}() # 创建空字典
d = Dict("A"=>1, "B"=>2)
所有的键 (迭代器) keys(d)
所有的值 (迭代器) values(d)
按键值对迭代 for (k,v) in d
    println("key: $k, value: $v")
end
是否存在键 k haskey(d, k)
in("A" => 1, d) # 返回true
将键/值复制到数组 arr = collect(keys(d))
arr = [k for (k,v) in d]
添加键/值对 d["C"]=3
删除键/值对 delete!(d, "C")
字典转向量 c = collect(d) # 返回Pair类型的数组
向量转字典 Dict(c) # 要求输入是Pair类型的数组
字典长度 length(d)
判断字典是否为空 isempty(d)
清空字典 empty!(d)
字典排序 d = Dict("R" => 100, "S" => 220, "T" => 350, "U" => 400, "V" => 575)
for k in sort(collect(keys(d)))
    println("$k => $(d[k])")
end

Set 集合是没有重复的对象数据集,所有的元素都是唯一的。

创建集合 s = Set([1, 2, 3])
并集 s1 ∪ s2 union(s1, s2)
交集 s1 ∩ s2 intersect(s1, s2)
补集 s1 ∖ s2 setdiff(s1, s2)
对称差 s1 △ s2 (symmetric difference) symdiff(s1, s2)
子集? s1 ⊆ s2 issubset(s1, s2)
判断元素是否存在集合中 in(2, s) # true
添加元素 push!(s, 4)
删除元素 delete!(s, 4)
遍历集合 for i in s
    println(i)
end
清空元素 empty!(s)

# 日期和时间

日期和时间的详细介绍,请参见日期和时间

# 高阶函数

在 julia 中,函数都是对象。函数对象可以作为参数传递给其他函数,我们将这些接收函数的函数,称为高阶函数。

列表推导 arr = [f(elem) for elem in coll]
[n^2 for n in 1:5] # [1, 4, 9, 16, 25]
将 f 应用到 coll 中的每一个元素上 map(f, coll)
map(x -> x * 2, [1, 2, 3]) # [2, 4, 6]
滤出 coll 中使 f 为真的每一个元素 filter(f, coll)
filter(isodd, 1:10) # [1, 3, 5, 7, 9]
累积计算函数,即将结果继续和序列的下一个元素做累积计算 reduce(*, [2; 3; 4]) # 24
reduce(*, [2; 3; 4]; init=-1) # -24

# 类型

关于类型的详细介绍,请参见类型系统

类型就像是没有方法的类。抽象类型可以拥有子类型,但不能被实例化。具体类型不能拥有子类型。

struct默认是不可变的。不可变类型能改善程序的性能,并且它们是线程安全的,它们在跨线程使用时不需要同步。

类型注释 var::TypeName
类型声明 struct Programmer
    name::String
    birth_year::UInt16
    fave_language::AbstractString
end
可变类型声明 struct 替换为 mutable struct
类型别名 const Nerd = Programmer
类型构造器 methods(TypeName)
类型实例 me = Programmer("Ian", 1984, "Julia")
me = Nerd("Ian", 1984, "Julia")
子类型声明 abstract type Bird end
struct Duck <: Bird
    pond::String
end
参数化类型 struct Point{T <: Real}
    x::T
    y::T
end
p = Point{Float64}(1,2)
联合类型 Union{Int, String}
遍历类型层级 supertype(TypeName) # 父类
subtypes(TypeName) # 子类
默认的超类型 Any
变量类型 M = [1 2; 3.5 4]
typeof(M) # Matrix{Float64} (alias for Array{Float64, 2})
数组元素类型 eltype(M) # Float64
类型判断 isa(1, Int) # true
isa(1.0, Float64) # true
实数判断 isreal(5) # true
isreal(5.0) # true
isreal(NaN) # true
isreal(Inf) # true
isreal(5.0 + 0im) # true
isreal(5.0 + 1im) # false

isreal(Any[1 2; 3 4]) # 若数组元素都是实数,返回 true
所有字段 struct PointF
     x::Float64
     y::Float64
end
fieldnames(PointF) # (:x, :y)
所有字段类型 PointF.types # svec(Float64, Float64)
PointF.types[1] # Float64

类型参数具有不变性(invariant),这意味着即使 Float64 <: RealPoint{Float64} <: Point{Real} 依旧为假。 与此不同的是,元组类型(Tuple)是协变的(covariant):Tuple{Float64} <: Tuple{Real}

通过code_typed()函数可以查看 Julia 代码经过类型推断后的内部表示形式(IR)。这个函数常用来确定本地代码中那些地方出现了Any类型而不是特定的类型。

# 缺失值与空值

空值(Null) nothing
缺失数据 missing
浮点数的非数值 NaN
滤除缺失值 collect(skipmissing([1, 2, missing])) # [1,2]
替换缺失值 replace([1,2,missing, missing], missing => 0) # [1, 2, 0, 0]
检查是否缺失值 ismissing(x) 而不是 x == missing
检查是否空值 isnan(NaN) # true 而不是 NaN == NaN # false

# 异常处理

抛出异常 SomeExcep throw(SomeExcep())
再次引发当前的异常 rethrow()
定义新异常 NewExcep struct NewExcep <: Exception
    v::String
end

Base.showerror(io::IO, e::NewExcep) = print(io, "A problem with $(e.v)!")
throw(NewExcep("x"))
断言 @assert iseven(3) "3 is an odd number!"
消息 @info "An informational message"
警告 @warn "Something was odd. You should pay attention"
错误 @error "A non fatal error occurred"
异常处理流程 try
     # 进行一些可能会失败的操作
catch ex
    if isa(ex, SomeExcep)
        # 处理异常 SomeExcep
    elseif isa(ex, AnotherExcep)
        # 处理另一个异常 AnotherExcep
    else
        # 处理其余的异常
    end
finally
    # 永远执行这些语句
end

# 模块

模块是独立的全局变量工作区,它们将类似的功能组合到一起。

定义 module NiceStuff
     export nice, DOG
     struct Dog end
     const DOG = Dog()
     nice(x) = "nice $x"
end
包含文件 filename.jl include("filename.jl")
加载 using ModuleName # 导入所有名称
using ModuleName: x, y # 仅导入 x, y
using ModuleName.x, ModuleName.y # 仅导入 x, y
import ModuleName # 仅导入 ModuleName
import ModuleName: x, y # 仅导入 x, y
import ModuleName.x, ModuleName.y # 仅导入 x, y
模块导出列表 # 得到模块导出名称的数组
names(ModuleName)

# 包含未导出的、弃用的和编译器产生的名称
names(ModuleName, all::Bool)

# 也显示从其他模块显式导入的名称
names(ModuleName, all::Bool, imported::Bool)
所属模块 parentmodule(sin) # Base
用 as 来重命名 import BenchmarkTools as BT
import CSV: read as rd
裸模块 # 模块自动包含 using Core、using Base 以及 eval 和 include 函数的定义。如果不需要这些默认定义,可以使用 baremodule 来定义模块(注意:Core 仍然是导入的)
baremodule Mod
     #...
end
标准模块 Core包含了语言内置的所有功能。
Base包含了绝大多数情况下都会用到的基本功能。
Main是顶层模块,当 julia 启动时,也是当前模块。

usingimport 的一点区别:

使用 using 时,你需要写 function Foo.bar(...) 来给 Foo模块的函数 bar 增添一个新方法; 而使用 import Foo.bar 时,只需写 function bar(...) 就能达到同样的效果。

# 命名空间管理

命名空间管理用于使模块中的名称在其他模块中可用。我们在下面详细讨论相关的概念和功能。

# 合格的名称

全局作用域内的函数、变量和类型的名称,如sinARGSUnitRange始终属于一个模块,称为母模块,例如,可以用parentmodule来找到该模块。

parentmodule(UnitRange) # 返回 Base

也可以通过在它们的模块前面加上前缀来引用它们的父模块之外的这些名称,例如Base.UnitRange。这称为限定名称。父模块可以使用像Base.Math.sin这样的子模块链来访问,其中Base.Math被称为模块路径。由于句法歧义,限定只包含符号的名称,例如运算符,需要插入冒号,例如Base.:+。少数运算符还需要括号,例如Base.:(==)

# 导出列表

名称(指函数、类型、全局变量和常量)可以通过export添加到模块的导出列表。通常,它们位于或靠近模块定义的顶部,以便源代码的读者可以轻松找到它们,如:

module NiceStuff

export nice, DOG

struct Dog end      # singleton type, not exported
const DOG = Dog()   # named instance, exported

nice(x) = "nice $x" # function, exported

end

但这只是一个风格建议——一个模块可以在任意位置有多个export语句。

导出构成 API(应用程序接口)一部分的名称是很常见的。在上面的代码中,导出列表建议用户应该使用niceDOG。然而,由于限定名称总是使标识符可访问,这只是组织 API 的一个选项:与其他语言不同,Julia 没有真正隐藏模块内部的功能。

此外,某些模块根本不导出名称。这通常是因为他们的 API 中使用常用词,这很容易与其他模块的导出列表发生冲突。

# 单独使用 using 和 import

加载模块最常见的方式可能是using ModuleName

严格来说,声明using ModuleName意味着一个名为ModuleName的模块可用于根据需要解析名称。当遇到当前模块中没有定义的全局变量时,系统会在ModuleName导出的变量中查找,找到就使用。这意味着当前模块中该全局变量的所有使用都将解析为ModuleName中该变量的定义。

继续我们的例子,

using NiceStuff

将加载上面的代码,使NiceStuff(模块名称)、DOGnice可用。Dog不在导出列表中,但如果名称被模块路径(这里只是模块名称)限定为NiceStuff.Dog,则可以访问它。

重要的是,导出列表只在using ModuleName 的形式下起作用。

相反,

import NiceStuff

将模块名称带入作用域。 用户需要使用NiceStuff.DOGNiceStuff.DogNiceStuff.nice来访问其内容。通常,当用户想要保持命名空间干净时,在上下文中使用import ModuleName。正如我们将在下一节中看到的,import NiceStuff等同于using NiceStuff: NiceStuff

你可以用逗号分隔符来组合相同类型的多个usingimport语句,例如:

using LinearAlgebra, Statistics

# 具有特定标识符的 using 和 import

using ModuleName:import ModuleName:后跟以逗号分隔的名称列表时,模块会被加载,但 只有那些特定的名称才会被语句带入命名空间。 例如,

using NiceStuff: nice, DOG

将导入名称niceDOG

重要的是,模块名称NiceStuff 不会出现在命名空间中。如果要使其可访问,则必须明确列出它,如

using NiceStuff: nice, DOG, NiceStuff

Julia 有两种形式来表示似乎相同的内容,因为只有import ModuleName:f允许在 没有模块路径的情况下向f添加方法。也就是说,以下示例将给出一个错误:

using NiceStuff: nice
struct Cat end
nice(::Cat) = "nice 😸" # 报错

此错误可防止意外将方法添加到你仅打算使用的其他模块中的函数。

有两种方法可以解决这个问题。你始终可以使用模块路径限定函数名称:

using NiceStuff
struct Cat end
NiceStuff.nice(::Cat) = "nice 😸"

或者,你可以import特定的函数名称:

import NiceStuff: nice
struct Cat end
nice(::Cat) = "nice 😸"

# 用 as 来重命名

importusing引入作用域的标识符可以用关键字as重命名。这对于解决名称冲突以及缩短名称很有用。 例如,Base导出函数名read,但 CSV.jl 包也提供了CSV.read。如果我们要多次调用 CSV 读取,删除 CSV.限定符会很方便。但是,我们指的是Base.read还是CSV.read是模棱两可的:

read; # Base.read

import CSV: read
# WARNING: ignoring conflicting import of CSV.read into Main

重命名提供了一个解决方案:

import CSV: read as rd

导入的包本身也可以重命名:

import BenchmarkTools as BT

as仅在将单个标识符引入作用域时才与using一起使用。例如,using CSV: read as rd有效,但using CSV as C无效,因为它对CSV中的所有导出名称进行操作。

# 处理名称冲突

考虑两个(或更多)包导出相同名称的情况,如:

module A
export f
f() = 1
end

module B
export f
f() = 2
end

using A, B语句有效,但是当你尝试调用f时,你会收到警告:

WARNING: both B and A export "f"; uses of it in module Main must be qualified
ERROR: LoadError: UndefVarError: f not defined

在这里,Julia无法确定您指的是哪个f,因此你必须做出选择。常用的解决方法有以下几种:

(1)只需继续使用限定名称,如A.fB.f。这使代码的读者可以清楚地了解上下文,特别是如果f恰好重合但在不同的包中具有不同的含义;

(2)使用上面的as关键字重命名一个或两个标识符,例如:

using A: f as f
using B: f as g

会使B.f可用作g。在这里,我们假设您之前没有使用using A,这会把 f 代入命名空间;

(3)当问题中的多个名称确实有相同的含义时,通常一个模块会从另一个模块导入它,或者有一个轻量级的“基础”包,它的唯一功能是定义这样的接口,可以被其他包使用。按照惯例,这些包名以...Base结尾(这与 Julia 的Base模块无关)。

# 子模块和相对路径

模块可以包含 子模块,嵌套相同的语法module ... end。它们可用于引入单独的命名空间,这有助于组织复杂的代码库。请注意,每个module都引入了自己的 作用域,因此子模块不会自动从其父模块“继承”名称。

建议子模块在usingimport语句中使用 相对模块限定符 来引用封闭父模块中的其他模块(包括后者)。 相对模块限定符以句点 (.) 开头,它对应于当前模块,每个连续的.都指向当前模块的父级。 如有必要,这应该跟在模块之后,最后是要访问的实际名称,所有名称都以.分隔。

考虑以下示例,其中子模块SubA定义了一个函数,然后在其兄弟模块中进行扩展:

module ParentModule
module SubA
    export add_D
    const D = 3
    add_D(x) = x + D
end

using .SubA  # brings `add_D` into the namespace

export add_D # export it from ParentModule too

module SubB
    import ..SubA: add_D # 兄弟模块的相对路径
    struct Infinity end
    add_D(x::Infinity) = x
end
end

请注意,如果你正在评估值,定义的顺序也很重要。 例如:

module TestPackage
export x, y

x = 0

module Sub
    using ..TestPackage
    z = y # ERROR: UndefVarError: y not defined
end

y = 1
end

# 表达式

Julia 具有同像性:程序被表示为语言本身的数据结构。 实际上 Julia 语言里的任何东西都是一个表达式Expr

符号 (Symbols) 即驻留字符串 (opens new window),以冒号:为前缀。 相对于其他类型来说,符号效率更高。它也经常用作标识符、字典的键或者数据表里的列名。 符号不能进行拼接。

使用引用:( ... )或块引用quote ... end可以创建一个表达式,就像 parse(str) (opens new window),和Expr(:call, ...)

x = 1
line = "1 + $x"         # 一些代码
expr = Meta.parse(line) # 生成一个 Expr 对象
typeof(expr) == Expr    # true
dump(expr)              # 打印生成抽象语法(AST)
eval(expr) == 2         # 对 Expr 对象求值: true

#

宏允许你在程序中自动生成代码(如:表达式)。

在 julia 终端中输入@,再连续按两下 Tab 键,可以显示所有内置的宏。

在 julia 终端中输入?,再输入宏名,可以查看该宏的相关帮助。

定义 macro macroname(expr)
     # 做点啥
end
使用 macroname(ex1, ex2, ...)@macroname ex1, ex2, ...
性能分析的宏 @time # 运行时间与内存分配统计
@elapsed # 返回执行用时
@allocated # 查看内存分配

using BenchmarkTools
@btime sin(pi) # 在基准测试期间测量的最小运行时间

@profview myfunc() # 性能分析并显示火焰图

using Profile
@profile myfunc() # 性能分析
Profile.print() # 显示结果
类型推断的宏 @code_warntype(sin(pi)) # 查看类型稳定
@code_typed(sin(pi)) #查看类型推断
@code_llvm(sin(pi)) # 查看LLVM IR
@code_native(sin(pi)) # 查看汇编代码
测试相关的宏 using Test
@test 1+1 == 2 # 精确相等
@test x ≈ y # 近似相等 isapprox(x, y)

@testset "math" begin # 测试分组
     @test 1 + 1 == 2
     @test 1 - 1 == 0
end
报错相关的宏 @debug "Verbose debugging information. Invisible by default"
@info "An informational message"
@warn "Something was odd. You should pay attention"
@error "A non fatal error occurred"
打印相关的宏 x=1;y=2;
@show x y; # 查看表达式
@printf "Decimal two digits %.2f" 1.23456 # 字符串格式化
路径相关的宏 @__DIR__ # 当前文件所属文件夹路径
@__FILE__ # 当前文件名
@__LINE__ # 当前所在行号
@__MODULE__ # 当前顶层模块
其他内置宏 @async # 异步任务
@which # 查看对特定参数使用的方法/查找函数所在的模块

创建 卫生宏 (hygienic macros)的规则:

  • 在宏的内部只通过 local 声明本地变量。
  • 在宏的内部不使用 eval
  • 转义插值表达式以避免宏变大:$(esc(expr))

# 文件读写

关于文件系统的详细介绍及相关示例,请参见文件系统

关于文件读写的详细介绍及相关示例,请参见文件 I/O

读取流 stream = stdin
for line in eachline(stream)
     # 做点啥
end
读取文件 open(filename) do file
    for line in eachline(file)
        # 做点啥
    end
end
读取 CSV 文件 using CSV
data = CSV.File(filename)
写入 CSV 文件 using CSV
CSV.write(filename, data)
保存 Julia 对象 using JLD
save(filename, "object_key", object, ...)
读取 Julia 对象 using JLD
d = load(filename) # 返回对象的字典
保存 HDF5 using HDF5
h5write(filename, "key", object)
读取 HDF5 using HDF5
h5read(filename, "key")

# 自我检查与反射

类型 typeof(name)
类型检查 isa(name, TypeName)
列出子类型 subtypes(TypeName)
列出超类型 supertype(TypeName)
函数方法 methods(func)
即时编译的字节码 code_llvm(expr)
汇编代码 code_native(expr)

# 赋值与拷贝

赋值与拷贝的详细介绍,请参见赋值与拷贝

# 变量作用域

变量作用域的详细介绍,请参见变量作用域

# 运行外部程序

运行外部程序的详细介绍,请参见运行外部程序

# 常用易混淆函数

常用易混淆函数的详细介绍,请参见常用易混淆函数

# 命名规范

  • Julia 代码风格主要的约定是:尽量避免使用下划线,除非不用就难于理解。
  • 变量名小写或使用蛇形命名(snake_case):somevariable
  • 常数全部大写:SOMECONSTANT
  • 函数名小写或使用蛇形命名(snake_case):somefunction
  • 宏小写或使用蛇形命名(snake_case):@somemacro
  • 类型名用首字母大写的驼峰命名:SomeType
  • Julia 代码文件以 .jl 为后缀。

更详细的代码风格规范请参阅手册:代码风格指南 (opens new window)

# 其他建议

  • 编写 类型稳定 (opens new window) 的代码
  • 尽可能使用不可变类型
  • 大数组用 sizehint! 预分配内存
  • arr = nothing 释放大数组的内存
  • 使用列访问数组,因为多维数组总是以列优先的顺序储存
  • 预分配储存结果用的数据结构
  • 在实时应用中使用 disable_gc() 关闭垃圾收集器
  • 避免使用关键字参数的 splat 操作符(...)
  • 使用会改变参数的 APIs 以避免复制数据结构。(例如:以 ! 结尾的函数)
  • 使用逐元素的数组操作,而不是列表推断(list comprehensions)
  • 避免在计算密集的循环中使用 try-catch
  • 避免在收集(collections)中出现 Any
  • 避免在收集(collections)中使用抽象类型
  • 避免在 I/O 中使用字符串插值
  • 不像 R, MATLAB 或 Python,在 Julia 中向量化 (opens new window) 并不会提升运行速度
  • 避免在运行时使用 eval