现在关注 Julia 的一些独特语法和特性,会省略与 MATLAB 或 Python 相似的语法特性。

数值系统

整数

Julia 支持固定长度的整数类型,包括有符号和无符号的版本,例如 Int8Int32UInt32 等。对于十进制整数字面量,在64位系统中默认使用 Int64 类型整数。可以使用 typeof 查看字面量的类型

1
typeof(1) # Int64

类型也是可以作为参数进行运算的,例如使用 typemintypemax 直接查看类型的最大最小值

1
2
3
typemin(Int32) # -2147483648
typemin(Int64) # -9223372036854775808
typemax(Int64) # 9223372036854775807

注:

  • Julia 支持使用 _ 作为数字的分隔符,可以提高可读性,例如10_000
  • 由于整数的位数是固定的,在运算中自然也存在溢出问题,与其它语言中的处理类似,不再赘述。
  • Python 的整数是无限精度的,Julia 也提供了 BigInt 类型以支持无限精度。

对于非十进制的整数字面量,Julia 采用了不同的处理:默认将其视作无类型整数,并自动选择合适的位数,例如

1
2
typeof(0x11)        # UInt8
typeof(0x1111111) # UInt32

浮点数

Julia 支持基本的浮点数类型,包括默认使用的双精度浮点数 Float64,单精度浮点数 Float32 和半精度浮点数 Float16

有一点奇怪的是,在科学记数法的表示中,使用 e 会对应 Float64 的字面量,使用 f 则会对于 Float32 的字面量。

1
2
typeof(1.1e-3) # Float64
typeof(1.1f-3) # Float32

可以使用类型名将整数转换为浮点数,例如

1
2
3
Float64(1) # 1.0

typeof(Float64(1)) # Float64

有意思的是,Julia 给不同的浮点数类型分别设置了 naninf

  • Float64:NaNInf
  • Float32:NaN32Inf32
  • Float16:NaN16Inf16

关于这些特殊值的运算都符合 IEEE 标准,因此不同语言并没有什么区别。

Julia 的 eps 是一个函数,可以传入一个浮点数或者浮点数类型:

  • 如果传入的是浮点数,那么返回的就是它附近的浮点数间隙;
  • 如果传入的是浮点数类型,返回的是 1.0 附近的浮点数间隙;
  • 缺省时相当于 eps(Float64)
1
2
3
4
5
6
eps(1000.0) # 1.1368683772161603e-13
eps(1.0) # 2.220446049250313e-16

eps(Float32) # 1.1920929f-7
eps(Float64) # 2.220446049250313e-16
eps() # 2.220446049250313e-16

Julia 提供了 zeroone 函数来创建指定类型的 0 和 1,可以传入类型或者一个数,在某些情况下可以避免不必要的类型转换。 例如

1
2
zero(1.0) # 0.0
one(Int32) # 1

在整数和浮点数混合运算时,Julia 会自动转换为浮点数进行处理,并没有 MATLAB 那样的奇葩语法。

1
1 + 0.2 # 1.2

复数

Julia 支持复数,使用全局常量 im 表示虚数单位,例如

1
1 + 2im

有意思的是

1
2
typeof(1+im) # Complex{Int64}
typeof(im) # Complex{Bool}

这表明复数并不是简单的一个类型,实际类型与复数的实部虚部所属的类型有关。

涉及到 im 的字面量需要特别注意运算优先级的差异,例如

1
2
3/4*im # 0.0 + 0.75im
3/4im # 0.0 - 0.75im

更建议的做法是使用 complex 函数来创建复数,例如

1
complex(1,2) # 1 + 2im

注意 Julia 严格区分字面量是否是复数类型,例如 -1-1+0im 是完全不同的

1
2
-1 == -1+0im # true
-1 === -1+0im # false

在使用 sqrt() 时的差异非常明显

1
2
sqrt(-1) # ERROR
sqrt(-1+0im) # 0.0 + 1.0im

有理数

Julia 支持有理数类型,可以通过 // 运算符创建,例如

