与其他语言的显著区别

与 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 中的 ij
  • 在 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] 将始终构造一个包含 xyz 的 3 个元素的数组。
    • 若要在第一个(“垂直”)维度进行连接,请使用 vcat(x,y,z) 或者用分号隔开([x; y; z])。
    • 若要在第二个(“水平”)维度进行连接,请使用 hcat(x,y,z) 或者用空格隔开([x y z])。
    • 若要构造块矩阵(在第一个两个维度上连接),请使用 hvcat 或者将空格和分号组合起来([a b; c d])。
  • 在 Julia 中,a:ba:b:c 构造 AbstractRange 对象。若要构造一个类似 MATLAB 的完整向量,请使用 collect(a:b)。通常情况下,不需要调用 collectAbstractRange 对象在大多数情况下可以像普通数组一样使用,但效率更高,因为它延迟计算其值。这种创建专门对象而不是完整数组的模式在 Julia 中很常见,在 range 等函数中,或者在 enumeratezip 等迭代器中都可以看到。这些特殊对象在大多数情况下可以像普通数组一样使用。
  • Julia 中的函数从它们的最后一个表达式或 return 关键字返回结果,而不是在函数定义中列出要返回的变量的名称(有关详细信息,请参阅 return 关键字)。
  • Julia 脚本可以包含任意数量的函数,当加载文件时,所有定义都将外部可见。函数定义可以从当前工作目录之外的文件加载。
  • 在 Julia 中,当使用单个参数调用 sumprodmax 等约简函数时,它们将在数组的每个元素上执行,即使 A 的维度超过一个。
  • 在 Julia 中,必须使用圆括号来调用不带参数的函数,例如 rand()
  • Julia 不鼓励使用分号来结束语句。语句的结果不会自动打印(除了在交互式提示符下),并且代码行不需要以分号结束。可以使用 println@printf 来打印特定输出。
  • 在 Julia 中,如果 AB 是数组,则逻辑比较运算(如 A == B)不会返回布尔值数组。相反,请使用 A .== B,类似地,对于其他布尔运算符,如 <>
  • 在 Julia 中,运算符 &| (xor) 执行与 MATLAB 中的 andorxor 等效的按位运算,并且它们的优先级与 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 提供了更高阶函数 filterfilter!,允许用户编写 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]
  • 与许多语言一样,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; endcond && statement!cond || statement 形式的语句。在后两种语法中,赋值语句必须显式地用括号括起来,例如 cond && (x = value)

  • 在 Julia 中,<-<<--> 不是赋值运算符。

  • Julia 的 -> 创建一个匿名函数。

  • Julia 的 * 运算符可以执行矩阵乘法,这与 R 不同。如果 AB 是矩阵,那么 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 不会将数字 01 视为布尔值。您不能在 Julia 中编写 if (1),因为 if 语句只接受布尔值。相反,您可以编写 if trueif Bool(1)if 1==1

  • Julia 没有提供 nrowncol。相反,使用 size(M, 1) 代替 nrow(M),使用 size(M, 2) 代替 ncol(M)

  • Julia 谨慎地区分标量、向量和矩阵。在 R 中,1c(1) 是相同的。在 Julia 中,它们不能互换使用。

  • Julia 的 diagdiagm 与 R 中的函数不同。

  • Julia 不允许在赋值操作的左侧对函数调用的结果进行赋值:您不能编写 diag(M) = fill(1, n)

  • Julia 不鼓励在主命名空间中填充函数。Julia 的大多数统计功能都在 中,位于 JuliaStats 组织 下。例如

  • 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 中,向量和矩阵使用 hcatvcathvcat 进行连接,而不是像 R 中一样使用 crbindcbind

  • 在 Julia 中,a:b 之类的范围不是 R 中向量类型的简写,而是一个专门的 AbstractRange 对象,用于迭代。要将范围转换为向量,请使用 collect(a:b)

  • : 运算符在 R 和 Julia 中具有不同的优先级。特别是在 Julia 中,算术运算符的优先级高于 : 运算符,而在 R 中则相反。例如,Julia 中的 1:n-1 等同于 R 中的 1:(n-1)

  • Julia 的 maxmin 分别等同于 R 中的 pmaxpmin,但两个参数的维数必须相同。虽然 maximumminimum 取代了 R 中的 maxmin,但它们之间存在重要差异。

  • Julia 的 sumprodmaximumminimum 与 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)。需要注意的是,第二个参数没有错误检查。

  • Julia 有几个可以修改其参数的函数。例如,它既有 sort 又有 sort!

  • 在 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 中的 assignget 的函数。

  • 在 Julia 中,return 不需要括号。

  • 在 R 中,一种常用的删除不需要的值的方法是使用逻辑索引,例如在表达式 x[x>3] 中,或在语句 x = x[x>3] 中,以原地修改 x。相反,Julia 提供了高阶函数 filterfilter!,允许用户编写 filter(z->z>3, x)filter!(z->z>3, x) 作为对相应翻译 x[x.>3]x = x[x.>3] 的替代方案。使用 filter! 减少了临时数组的使用。

