与其他语言的显著区别
与 MATLAB 的显著区别
虽然 MATLAB 用户可能觉得 Julia 的语法很熟悉,但 Julia 不是 MATLAB 的克隆。在语法和功能上存在重大差异。以下是可能会让习惯使用 MATLAB 的 Julia 用户感到困惑的一些显著区别。
- Julia 数组使用方括号索引,例如
A[i,j]
。 - Julia 数组在赋值给另一个变量时不会被复制。在
A = B
之后,更改B
的元素也会修改A
。若要避免这种情况,请使用A = copy(B)
。 - 在传递给函数时,Julia 值不会被复制。如果函数修改了数组,则更改将在调用者中可见。
- Julia 不会在赋值语句中自动扩展数组。在 MATLAB 中,
a(4) = 3.2
可以创建数组a = [0 0 0 3.2]
,而a(5) = 7
可以将其扩展为a = [0 0 0 3.2 7]
,但在 Julia 中,相应的语句a[5] = 7
会抛出错误,除非a
的长度小于 5,或者该语句是标识符a
的第一次使用。Julia 有push!
和append!
,它们比 MATLAB 的a(end+1) = val
更高效地扩展Vector
。 - 在 Julia 中,虚数单位
sqrt(-1)
表示为im
,而不是 MATLAB 中的i
或j
。 - 在 Julia 中,没有小数点的数字常量(例如
42
)创建整数而不是浮点数。因此,某些运算可能会抛出域错误,如果它们期望的是浮点数;例如,julia> a = -1; 2^a
会抛出域错误,因为结果不是整数(有关详细信息,请参阅 有关域错误的常见问题解答条目)。 - 在 Julia 中,多个返回值作为元组返回并赋值,例如
(a, b) = (1, 2)
或a, b = 1, 2
。MATLAB 的nargout
通常用于根据返回值的数量执行可选的操作,在 Julia 中不存在。相反,用户可以使用可选参数和关键字参数来实现类似的功能。 - Julia 有真正的单维数组。列向量的大小为
N
,而不是Nx1
。例如,rand(N)
创建一个一维数组。 - 在 Julia 中,
[x,y,z]
将始终构造一个包含x
、y
和z
的 3 个元素的数组。- 若要在第一个(“垂直”)维度进行连接,请使用
vcat(x,y,z)
或者用分号隔开([x; y; z]
)。 - 若要在第二个(“水平”)维度进行连接,请使用
hcat(x,y,z)
或者用空格隔开([x y z]
)。 - 若要构造块矩阵(在第一个两个维度上连接),请使用
hvcat
或者将空格和分号组合起来([a b; c d]
)。
- 若要在第一个(“垂直”)维度进行连接,请使用
- 在 Julia 中,
a:b
和a:b:c
构造AbstractRange
对象。若要构造一个类似 MATLAB 的完整向量,请使用collect(a:b)
。通常情况下,不需要调用collect
。AbstractRange
对象在大多数情况下可以像普通数组一样使用,但效率更高,因为它延迟计算其值。这种创建专门对象而不是完整数组的模式在 Julia 中很常见,在range
等函数中,或者在enumerate
和zip
等迭代器中都可以看到。这些特殊对象在大多数情况下可以像普通数组一样使用。 - Julia 中的函数从它们的最后一个表达式或
return
关键字返回结果,而不是在函数定义中列出要返回的变量的名称(有关详细信息,请参阅 return 关键字)。 - Julia 脚本可以包含任意数量的函数,当加载文件时,所有定义都将外部可见。函数定义可以从当前工作目录之外的文件加载。
- 在 Julia 中,当使用单个参数调用
sum
、prod
和max
等约简函数时,它们将在数组的每个元素上执行,即使A
的维度超过一个。 - 在 Julia 中,必须使用圆括号来调用不带参数的函数,例如
rand()
。 - Julia 不鼓励使用分号来结束语句。语句的结果不会自动打印(除了在交互式提示符下),并且代码行不需要以分号结束。可以使用
println
或@printf
来打印特定输出。 - 在 Julia 中,如果
A
和B
是数组,则逻辑比较运算(如A == B
)不会返回布尔值数组。相反,请使用A .== B
,类似地,对于其他布尔运算符,如<
、>
。 - 在 Julia 中,运算符
&
、|
和⊻
(xor
) 执行与 MATLAB 中的and
、or
和xor
等效的按位运算,并且它们的优先级与 Python 的按位运算符类似(与 C 不同)。它们可以作用于标量或数组中的每个元素,并且可以用于组合逻辑数组,但请注意操作顺序的差异:可能需要使用圆括号(例如,若要选择等于 1 或 2 的A
的元素,请使用(A .== 1) .| (A .== 2)
)。 - 在 Julia 中,可以使用 splat 运算符
...
将集合的元素作为参数传递给函数,例如xs=[1,2]; f(xs...)
。 - Julia 的
svd
将奇异值作为向量返回,而不是作为密集对角矩阵返回。 - 在 Julia 中,
...
不用于继续代码行。相反,不完整的表达式会自动继续到下一行。 - 在 Julia 和 MATLAB 中,变量
ans
被设置为交互式会话中最后发出的表达式的值。在 Julia 中,与 MATLAB 不同的是,当在非交互式模式下运行 Julia 代码时,不会设置ans
。 - Julia 的
struct
不支持在运行时动态添加字段,与 MATLAB 的class
不同。相反,请使用Dict
。Julia 中的 Dict 是无序的。 - 在 Julia 中,每个模块都有自己的全局作用域/命名空间,而在 MATLAB 中只有一个全局作用域。
- 在 MATLAB 中,删除不需要的值的惯用方法是使用逻辑索引,例如表达式
x(x>3)
,或者语句x(x>3) = []
来就地修改x
。相反,Julia 提供了更高阶函数filter
和filter!
,允许用户编写filter(z->z>3, x)
和filter!(z->z>3, x)
作为相应翻译x[x.>3]
和x = x[x.>3]
的替代方案。使用filter!
可以减少临时数组的使用。 - 在 Julia 中,提取(或“解除引用”)元胞数组所有元素的类似操作,例如在 MATLAB 中的
vertcat(A{:})
,使用 Julia 的 splat 操作符编写,例如vcat(A...)
。 - 在 Julia 中,
adjoint
函数执行共轭转置;在 MATLAB 中,adjoint
提供“伴随矩阵”或经典伴随矩阵,它是伴随矩阵的转置。 - 在 Julia 中,a^b^c 计算为 a^(b^c),而在 MATLAB 中计算为 (a^b)^c。
与 R 的显著差异
Julia 的目标之一是为数据分析和统计编程提供一种有效的语言。对于从 R 转向 Julia 的用户,以下是一些显著的差异。
Julia 的单引号包含字符,而不是字符串。
Julia 可以通过对字符串进行索引来创建子字符串。在 R 中,字符串必须转换为字符向量才能创建子字符串。
在 Julia 中,与 Python 相似,但与 R 不同,字符串可以使用三引号
""" ... """
创建。此语法便于构建包含换行的字符串。在 Julia 中,可变参数使用 splat 操作符
...
指定,该操作符始终位于特定变量的名称之后,这与 R 不同,在 R 中...
可以单独出现。在 Julia 中,模运算符为
mod(a, b)
,而不是a %% b
。Julia 中的%
是余数运算符。Julia 使用方括号构建向量。Julia 的
[1, 2, 3]
等同于 R 的c(1, 2, 3)
。在 Julia 中,并非所有数据结构都支持逻辑索引。此外,Julia 中的逻辑索引仅支持长度等于被索引对象的向量。例如
- 在 R 中,
c(1, 2, 3, 4)[c(TRUE, FALSE)]
等同于c(1, 3)
。 - 在 R 中,
c(1, 2, 3, 4)[c(TRUE, FALSE, TRUE, FALSE)]
等同于c(1, 3)
。 - 在 Julia 中,
[1, 2, 3, 4][[true, false]]
会抛出BoundsError
错误。 - 在 Julia 中,
[1, 2, 3, 4][[true, false, true, false]]
会生成[1, 3]
。
- 在 R 中,
与许多语言一样,Julia 并不总是允许对不同长度的向量进行运算,而在 R 中,向量只需要共享一个公共索引范围。例如,
c(1, 2, 3, 4) + c(1, 2)
在 R 中是有效的,但在 Julia 中,等效的[1, 2, 3, 4] + [1, 2]
会抛出错误。当逗号不会改变代码含义时,Julia 允许在末尾添加可选的逗号。当对数组进行索引时,这可能会让 R 用户感到困惑。例如,在 R 中,
x[1,]
会返回矩阵的第一行;但在 Julia 中,逗号会被忽略,所以x[1,] == x[1]
,并且会返回第一个元素。要提取一行,请确保使用:
,例如x[1,:]
。Julia 的
map
函数首先接受函数,然后接受其参数,这与 R 中的lapply(<structure>, function, ...)
不同。类似地,Julia 中等同于 R 中apply(X, MARGIN, FUN, ...)
的函数是mapslices
,其中函数是第一个参数。R 中的多变量 apply 函数,例如
mapply(choose, 11:13, 1:3)
,可以在 Julia 中写成broadcast(binomial, 11:13, 1:3)
。等效地,Julia 提供了一个更短的点语法来向量化函数binomial.(11:13, 1:3)
。Julia 使用
end
表示条件块(如if
)、循环块(如while
/for
)和函数的结尾。作为if ( cond ) statement
的单行形式,Julia 允许使用if cond; statement; end
、cond && statement
和!cond || statement
形式的语句。在后两种语法中,赋值语句必须显式地用括号括起来,例如cond && (x = value)
。在 Julia 中,
<-
、<<-
和->
不是赋值运算符。Julia 的
->
创建一个匿名函数。Julia 的
*
运算符可以执行矩阵乘法,这与 R 不同。如果A
和B
是矩阵,那么A * B
在 Julia 中表示矩阵乘法,等同于 R 中的A %*% B
。在 R 中,相同的符号将执行逐元素(Hadamard)乘法。要获得逐元素乘法运算,需要在 Julia 中编写A .* B
。Julia 使用
transpose
函数执行矩阵转置,使用'
运算符或adjoint
函数执行共轭转置。因此,Julia 的transpose(A)
等同于 R 的t(A)
。此外,Julia 还提供了一个非递归转置函数permutedims
。Julia 在编写
if
语句或for
/while
循环时不需要括号:使用for i in [1, 2, 3]
代替for (i in c(1, 2, 3))
,使用if i == 1
代替if (i == 1)
。Julia 不会将数字
0
和1
视为布尔值。您不能在 Julia 中编写if (1)
,因为if
语句只接受布尔值。相反,您可以编写if true
、if Bool(1)
或if 1==1
。Julia 没有提供
nrow
和ncol
。相反,使用size(M, 1)
代替nrow(M)
,使用size(M, 2)
代替ncol(M)
。Julia 谨慎地区分标量、向量和矩阵。在 R 中,
1
和c(1)
是相同的。在 Julia 中,它们不能互换使用。Julia 不允许在赋值操作的左侧对函数调用的结果进行赋值:您不能编写
diag(M) = fill(1, n)
。Julia 不鼓励在主命名空间中填充函数。Julia 的大多数统计功能都在 包 中,位于 JuliaStats 组织 下。例如
- 与概率分布相关的函数由 Distributions 包 提供。
- DataFrames 包 提供数据帧。
- GLM 包 提供广义线性模型。
Julia 提供元组和真正的哈希表,但不提供 R 风格的列表。当返回多个项目时,您通常应该使用元组或命名元组:代替
list(a = 1, b = 2)
,使用(1, 2)
或(a=1, b=2)
。Julia 鼓励用户编写自己的类型,这些类型比 R 中的 S3 或 S4 对象更易于使用。Julia 的多重调度系统意味着
table(x::TypeA)
和table(x::TypeB)
的行为类似于 R 中的table.TypeA(x)
和table.TypeB(x)
。在 Julia 中,当赋值或传递给函数时,不会复制值。如果函数修改了数组,则更改将在调用者中可见。这与 R 非常不同,并且允许新函数更有效地操作大型数据结构。
在 Julia 中,向量和矩阵使用
hcat
、vcat
和hvcat
进行连接,而不是像 R 中一样使用c
、rbind
和cbind
。在 Julia 中,
a:b
之类的范围不是 R 中向量类型的简写,而是一个专门的AbstractRange
对象,用于迭代。要将范围转换为向量,请使用collect(a:b)
。:
运算符在 R 和 Julia 中具有不同的优先级。特别是在 Julia 中,算术运算符的优先级高于:
运算符,而在 R 中则相反。例如,Julia 中的1:n-1
等同于 R 中的1:(n-1)
。Julia 的
max
和min
分别等同于 R 中的pmax
和pmin
,但两个参数的维数必须相同。虽然maximum
和minimum
取代了 R 中的max
和min
,但它们之间存在重要差异。Julia 的
sum
、prod
、maximum
和minimum
与 R 中的对应函数不同。它们都接受一个可选的关键字参数dims
,该参数指示执行运算的维度。例如,假设在 Julia 中A = [1 2; 3 4]
,在 R 中B <- rbind(c(1,2),c(3,4))
是相同的矩阵。那么sum(A)
给出与sum(B)
相同的结果,但sum(A, dims=1)
是一个行向量,包含每列的总和,sum(A, dims=2)
是一个列向量,包含每行的总和。这与 R 的行为形成对比,在 R 中,单独的colSums(B)
和rowSums(B)
函数提供了这些功能。如果dims
关键字参数是一个向量,那么它将指定执行总和的所有维度,同时保留总和数组的维度,例如sum(A, dims=(1,2)) == hcat(10)
。需要注意的是,第二个参数没有错误检查。在 R 中,性能需要向量化。在 Julia 中,几乎相反:最高效的代码通常是通过使用非向量化的循环实现的。
Julia 是急切求值的,不支持 R 风格的惰性求值。对于大多数用户来说,这意味着很少有未引用的表达式或列名。
Julia 不支持
NULL
类型。最接近的等效类型是nothing
,但它的行为类似于标量值,而不是像列表。使用x === nothing
代替is.null(x)
。在 Julia 中,缺失值由
missing
对象表示,而不是由NA
表示。使用ismissing(x)
(或对向量进行逐元素运算的ismissing.(x)
)代替is.na(x)
。skipmissing
函数通常用于代替na.rm=TRUE
(尽管在某些特定情况下,函数会接受skipmissing
参数)。Julia 缺少等效于 R 中的
assign
或get
的函数。在 Julia 中,
return
不需要括号。在 R 中,一种常用的删除不需要的值的方法是使用逻辑索引,例如在表达式
x[x>3]
中,或在语句x = x[x>3]
中,以原地修改x
。相反,Julia 提供了高阶函数filter
和filter!
,允许用户编写filter(z->z>3, x)
和filter!(z->z>3, x)
作为对相应翻译x[x.>3]
和x = x[x.>3]
的替代方案。使用filter!
减少了临时数组的使用。
与 Python 的显著差异
- Julia 中的
for
、if
、while
等代码块使用end
关键字结束。缩进级别与 Python 不同,在 Julia 中不重要。与 Python 不同,Julia 没有pass
关键字。 - 在 Julia 中,字符串用双引号 (
"text"
) 表示(多行字符串使用三个双引号),而在 Python 中,字符串可以用单引号 ('text'
) 或双引号 ("text"
) 表示。在 Julia 中,单引号用于表示字符 ('c'
)。 - 在 Julia 中,字符串连接使用
*
,而不是 Python 中的+
。类似地,字符串重复使用^
,而不是*
。Julia 不支持 Python 中的字符串字面量隐式连接(例如,'ab' 'cd' == 'abcd'
)。 - Python 列表(灵活但速度慢)对应于 Julia 的
Vector{Any}
类型,或者更一般地Vector{T}
,其中T
是某种非具体元素类型。“快速”数组,例如 NumPy 数组,将元素按位置存储(即,dtype
为np.float64
、[('f1', np.uint64), ('f2', np.int32)]
等),可以用Array{T}
表示,其中T
是一个具体的、不可变的元素类型。这包括内置类型,例如Float64
、Int32
、Int64
,但也包括更复杂的类型,例如Tuple{UInt64,Float64}
以及许多用户定义的类型。 - 在 Julia 中,数组、字符串等的索引从 1 开始,而不是从 0 开始。
- 与 Python 不同,Julia 的切片索引包含最后一个元素。在 Julia 中,
a[2:3]
等同于 Python 中的a[1:3]
。 - 与 Python 不同,Julia 允许 具有任意索引的 AbstractArrays。Python 中负索引的特殊解释,
a[-1]
和a[-2]
,在 Julia 中应该写成a[end]
和a[end-1]
。 - Julia 要求使用
end
来索引到最后一个元素。在 Python 中,x[1:]
等同于在 Julia 中的x[2:end]
。 - 在 Julia 中,在任何对象之前使用
:
会创建一个Symbol
或引用表达式;因此,x[:5]
等同于x[5]
。如果要获取数组的前n
个元素,请使用范围索引。 - Julia 的范围索引格式为
x[start:step:stop]
,而 Python 的格式为x[start:(stop+1):step]
。因此,Python 中的x[0:10:2]
等同于 Julia 中的x[1:2:10]
。类似地,Python 中的x[::-1]
(表示反转数组)等同于 Julia 中的x[end:-1:1]
。 - 在 Julia 中,范围可以独立地构造为
start:step:stop
,与它在数组索引中使用的语法相同。也支持range
函数。 - 在 Julia 中,使用数组对矩阵进行索引,例如
X[[1,2], [1,3]]
,指的是包含第一行和第二行与第一列和第三列交集的子矩阵。在 Python 中,X[[1,2], [1,3]]
指的是包含矩阵中单元格[1,1]
和[2,3]
的值的向量。Julia 中的X[[1,2], [1,3]]
等同于 Python 中的X[np.ix_([0,1],[0,2])]
。Python 中的X[[0,1], [0,2]]
等同于 Julia 中的X[[CartesianIndex(1,1), CartesianIndex(2,3)]]
。 - Julia 没有行延续语法:如果在一行结束时,到目前为止的输入是一个完整的表达式,则认为它已完成;否则输入将继续。强制表达式继续的一种方法是将其括在括号中。
- Julia 数组是列优先的(Fortran 顺序),而 NumPy 数组默认情况下是行优先的(C 顺序)。为了在遍历数组时获得最佳性能,Julia 中循环的顺序应该与 NumPy 相反(参见 性能提示的相关部分)。
- Julia 的更新运算符(例如
+=
、-=
等)不是就地运算符,而 NumPy 的是。这意味着A = [1, 1]; B = A; B += [3, 3]
不会改变A
中的值,而是将名称B
重新绑定到右侧B = B + 3
的结果,这是一个新的数组。对于就地操作,请使用B .+= 3
(另见 点运算符)、显式循环或InplaceOps.jl
。 - 与 Python 不同,Julia 在每次调用方法时都会评估函数参数的默认值,而在 Python 中,默认值只在定义函数时评估一次。例如,函数
f(x=rand()) = x
在每次没有参数调用时都会返回一个新的随机数。另一方面,函数g(x=[1,2]) = push!(x,3)
在每次调用g()
时都会返回[1,2,3]
。 - 在 Julia 中,关键字参数必须使用关键字传递,不像 Python,通常可以按位置传递它们。尝试按位置传递关键字参数会导致方法签名发生改变,从而导致
MethodError
或者调用了错误的方法。 - 在 Julia 中,
%
是余数运算符,而在 Python 中是模运算符。 - 在 Julia 中,常用的
Int
类型对应于机器整数类型 (Int32
或Int64
),与 Python 不同,在 Python 中,int
是任意长度的整数。这意味着在 Julia 中,Int
类型会溢出,因此2^64 == 0
。如果需要更大的值,请使用其他合适的类型,例如Int128
、BigInt
或浮点类型,例如Float64
。 - 在 Julia 中,虚数单位
sqrt(-1)
用im
表示,而不是 Python 中的j
。 - 在 Julia 中,指数运算符是
^
,而不是 Python 中的**
。 - Julia 使用
nothing
类型为Nothing
来表示空值,而 Python 使用None
类型为NoneType
。 - 在 Julia 中,矩阵类型的标准运算符是矩阵运算,而在 Python 中,标准运算符是逐元素运算。当
A
和B
都是矩阵时,Julia 中的A * B
执行矩阵乘法,而不是 Python 中的逐元素乘法。Julia 中的A * B
等同于 Python 中的A @ B
,而 Python 中的A * B
等同于 Julia 中的A .* B
。 - Julia 中的伴随运算符
'
返回向量的伴随矩阵(行向量的惰性表示),而 Python 中向量的转置运算符.T
返回原始向量(无操作)。 - 在 Julia 中,一个函数可以包含多个具体实现(称为方法),这些方法通过多重分派根据调用所有参数的类型来选择,而 Python 中的函数只有一个实现,没有多态性(与 Python 方法调用不同,它使用不同的语法并允许在方法接收者上进行分派)。
- Julia 中没有类。取而代之的是结构(可变或不可变),包含数据但不包含方法。
- 在 Python 中调用类实例的方法(
x = MyClass(*args); x.f(y)
)对应于 Julia 中的函数调用,例如x = MyType(args...); f(x, y)
。总的来说,多重分派比 Python 类系统更灵活、更强大。 - Julia 结构可以只有一个抽象超类型,而 Python 类可以继承自一个或多个(抽象或具体)超类。
- Julia 程序结构的逻辑(包和模块)独立于文件结构(
include
用于其他文件),而 Python 代码结构由目录(包)和文件(模块)定义。 - Julia 中的三元运算符
x > 0 ? 1 : -1
对应于 Python 中的条件表达式1 if x > 0 else -1
。 - 在 Julia 中,
@
符号指的是宏,而在 Python 中指的是装饰器。 - Julia 中的异常处理使用
try
—catch
—finally
完成,而不是try
—except
—finally
。与 Python 不同,不建议在 Julia 中将异常处理作为正常工作流程的一部分(与 Python 相比,Julia 在普通控制流方面更快,但在异常捕获方面更慢)。 - 在 Julia 中,循环很快,为了性能原因,不需要编写“矢量化”代码。
- 在 Julia 中,要小心非常量全局变量,特别是在紧凑循环中。由于你可以在 Julia 中编写接近底层的代码(与 Python 不同),全局变量的影响可能会很大(参见 性能提示)。
- 在 Julia 中,舍入和截断是显式的。Python 中的
int(3.7)
应该写成floor(Int, 3.7)
或Int(floor(3.7))
,它与round(Int, 3.7)
不同。floor(x)
和round(x)
本身返回与x
类型相同的整数值,而不是总是返回Int
。 - 在 Julia 中,解析是显式的。Python 中的
float("3.7")
在 Julia 中应为parse(Float64, "3.7")
。 - 在 Python 中,大多数值可以在逻辑上下文中使用(例如,
if "a":
表示执行后面的代码块,if "":
表示不执行)。在 Julia 中,你需要显式转换为Bool
(例如,if "a"
会抛出异常)。如果你想在 Julia 中测试一个非空字符串,你需要显式地写if !isempty("")
。也许令人惊讶的是,在 Python 中if "False"
和bool("False")
都计算为True
(因为"False"
是一个非空字符串);在 Julia 中,parse(Bool, "false")
返回false
。 - 在 Julia 中,大多数代码块(包括循环和
try
—catch
—finally
)都会引入一个新的局部作用域。请注意,在 Python 和 Julia 中,推导式(列表、生成器等)都会引入一个新的局部作用域,而在两种语言中,if
代码块都不会引入新的局部作用域。
与 C/C++ 的显著差异
- Julia 数组用方括号索引,可以有多个维度
A[i,j]
。此语法不仅仅是 C/C++ 中指针或地址引用的语法糖。请参见 有关数组构造的手册条目。 - 在 Julia 中,数组、字符串等的索引从 1 开始,而不是从 0 开始。
- 当 Julia 数组被赋值给另一个变量时,不会被复制。在
A = B
之后,更改B
的元素也会修改A
。更新运算符,如+=
,不会就地操作,它们等同于A = A + B
,这将左侧重新绑定到右侧表达式的结果。 - Julia 数组是列优先的(Fortran 顺序),而 C/C++ 数组默认情况下是行优先的。为了在遍历数组时获得最佳性能,Julia 中循环的顺序应该与 C/C++ 相反(参见 性能提示的相关部分)。
- 当 Julia 值被赋值或传递给函数时,不会被复制。如果函数修改了数组,则这些更改将在调用者中可见。
- 在 Julia 中,空白很重要,与 C/C++ 不同,因此在向 Julia 程序中添加或删除空白时,必须小心。
- 在 Julia 中,没有小数点的数字字面量(例如
42
)会创建类型为Int
的有符号整数,但超过机器字长大小的字面量会自动提升为更大的类型,例如Int64
(如果Int
是Int32
),Int128
,或任意大的BigInt
类型。没有像L
,LL
,U
,UL
,ULL
这样的数字字面量后缀来指示无符号和/或有符号与无符号。十进制字面量始终是有符号的,十六进制字面量(以0x
开头,类似 C/C++),是无符号的,除非它们编码超过 128 位,在这种情况下它们是BigInt
类型。十六进制字面量也与 C/C++/Java 不同,也不像 Julia 中的十进制字面量,它们的类型是根据字面量的长度来决定的,包括前导 0。例如,0x0
和0x00
的类型是UInt8
,0x000
和0x0000
的类型是UInt16
,然后包含 5 到 8 个十六进制数字的字面量是UInt32
类型,9 到 16 个十六进制数字是UInt64
类型,17 到 32 个十六进制数字是UInt128
类型,超过 32 个十六进制数字是BigInt
类型。在定义十六进制掩码时需要考虑到这一点,例如~0xf == 0xf0
与~0x000f == 0xfff0
有很大区别。64 位Float64
和 32 位Float32
位字面量分别表示为1.0
和1.0f0
。如果浮点字面量不能精确表示,则会进行舍入(并且不会提升为BigFloat
类型)。浮点字面量的行为更接近于 C/C++。八进制(以0o
为前缀)和二进制(以0b
为前缀)字面量也被视为无符号的(或对于超过 128 位的字面量,视为BigInt
)。 - 在 Julia 中,除法运算符
/
在两个操作数都是整数类型时返回浮点数。要执行整数除法,请使用div
或÷
。 - 用浮点类型索引
Array
通常在 Julia 中会导致错误。Julia 中等同于 C 表达式a[i / 2]
的表达式是a[i ÷ 2 + 1]
,其中i
是整数类型。 - 字符串字面量可以用
"
或"""
分隔,"""
分隔的字面量可以包含"
字符,而无需像"\""
那样用引号引起来。字符串字面量可以包含其他变量或表达式的值,用$variablename
或$(expression)
指示,它们会在函数上下文中评估变量名或表达式。 //
表示Rational
数字,而不是单行注释(Julia 中是#
)。#=
表示多行注释的开始,=#
表示结束。- Julia 中的函数从它们最后一个表达式(或表达式)或
return
关键字返回值。函数可以返回多个值,并作为元组分配,例如(a, b) = myfunction()
或a, b = myfunction()
,而无需像 C/C++ 中那样传递指向值的指针(即a = myfunction(&b)
)。 - Julia 不需要使用分号来结束语句。表达式的结果不会自动打印(除了在交互式提示符下,即 REPL),并且代码行不需要以分号结尾。可以使用
println
或@printf
来打印特定输出。在 REPL 中,可以使用;
来抑制输出。;
在[ ]
中也有不同的含义,需要注意。;
可以用来在一行上分隔表达式,但在很多情况下并不严格需要,更多的是为了可读性。 - 在 Julia 中,运算符
⊻
(xor
) 执行按位异或运算,即 C/C++ 中的^
。此外,按位运算符的优先级与 C/C++ 不同,因此可能需要使用括号。 - Julia 的
^
是指数运算(pow),而不是像 C/C++ 中的按位异或运算(在 Julia 中使用⊻
或xor
)。 - Julia 有两个右移运算符,
>>
和>>>
。>>
执行算术移位,>>>
始终执行逻辑移位,与 C/C++ 不同,在 C/C++ 中,>>
的含义取决于被移位的类型。 - Julia 的
->
创建匿名函数,它不通过指针访问成员。 - Julia 在编写
if
语句或for
/while
循环时不需要使用括号:使用for i in [1, 2, 3]
代替for (int i=1; i <= 3; i++)
,使用if i == 1
代替if (i == 1)
。 - Julia 不会将数字
0
和1
视为布尔值。您不能在 Julia 中编写if (1)
,因为if
语句只接受布尔值。相反,您可以编写if true
、if Bool(1)
或if 1==1
。 - Julia 使用
end
来表示条件块的结束,例如if
,循环块,例如while
/for
,以及函数。为了替代单行if ( cond ) statement
,Julia 允许使用以下形式的语句:if cond; statement; end
,cond && statement
和!cond || statement
。在后两种语法中的赋值语句必须显式地用括号括起来,例如cond && (x = value)
,因为运算符优先级的原因。 - Julia 没有行延续语法:如果在一行结束时,到目前为止的输入是一个完整的表达式,则认为它已完成;否则输入将继续。强制表达式继续的一种方法是将其括在括号中。
- Julia 宏作用于解析后的表达式,而不是程序的文本,这使得它们能够对 Julia 代码进行复杂的转换。宏名称以
@
字符开头,并且具有类似函数的语法,@mymacro(arg1, arg2, arg3)
,以及类似语句的语法,@mymacro arg1 arg2 arg3
。这些形式是可互换的;类似函数的形式在宏出现在另一个表达式中时特别有用,而且通常最清楚。类似语句的形式通常用于注释块,例如在分布式for
结构中:@distributed for i in 1:n; #= body =#; end
。在宏结构的结束位置不明确的地方,请使用类似函数的形式。 - Julia 具有枚举类型,使用宏
@enum(name, value1, value2, ...)
表达。例如:@enum(Fruit, banana=1, apple, pear)
- 按照惯例,修改其参数的函数在名称末尾都有一个
!
,例如push!
。 - 在 C++ 中,默认情况下,你拥有静态分派,即你需要将函数注释为 virtual,才能实现动态分派。另一方面,在 Julia 中,每个方法都是 "virtual"(尽管它比这更通用,因为方法是根据每个参数类型进行分派的,而不仅仅是
this
,使用最具体的声明规则)。
Julia ⇔ C/C++: Namespaces
- C/C++ 的
namespace
粗略地对应于 Julia 的module
。 - Julia 中没有私有全局变量或字段。所有内容都可以通过完全限定路径(或所需的相对路径)公开访问。
using MyNamespace::myfun
(C++)大致对应于import MyModule: myfun
(Julia)。using namespace MyNamespace
(C++)大致对应于using MyModule
(Julia)。- 在 Julia 中,只有
export
ed 符号对调用模块可用。 - 在 C++ 中,只有包含在(公共)头文件中的元素可用。
- 在 Julia 中,只有
- 注意:
import
/using
关键字(Julia)还会加载模块(见下文)。 - 注意:
import
/using
(Julia)仅在全局作用域级别(module
)有效。- 在 C++ 中,
using namespace X
在任意作用域内有效(例如:函数作用域)。
- 在 C++ 中,
Julia ⇔ C/C++: Module loading
- 当你想到 C/C++ 的 "library" 时,你可能在寻找 Julia 的 "package"。
- 注意:C/C++ 库通常包含多个 "软件模块",而 Julia "package" 通常只包含一个。
- 提醒:Julia
module
是全局作用域(不一定是 "软件模块")。
- 代替 build/
make
脚本,Julia 使用 "Project Environments"(有时也称为 "Project" 或 "Environment")。- 构建脚本仅在更复杂的应用程序中需要(例如那些需要编译或下载 C/C++ 可执行文件)。
- 要在 Julia 中开发应用程序或项目,你可以将它的根目录初始化为 "Project Environment",并在其中存放应用程序特定的代码/package。这提供了对项目依赖项的良好控制,以及未来的可重复性。
- 可以使用
Pkg.add()
函数或 Pkg REPL 模式将可用的 package 添加到 "Project Environment" 中。(但这并不会加载该 package)。 - "Project Environment" 的可用 package 列表(直接依赖项)保存在其
Project.toml
文件中。 - "Project Environment" 的完整依赖项信息由
Pkg.resolve()
自动生成并保存在其Manifest.toml
文件中。
- "Project Environment" 可用的 package("软件模块")使用
import
或using
加载。- 在 C/C++ 中,你使用
#include <moduleheader>
来获取对象/函数声明,并在构建可执行文件时链接库。 - 在 Julia 中,再次调用 using/import 只会将现有的模块引入作用域,但不会再次加载它(类似于向 C/C++ 添加非标准的
#pragma once
)。
- 在 C/C++ 中,你使用
- 基于目录的 package 仓库(Julia)可以通过将仓库路径添加到
Base.LOAD_PATH
数组来使其可用。- 来自基于目录的 package 仓库的 package 在使用
import
或using
加载之前不需要Pkg.add()
工具。它们只是对项目可用。 - 基于目录的 package 仓库是开发本地 "软件模块" 库的最快解决方案。
- 来自基于目录的 package 仓库的 package 在使用
Julia ⇔ C/C++: Assembling modules
- 在 C/C++ 中,
.c
/.cpp
文件使用 build/make
脚本编译并添加到库中。- 在 Julia 中,
import [PkgName]
/using [PkgName]
语句加载位于 package 的[PkgName]/src/
子目录中的[PkgName].jl
。 - 反过来,
[PkgName].jl
通常使用对include "[someotherfile].jl"
的调用加载相关的源文件。
- 在 Julia 中,
include "./path/to/somefile.jl"
(Julia)与#include "./path/to/somefile.jl"
(C/C++)非常相似。- 但是,
include "..."
(Julia)不用于包含头文件(不需要)。 - 不要使用
include "..."
(Julia)从其他 "软件模块" 加载代码(请改用import
/using
)。 include "path/to/some/module.jl"
(Julia)会在不同的模块中实例化多个相同代码的版本(创建具有相同名称的不同类型(等))。include "somefile.jl"
通常用于在同一个 Julia package("软件模块")内组装多个文件。因此,确保文件只被include
一次相对简单(没有#ifdef
混乱)。
- 但是,
Julia ⇔ C/C++: Module interface
- C++ 使用 "public"
.h
/.hpp
文件公开接口,而 Juliamodule
将专门用于其用户的符号标记为public
或export
ed。- 通常,Julia
module
通过为现有函数生成新的 "方法" 来添加功能(例如:Base.push!
)。 - 因此,Julia package 的开发人员不能依赖头文件来进行接口文档化。
- Julia package 的接口通常使用 docstrings、README.md、静态网页等来描述。
- 通常,Julia
- 一些开发人员选择不
export
使用其 package/module 所需的所有符号。- 用户可能需要通过使用 package/module 名称限定函数/结构体/... 来访问这些组件(例如:
MyModule.run_this_task(...)
)。
- 用户可能需要通过使用 package/module 名称限定函数/结构体/... 来访问这些组件(例如:
Julia ⇔ C/C++: Quick reference
软件概念 | Julia | C/C++ |
---|---|---|
无名作用域 | begin ... end | { ... } |
函数作用域 | function x() ... end | int x() { ... } |
全局作用域 | module MyMod ... end | namespace MyNS { ... } |
软件模块 | 一个 Julia "包" | .h /.hpp 文件<br>+编译后的 somelib.a |
组装<br>软件模块 | SomePkg.jl : ...<br>import("subfile1.jl") <br>import("subfile2.jl") <br>... | $(AR) *.o ⇒ somelib.a |
导入<br>软件模块 | import SomePkg | #include <somelib> <br>+链接 somelib.a |
模块库 | LOAD_PATH[] , *Git 仓库,<br>**自定义包注册表 | 更多 .h /.hpp 文件<br>+更大编译后的 somebiglib.a |
* Julia 包管理器支持从单个 Git 仓库注册多个包。<br> * 这允许用户在一个仓库中存放一组相关的包。<br> ** Julia 注册表主要用于提供包的版本控制和分发。<br> ** 自定义包注册表可以用来创建一种模块库。
与 Common Lisp 的显著差异
Julia 默认情况下使用 1 为起始的索引,也可以处理任意索引偏移。
函数和变量共享同一个命名空间(“Lisp-1”)。
有一个
Pair
类型,但它不适合作为COMMON-LISP:CONS
。各种可迭代集合可以在语言的大部分地方互换使用(例如 splatting、元组等)。对于短的异构元素集合,Tuple
与 Common Lisp 列表最为接近。使用NamedTuple
代替关联列表。对于较大规模的同构类型集合,应该使用Array
和Dict
。典型的 Julia 原型设计工作流程也使用对图像的连续操作,这通过Revise.jl 包实现。
为了性能,Julia 倾向于操作具有类型稳定性。Common Lisp 从底层机器操作中抽象出来,而 Julia 更贴近它们。例如
模块(命名空间)可以是分层的。
import
和using
有双重作用:它们加载代码并使其在命名空间中可用。import
只针对模块名称(大致相当于ASDF:LOAD-OP
)。槽名称不需要单独导出。全局变量不能从模块外部赋值(除非使用eval(mod, :(var = val))
作为转义方法)。宏以
@
开头,并没有像 Common Lisp 那样无缝地集成到语言中;因此,宏的使用不像在后者中那样普遍。语言支持宏 的一种卫生形式。由于不同的表面语法,没有等效于COMMON-LISP:&BODY
的东西。所有函数都是泛型的,并使用多重调度。参数列表不必遵循相同的模板,这会导致一个强大的习惯用法(参见
do
)。可选参数和关键字参数的处理方式不同。方法歧义不会像在 Common Lisp 对象系统中那样解决,需要为交集定义更具体的 method。符号不属于任何包,本身也不包含任何值。
M.var
在模块M
中评估符号var
。语言完全支持函数式编程风格,包括闭包,但这并不总是 Julia 的惯用解决方案。对于修改捕获变量时的性能,可能需要一些变通方法。