1
2
2//3
typeof(2//3) # Rational{Int64}

注:

  • Julia 会自动对分数进行化简,并且保证分母非负。
  • Julia 允许分母为0,例如 5//0,但是不允许分子分母同时为 0。
  • Julia 不支持涉及浮点数的 // 运算

字符和字符串

与 Python 不同,Julia 使用单引号 ' 表示字符,使用双引号 " 来表示字符串,两者是不同的类型,不可以混用。例如

1
2
typeof('a')  # Char
typeof("a") # String

字符的底层当然是基于 ASCII 存储的,因此也支持与数值的运算,例如

1
'a' + 1     # 'b'

Julia 支持多行字符串和 raw 标记的原始字符串(禁用 \ 转义,适合用于 Windows 路径),例如

1
2
3
4
5
6
text = """
这是
多行字符串
"""

rawstr = raw"C:\Users\Alice"

Julia 有如下两种方式拼接字符串,注意使用 * 而不是 + 进行拼接!

1
2
3
"Hello, " * "world!"         # "Hello, world!"

join(["a", "b", "c"], ", ") # "a, b, c"

使用 ^ 表示字符串的重复

1
"abc"^4 # "abcabcabcabc"

与 Python 的 f-string 类似,在字符串中可以使用 $ 插入变量或表达式,有歧义时可以加括号,例如

1
2
3
4
5
name = "Alice"
greeting = "Hello, $(name)!" # "Hello, Alice!"

age = 25
info = "Age next year: $(age + 1)" # "Age next year: 26"

布尔值

提供布尔类型的 truefalse

在底层实现时,Julia 采用 8 位整数存储的 0 和 1 分别代表 truefalse。相应的类型转换为

1
2
Bool(1) # true
Bool(0) # false

需要注意到是,Julia 不会把非零整数全部转换为 true,下面的语句报错

1
Bool(2) # error

运算符

不含向量化运算的部分。

运算符和其它语言都差不多,只有几个值得注意的:

  • a*b:乘法
  • a/b:除法
  • a\b:反向除法,a\b 等价于 b/a
  • a^b:幂运算
  • a%b:取余
  • ==:相等,这和 isequal 函数不太一样
  • !=: 不等
  • ===:严格比较二进制数据相等

注:

  • 涉及到 NaNInf 的等号和不等号判断可能违反直觉,例如 NaN != NaN
  • 部分运算符对应的 Unicode(LaTeX) 字符也可以正常使用,例如不等号也支持 \leq
  • =====isequal 的语义存在细微区别,尤其在某些边缘例子中,一般 === 最严格,isequal 最弱。

与 Python 一样,Julia 支持链式比较,例如

1
1 < 2 <= 2 < 3 == 3 > 2 >= 1 == 1 < 3 != 5 # true

Julia 支持 += 等复合赋值运算符,例如 a += b 等价于 a = a + b,这个过程中可能改变 a 的类型,例如

1
2
3
a=0; typeof(a) # Int64

a += 0.1; typeof(a) # Float64

! 是逻辑非,&& 是逻辑与,|| 是逻辑或,并且特别注意&&||都是短路运算,例如

1
2
3
true && false # false
true || false # true
!true # false

~&|按位运算符,例如

1
2
3
~0 # -1
123 & 234 # 106
1 | 2 | 4 # 7

容器

元组和具名元组

Julia 支持和 Python 几乎一样的元组,元组中的元素可以是不同类型,例如

1
2
3
4
(1, 2)
1, 2

(1, "2", 3.0)

这里的括号在不存在歧义时可以省略,只含一个元素的元组和空元组有固定的写法,例如

1
2
(1,)
()

元组中的元素可以使用索引访问(索引从1开始),例如

1
2
3
a = (1,'a');
a[1] # 1
a[2] # 'a'

和 Python 一样,Julia 的元组赋值可以自动解包,因此可以用来同时定义多个变量,例如

1
2
3
(x,y,z) = (1,2,3)

x,y,z = 1,2,3

与 Python 相比,Julia 的具名元组在语法上更加方便,例如

1
2
3
a = (x=1, y=2);
a.x # 1
a.y # 2

这看起来非常适合用来打包不可变数据,比字典更加方便。

字典

Julia 的字典也和 Python 差不多,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
dic1 = Dict("a" => 1, "b" => 2);

dic1["a"] # 1
dic1["b"] = 100;

length(dic1) # 2
keys(dic1)
# KeySet for a Dict{String, Int64} with 2 entries. Keys:
# "b"
# "a"

values(dic1)
# ValueIterator for a Dict{String, Int64} with 2 entries. Values:
# 100
# 1

字典的索引通常是字符串,但是实际上几乎可以是任何类型。

值得注意的是,在定义字典时使用的键值对并不是特殊语法,而是类似于 C++ 的 std::pair 的数据结构

1
typeof("a"=>2) # Pair{String, Int64}

集合

Julia 的集合语法与 Python 明显不同:

  • Julia 要求集合中的元素必须是同类型的,并且互不相等。(Python 只是要求不可变,用于确保唯一性)
  • Python 支持使用花括号创建集合字面量,Julia 必须使用 Set() 函数

例如

1
2
3
4
5
6
7
8
9
10
11
set1 = Set([1, 2, 3, 2, 3])
# Set{Int64} with 3 elements:
# 2
# 3
# 1

set2 = Set(('a','b','c','a','b','c'))
# Set{Char} with 3 elements:
# 'a'
# 'c'
# 'b'

集合中的元素存储是无序的,可以使用 in 判断元素是否在集合中

1
2
3
set1 = Set([1, 2, 3, 2, 3])

1 in set1 # true

支持集合间的基本运算,包括并集、交集、差集等。

数组

数组是科学计算中的核心容器,Julia 的这部分语法与 Python(Numpy) 和 MATLAB 均有所不同,相关的字面量语法非常混乱(比MATLAB还混乱)

Julia 提供数组来存储有序数据,通常用来存储同一类型的数据,当然也允许混合存储任意类型的数据,但是这会影响数组操作的效率。

TODO

输入输出基础

print/println

格式化输出

readline

parse与convert

补充

数值字面量系数

这是一个非常糟糕的,为了所谓的公式简洁优雅而提供的语法糖,实际上给代码可读性和语法解析都带来了额外的问题。

Julia 允许变量直接跟在一个数值字面量后,暗指乘法,例如

1
2
3
x = 3
2x^2 + 4x + 5 # 21
6.0x^3-x # 46.0

数值字面量系数的优先级跟一元运算符相同,比如取相反数,例如:

  • 2^3x 会被解析成 2^(3x)
  • 2x^3 会被解析成 2*(x^3)

括号表达式在某些情况下可以被用作变量的系数,暗指表达式与变量相乘,例如

1
2
3
x = 3
2(x+1) # 8
(x-1)x # 6

但是这玩意其实很鸡肋,因为下面的表达式都是语法错误:

1
2
3
x = 3
x(x-1) # ERROR
(x-1)(x+1) # ERROR

而且这里还可能和其它语法产生冲突,例如函数调用,十六进制的前缀0x,用于浮点数表示的fe,以及虚数单位im等。 总的来说,字面量系数这种奇葩语法糖还是了解即可,实践中不要使用。