与 Python 的显著差异

  • Julia 中的 forifwhile 等代码块使用 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 数组,将元素按位置存储(即,dtypenp.float64[('f1', np.uint64), ('f2', np.int32)] 等),可以用 Array{T} 表示,其中 T 是一个具体的、不可变的元素类型。这包括内置类型,例如 Float64Int32Int64,但也包括更复杂的类型,例如 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 类型对应于机器整数类型 (Int32Int64),与 Python 不同,在 Python 中,int 是任意长度的整数。这意味着在 Julia 中,Int 类型会溢出,因此 2^64 == 0。如果需要更大的值,请使用其他合适的类型,例如 Int128BigInt 或浮点类型,例如 Float64
  • 在 Julia 中,虚数单位 sqrt(-1)im 表示,而不是 Python 中的 j
  • 在 Julia 中,指数运算符是 ^,而不是 Python 中的 **
  • Julia 使用 nothing 类型为 Nothing 来表示空值,而 Python 使用 None 类型为 NoneType
  • 在 Julia 中,矩阵类型的标准运算符是矩阵运算,而在 Python 中,标准运算符是逐元素运算。当 AB 都是矩阵时,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 中的异常处理使用 trycatchfinally 完成,而不是 tryexceptfinally。与 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 中,大多数代码块(包括循环和 trycatchfinally)都会引入一个新的局部作用域。请注意,在 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(如果 IntInt32),Int128,或任意大的 BigInt 类型。没有像 LLLUULULL 这样的数字字面量后缀来指示无符号和/或有符号与无符号。十进制字面量始终是有符号的,十六进制字面量(以 0x 开头,类似 C/C++),是无符号的,除非它们编码超过 128 位,在这种情况下它们是 BigInt 类型。十六进制字面量也与 C/C++/Java 不同,也不像 Julia 中的十进制字面量,它们的类型是根据字面量的长度来决定的,包括前导 0。例如,0x00x00 的类型是 UInt80x0000x0000 的类型是 UInt16,然后包含 5 到 8 个十六进制数字的字面量是 UInt32 类型,9 到 16 个十六进制数字是 UInt64 类型,17 到 32 个十六进制数字是 UInt128 类型,超过 32 个十六进制数字是 BigInt 类型。在定义十六进制掩码时需要考虑到这一点,例如 ~0xf == 0xf0~0x000f == 0xfff0 有很大区别。64 位 Float64 和 32 位 Float32 位字面量分别表示为 1.01.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 不会将数字 01 视为布尔值。您不能在 Julia 中编写 if (1),因为 if 语句只接受布尔值。相反,您可以编写 if trueif Bool(1)if 1==1
  • Julia 使用 end 来表示条件块的结束,例如 if,循环块,例如 while/ for,以及函数。为了替代单行 if ( cond ) statement,Julia 允许使用以下形式的语句:if cond; statement; endcond && 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 中,只有 exported 符号对调用模块可用。
    • 在 C++ 中,只有包含在(公共)头文件中的元素可用。
  • 注意:import/using 关键字(Julia)还会加载模块(见下文)。
  • 注意:import/using(Julia)仅在全局作用域级别(module)有效。
    • 在 C++ 中,using namespace X 在任意作用域内有效(例如:函数作用域)。

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("软件模块")使用 importusing 加载。
    • 在 C/C++ 中,你使用 #include <moduleheader> 来获取对象/函数声明,并在构建可执行文件时链接库。
    • 在 Julia 中,再次调用 using/import 只会将现有的模块引入作用域,但不会再次加载它(类似于向 C/C++ 添加非标准的 #pragma once)。
  • 基于目录的 package 仓库(Julia)可以通过将仓库路径添加到 Base.LOAD_PATH 数组来使其可用。
    • 来自基于目录的 package 仓库的 package 在使用 importusing 加载之前不需要 Pkg.add() 工具。它们只是对项目可用。
    • 基于目录的 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" 的调用加载相关的源文件。
  • 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 文件公开接口,而 Julia module 将专门用于其用户的符号标记为 publicexported。
    • 通常,Julia module 通过为现有函数生成新的 "方法" 来添加功能(例如:Base.push!)。
    • 因此,Julia package 的开发人员不能依赖头文件来进行接口文档化。
    • Julia package 的接口通常使用 docstrings、README.md、静态网页等来描述。
  • 一些开发人员选择不 export 使用其 package/module 所需的所有符号。
    • 用户可能需要通过使用 package/module 名称限定函数/结构体/... 来访问这些组件(例如:MyModule.run_this_task(...))。

Julia ⇔ C/C++: Quick reference

软件概念JuliaC/C++
无名作用域begin ... end{ ... }
函数作用域function x() ... endint x() { ... }
全局作用域module MyMod ... endnamespace MyNS { ... }
软件模块一个 Julia "包".h/.hpp 文件<br>+编译后的 somelib.a
组装<br>软件模块SomePkg.jl: ...<br>import("subfile1.jl")<br>import("subfile2.jl")<br>...$(AR) *.o &rArr; 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 代替关联列表。对于较大规模的同构类型集合,应该使用 ArrayDict

  • 典型的 Julia 原型设计工作流程也使用对图像的连续操作,这通过Revise.jl 包实现。

  • 为了性能,Julia 倾向于操作具有类型稳定性。Common Lisp 从底层机器操作中抽象出来,而 Julia 更贴近它们。例如

    • 使用 / 进行的整数除法始终返回浮点数结果,即使计算结果是精确的。
      • // 始终返回有理数结果
      • ÷ 始终返回(截断)的整数结果
    • 支持大整数,但转换不是自动的;普通整数会溢出
    • 支持复数,但要获得复数结果,需要复数输入
    • 有多种复数和有理数类型,具有不同的分量类型。
  • 模块(命名空间)可以是分层的。 importusing 有双重作用:它们加载代码并使其在命名空间中可用。 import 只针对模块名称(大致相当于 ASDF:LOAD-OP)。槽名称不需要单独导出。全局变量不能从模块外部赋值(除非使用 eval(mod, :(var = val)) 作为转义方法)。

  • 宏以 @ 开头,并没有像 Common Lisp 那样无缝地集成到语言中;因此,宏的使用不像在后者中那样普遍。语言支持 的一种卫生形式。由于不同的表面语法,没有等效于 COMMON-LISP:&BODY 的东西。

  • 所有函数都是泛型的,并使用多重调度。参数列表不必遵循相同的模板,这会导致一个强大的习惯用法(参见 do)。可选参数和关键字参数的处理方式不同。方法歧义不会像在 Common Lisp 对象系统中那样解决,需要为交集定义更具体的 method。

  • 符号不属于任何包,本身也不包含任何值。M.var 在模块 M 中评估符号 var

  • 语言完全支持函数式编程风格,包括闭包,但这并不总是 Julia 的惯用解决方案。对于修改捕获变量时的性能,可能需要一些变通方法