2 复合数据结构
这一部分介绍Julia的向量、元组、集合、字典等复合数据结构, 以及函数的进一步介绍。
2.1 一维数组
Julia支持一维和多维的数组, 当一维数组的元素是数值时, 也可以理解成数学中的向量。
在程序中直接定义一个向量, 只要用方括号内写多个逗号分隔的数值,如
v1 = [2, 3, 5, 7, 11, 13, 17]7-element Vector{Int64}:
2
3
5
7
11
13
17
v2 = [1.5, 3, 4, 9.12]4-element Vector{Float64}:
1.5
3.0
4.0
9.12
其中v1是整数型的向量, v2是浮点型Float64的向量。 也可以定义元素为字符串的数组, 元素为不同类型的数组, 等等:
v3 = ["苹果", "桔子", "香蕉"]3-element Vector{String}:
"苹果"
"桔子"
"香蕉"
v4 = [123, 3.14, "数学", [1, 2, 3]]4-element Vector{Any}:
123
3.14
"数学"
[1, 2, 3]
用length(x)求向量x的元素个数,如
length(v1)
## 7可以用1:5定义一个范围,
在仅使用其中的元素值而不改写时作用与[1, 2, 3, 4, 5]类似。
1:2:9定义带有步长的范围,表示的值与[1, 3, 5, 7, 9]类似。
范围只需要存储必要的开始、结束、步长信息,
所以更节省空间,
但是不能对其元素进行修改。
1:5
## 1:5
1:2:7
## 1:2:7
5:-1:1
## 5:-1:1范围不是向量,
而是一种“可遍历数据结构”。
用collect()函数可以将范围转换成向量,如:
collect(5:-1:1)5-element Vector{Int64}:
5
4
3
2
1
2.1.1 向量下标
若x是向量,i是正整数,
x[i]表示向量的第i个元素。
第一个元素的下标为1,这种规定与R、FORTRAN语言相同,
但不同于Python、C、C++、JAVA语言。
如
v1 = [2, 3, 5, 7, 11, 13, 17]7-element Vector{Int64}:
2
3
5
7
11
13
17
v1[2]
## 3用end表示最后一个元素位置,如:
v1[end]
## 17对元素赋值将在原地修改元素的值,如
v1[2] = 0
@show v1;
## v1 = [2, 0, 5, 7, 11, 13, 17]这说明数组是“可变类型”(mutable), 即其中的成分可以原地修改。 字符串和元组则属于不可变类型(immutable)。
@show expr可以用比较简洁的带有提示的方式显示表达式和表达式的值。
2.1.1.1 用范围作为下标
下标可以是一个范围,如
v1 = [2, 3, 5, 7, 11, 13, 17]
v1[2:4]3-element Vector{Int64}:
3
5
7
在这种范围中,用end表示最后一个下标,如
v1[4:end]4-element Vector{Int64}:
7
11
13
17
v1[1:(end-3)]4-element Vector{Int64}:
2
3
5
7
v1[1:2:7]4-element Vector{Int64}:
2
5
11
17
v1[end:-1:1]7-element Vector{Int64}:
17
13
11
7
5
3
2
实际上,reverse(x)可以返回次序颠倒后的数组。
可以用仅有冒号作为下标,
这时表示包含所有元素的子集。
取出的多个元素可以修改,
可以用.=运算符赋值为同一个标量,如:
v1 = [2, 3, 5, 7, 11, 13, 17]
v1[:] .= 0;
@show v1;
## v1 = [0, 0, 0, 0, 0, 0, 0]v1 = [2, 3, 5, 7, 11, 13, 17]
v1[1:3] .= 0
@show v1;
## v1 = [0, 0, 0, 7, 11, 13, 17]也可以分别赋值,如
v1 = [2, 3, 5, 7, 11, 13, 17]
v1[1:3] = [101, 303, 505];
@show v1;
## v1 = [101, 303, 505, 7, 11, 13, 17]2.1.2 数组类型
当数组元素都是整数时,
显示其类型为“Array{Int64}”,
常用的还有“Array{Float64}”,
“Array{String}”,
“Array{Any}”等,
“Any”是Julia语言类型系统的根类型,
相应的数组可以容纳任何Julia对象作为元素。
如果数组元素都是基本类型如Float64, 则不允许给元素赋值为其它类型,如:
vf = [1.2, 2.5, 3.6]; vf3-element Vector{Float64}:
1.2
2.5
3.6
vf[2] = "abc"
## MethodError: Cannot `convert` an object of type String to an object of type Float64
## .........用eltype()求元素类型,如:
eltype(vf)
## Float642.1.3 向量初始化
用zeros(n)可以生成元素类型为Float64、元素值为0、长度为n的向量,如
zeros(3)3-element Vector{Float64}:
0.0
0.0
0.0
用zeros(Int64, 3)可以生成指定类型的(这里是Int64)初始化向量。如
zeros(Int64, 3)3-element Vector{Int64}:
0
0
0
用Vector{Float64}(undef, n)可以生成元素类型为Float64的长度为n的向量,
元素值未初始化,如
Vector{Float64}(undef, 3)3-element Vector{Float64}:
1.40319995e-315
1.40320011e-315
1.40320027e-315
类似可以生成其它元素类型的元素值未初始化向量,如
y1 = Vector{Int}(undef, 3);用这样的办法为向量分配存储空间后可以随后再填入元素值。
可以用fill!()填入统一的值。
函数名以!结尾是一个习惯用法,
表示该函数会修改其第一自变量的值。
如:
fill!(y1, 100)3-element Vector{Int64}:
100
100
100
也可以用fill(value, n)生成一个元素值都等于value的长度为n的一维数组。
可以用collect()将一个范围转换成可修改的向量。如:
collect(1:5)5-element Vector{Int64}:
1
2
3
4
5
2.1.4 变量与值
由于Julia的变量仅仅是向实际存储空间的引用(reference), 或称绑定(binding), 所以两个变量可以引用(绑定)到同一个向量的存储空间, 修改了其中一个变量的元素值,则另一个变量的元素也被修改了。 如
x1 = [1,2,3]
x2 = x1
x2[2] = 100
@show x1;
## x1 = [1, 100, 3]用“===”或“≡”(\equiv+TAB)可以比较两个变量是否同一对象,
如:
x2 === x1
## true允许两个变量指向同一个对象是有用的, 尤其在函数自变量传递时, 但是在一般程序中这种作法容易引起混淆。 向量(或者数组)作为函数自变量时, 调用函数时传递的是引用, 在函数内可以修改传递进来的向量的元素值。
如果需要制作数组的副本,
用copy()函数。
如
x1 = [1,2,3]
x2 = copy(x1)
x2[2] = -100
@show x1;
## x1 = [1, 2, 3]
x2 === x1
## false将仅有冒号的子集如x[:]放在等号左边可以修改所有元素,
如果将其放在等号右边并赋值给一个变量,
就可以制作副本,如:
x1 = [1,2,3]
x2 = x1[:]
x2[2] = -100
@show x1;
## x1 = [1, 2, 3]Julia对象的这种引用或者绑定做法, 初学者比较容易用错。 例如, 下面的程序将一个数组嵌套在另一个数组中:
x0 = [3, 4]
x1 = [1,2, x0]
x1[3][1] = 333
@show x0;
## x0 = [333, 4]因为x1中引用(绑定)了x0的值,
所以x1[3]和x0共用同一存储,
修改了x1[3]就修改了x0。
那么,
制作x1的副本能否解决问题?
x0 = [3, 4]
x1 = [1, 2, x0]
x2 = copy(x1)
x2[1] = 111
x2[3][1] = 333
@show x2;
## x2 = Any[111, 2, [333, 4]]
@show x1;
## x1 = Any[1, 2, [333, 4]]
@show x0;
## x0 = [333, 4]虽然x1[1]没有被修改,但是x1[3][1]还是被修改了,
x0也被修改了。
这是因为copy()执行的是所谓“浅层复制”,
对于内嵌的对象仍为引用。
可以用deepcopy(),
能解决大部分问题:
x0 = [3, 4]
x1 = [1,2, x0]
x2 = deepcopy(x1)
x2[1] = 111
x2[3][1] = 333
@show x2;
## x2 = Any[111, 2, [333, 4]]
@show x1;
## x1 = Any[1, 2, [3, 4]]
@show x0;
## x0 = [3, 4]仅修改了x2,没有修改x1和x0。
2.1.5 向量的有关函数
为了判断元素x是否属于数组v,可以用表达式x in v或x ∈ v判断,
结果为布尔值。
函数indexin(a, b)返回向量a的每个元素首次出现在b中的位置,
没有时返回nothing,如:
indexin([1,3,5,3], [1,2,3])4-element Array{Union{Nothing, Int64},1}:
1
3
nothing
3
若v是向量,x是一个元素,
push!(v, x)修改向量v,
将x添加到向量v的末尾。
pushfirst!(v, x)修改向量v,
将x添加到向量v的开头,原有的元素后移。
注意,
函数名以叹号结尾是一个习惯约定,
表示此函数会修改其第一个自变量。
insert!(v, k, xi)函数可以在向量v的指定下标k位置插入指定的一个元素,
原有的元素后移。
如
v3 = [2,3,5]
push!(v3, 7)
@show v3;
## v3 = [2, 3, 5, 7]
pushfirst!(v3, 1)
@show v3;
## v3 = [1, 2, 3, 5, 7]若v是向量,u也是一个向量,
append!(v, u)修改向量v,
将u的所有元素添加到向量v的末尾。
要注意append!和push!的区别,
一个是添加一个向量的所有元素到末尾,
一个是添加一个元素到末尾。
如
v3 = [2,3,5]
append!(v3, [7,11])
@show v3;
## v3 = [2, 3, 5, 7, 11]pop!(v)可以返回v的最后一个元素并从v中删除此元素。
popfirst!(v)类似。
splice!(v, k)函数可以返回指定下标位置的元素并从v中删除此元素,
deleteat!(v, k)函数可以v中删除指定下标位置的元素但不返回值。
empty!(x)可以情况数组的所有元素,
实际上,这个函数可以情况集合、字典等复合类型的元素。
注意,push!()等函数修改输入的向量的大小,
根据使用的环境,
这可能是很高效的做法,
但是数值计算程序中通常不修改数组大小,
而是预先分配好数组的大小。
如果确实无法预先确定数组大小,
又有运行效率的困扰,
可以用如sizehint!(x, 10000)这样的做法为数组预先提示一个大小,
这可以提高程序的效率。
replace!()函数可以用来在数组中替换元素,如:
x = [1, 2, 1, 4, 1]
replace!(x, 1 => 0)
@show x;
## x = [0, 2, 0, 4, 0]x = [1, 2, 1, 4, 1]
replace!(x, 1 => 0, 4 => 3)
@show x;
## x = [0, 2, 0, 3, 0]可以指定一个总替换次数的上限,如:
x = [1, 2, 1, 4, 1]
replace!(x, 1 => 0, 4 => 3, count = 2)
@show x;
## x = [0, 2, 0, 4, 1]如果要合并两个一维数组并将结果生成一个新数组,
不修改原来的两个数组,
可以用vcat()函数,如:
v1 = [1,2]; v2 = [-2, -1]
v3 = vcat(v1, v2)
@show v3;
## v3 = [1, 2, -2, -1]
v3[1] = 111
@show v1;
## v1 = [1, 2]filter!(f, x)指定一个示性函数f,将x中不满足条件的元素删除,
如:
x = [2, 3, 5, 7, 11, 13]
filter!(a -> a % 3 == 1, x)
show(x)
## [7, 13]unique(v)返回去掉重复元素的结果,
unique!(v)则直接去掉v中的重复元素。
sort(v)返回向量v按升序排序的结果;
sort!(v)直接修改v,将其元素按升序排序。
如果要用降序排序,可以加选项rev=true。
sortperm(v)返回将v的元素从小到大排序所需要的下标序列,
在多个等长向量按照其中一个的次序同时排序时此函数有用。
maximum(v)求最大值,
minimum(v)求最小值,
argmax(v)求最大值首次出现的下标,
argmin(v)求最大值首次出现的下标。
findmax(v)和findmin(v)则返回最值和相应的首次出现的下标。
sum(v)求和,
prod(v)求乘积。
对于布尔型数组,
all(v)判断所有元素为真,
any(v)判断存在真值元素。
用x == y可以比较两个等长数组的对应元素是否完全相同。
对于字符串x,
可以用collect(x)将其转换为每个字符为一个元素的数组。
对于元组,
也可以用collect转换成数组。
2.1.6 广播
许多现代的数据分析语言, 如Python, Matlab, R等都存在循环的效率比编译代码低一两个数量级的问题, 在这些语言中, 如果将对向量和矩阵元素的操作向量化, 即以向量和矩阵整体来执行计算, 就可以利用语言内建的向量化计算获得与编译代码相近的执行效率。
Julia语言依靠其LLVM动态编译功能, 对向量和矩阵元素循环时不损失效率, 用显式循环处理向量、矩阵与向量化做法效率相近, 有时显式循环效率更高。 但是,向量化计算的程序代码更简洁。
Julia中的函数, 包括自定义函数, 如果可以对单个标量执行, 将函数名加后缀句点后, 就可以变成向量化版本, 对向量和矩阵执行。 这称为广播。 运算也是如此,运算符前面加点后就可以将标量运算应用到元素之间的运算。 如
sqrt.([1,2,3])3-element Array{Float64,1}:
1.0
1.4142135623730951
1.7320508075688772
这种向量化对于多个自变量的函数也成立。 通过编译优化, 可以达到专用的以向量作为输入的函数的效率, 如果同一个语句中有多个加点运算, 编译时能够将其尽可能地合并到同一循环中。
2.2 元组(Tuple)
2.2.1 概念和生成
与向量类似的一种数据类型称为元组(tuple)。 如
(1, 2, 3)
## (1, 2, 3)
(1, "John", 5.1)
## (1, "John", 5.1)元组的元素不要求属于同一类型。
单个元素的元组要有逗号分隔符,如(1,)是单个元素的元组,
而(1)不是元组。
元组表面上类似于一维数组, 但是元组属于不可修改(immutable)类型, 不能修改其中的元素。 其存储也与数组不同。
可以用tuple()函数生成元组。
可以用类似一维数组的方法对元组取子集,
如x[1], x[2:3]等。
如:
x = ('a', 'b', 'c', 'd')
typeof(x)
## NTuple{4,Char}这个类型的意思是由4个字符组成的元组。
2.2.2 访问片段
x[1]
## 'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)
x[2:3]
## ('b', 'c')不允许修改元组中的元素:
x[2] = 'x'结果出错:
MethodError: no method matching setindex!(::NTuple{4,Char}, ::Char, ::Int64)
.........
2.2.4 赋值等号左边的元组
可以利用元组写法对变量同时赋值, 如
a, b = 13, 17
println("a=", a, " b=", b)
## a=13 b=17这种赋值可以用来交换两个变量的值,如:
a, b = b, a
println("a=", a, " b=", b)
## a=17 b=13元组赋值的右侧也可以是数组等其它序列类型,如
a, b = [19, 23]
println("a=", a, " b=", b)
## a=19 b=232.3 有名元组
元组可以为元素命名, 这使得其在一定程度上类似于字典。 但是,字典是可变类型(可以修改其中的元素), 元组是不可变类型。 如
tn1 = (; name="John", age=32)
tn1[:name]
## "John"
tn1[1]
## "John"定义时用了左括号后面加一个分号的格式。 这是推荐的写法, 当有多个元素时可以省略分号, 但有分号使得定义有名元组的意图更明显。
有名元组经常用来给函数的关键字参数赋值, 而这样的关键字参数的值又是类似关键字参数的, 即内容可有可无,可多可少。
要注意有名元组用变量名访问时用的是符号(Symbol), 即不写成字符串的变量名前面有冒号。 也可以用加点格式访问:
tn1.name
## "John"2.4 字典
2.4.1 生成字典
Julia提供了一种Dict数据类型, 是映射的集合, 每个元素是从一个“键”(key)到另一个“值”(value)的映射, 元素之间没有固定次序。如
d = Dict("name" => "Li Ming", "age" => 18)Dict{String,Any} with 2 entries:
"name" => "Li Ming"
"age" => 18
用Dict()生成空的字典。
也可以用二元组的数组作为初值定义字典,如
d2orig = [('a', 1), ('b', 2), ('c', 3), ('d', 4)]
d2 = Dict(d2orig)Dict{Char,Int64} with 4 entries:
'a' => 1
'c' => 3
'd' => 4
'b' => 2
当键和值分别保存在两个等长的数组中的时候,
可以用zip()函数将这两个数组合并为二元组的数组,
从而产生字典,如:
x = ['a', 'b', 'c', 'd']
y = [1,2,3,4]
d2 = Dict(zip(x, y))Dict{Char, Int64} with 4 entries:
'a' => 1
'c' => 3
'd' => 4
'b' => 2
用length()求长度,如:
length(d2)
## 4字典的键可以用字符串、整数值、浮点数值、元组这样的不可变类型(immutable), 不能取数组这样的可变类型(mutable)。 最常用的是字符串。
上面生成字典的方法是自动判断键和值的数据类型,
为保险起见,
最好在生成字典时指定键和值的数据类型,
格式为Dict{S, T}(...),S为键的类型,T为值的类型。如:
Dict{String, Int64}("apple" => 1, "pear" => 2, "orange" => 3)Dict{String,Int64} with 3 entries:
"pear" => 2
"orange" => 3
"apple" => 1
2.4.2 对(Pair)
事实上,
"apple" => 1这样的写法也是Julia的一种数据类型,
称为“对”(Pair)。
如:
x = "apple" => 1
typeof(x)
## Pair{String, Int64}用first(x)取出对的第一项,
用last(x)取出对的第二项。如:
(first(x), last(x))
## ("apple", 1)可以用collect(dict)将字典转换成键、值二元组的一维数组,如:
collect(d2)4-element Vector{Pair{Char, Int64}}:
'a' => 1
'c' => 3
'd' => 4
'b' => 2
2.4.3 访问元素
访问单个元素如
d = Dict("name" => "Li Ming", "age" => 18)
d["age"]
## 18这种功能类似于R语言中用元素名作为下标, 但R中还可以用序号访问元素, 而字典中的元素没有次序,不能用序号访问。
读取字典中单个键的对应值也可以用get(d, key, default)的格式,
其中default是元素不存在时的返回值。如:
get(d, "age", 0)
## 18可以用haskey(d, key)检查某个键值是否存在,如:
haskey(d, "gender")
## false给不存在的键值赋值就可以增加一对映射,如
d["gender"] = "Male";
@show d;
## d = Dict{String, Any}("name" => "Li Ming",
## "gender" => "Male", "age" => 18)delete!(d, key)可以删除指定的键值对。
get!(d, key, default)可以在指定键值不存在时用default值填入该键值,
已存在时就不做修改,
两种情况下都返回新填入或原有的键对应的值。
pop!(d, key)返回key对应的值并从字典中删除该键值对。
merge(dict1, dict2)合并两个字典,
有共同键时取后一个字典的值。
2.4.4 遍历字典
可以用keys()函数遍历各个键值,次序不确定:
d2 = Dict('a' => 1, 'b' => 2, 'c' => 3, 'd' => 4)
for k in keys(d2)
println(k, " => ", d2[k])
enda => 1
c => 3
d => 4
b => 2
除了可以用haskey(dict, key)判断某个键是否存在,
也可以用key in keys(dict)判断,如:
'a' in keys(d2)
## true'g' ∈ keys(d2)
## false在字典中查找某个键是使用散列表(hash table)技术, 所以查找时间不会随元素个数增长而线性增长, 可以比较方便地存储需要快速查找的键值对。
字典存储并没有固定的存储次序。 为了在遍历时按键值的次序, 需要使用如下的效率较低的方法:
for k in sort(collect(keys(d2)))
println(k, " => ", d2[k])
enda => 1
b => 2
c => 3
d => 4
对字典排序遍历的另一方法是将字典转换成键值对的数组,
然后用sort排序,
再遍历,如:
d2p = collect(d2)
sort!(d2p, by=first)
for (k, v) in d2p
println(k, " ==> ", v)
enda ==> 1
b ==> 2
c ==> 3
d ==> 4
可以用values()遍历各个值,但也没有固定次序。比如
collect(values(d2))4-element Array{Int64,1}:
1
3
4
2
可以直接用二元组对字典遍历,如
for (k,v) in d2
println(k, " => ", v)
enda => 1
c => 3
d => 4
b => 2
可以将字典转换成键值对的列表, 如:
[(k, v) for (k, v) in d2]4-element Vector{Tuple{Char, Int64}}:
('a', 1)
('c', 3)
('d', 4)
('b', 2)
2.4.5 用生成器生成字典
可以用Dict(x => f(x) for x in collection)的方法生成字典,
如:
Dict(x => x*x for x in [2,3,5,7])Dict{Int64,Int64} with 4 entries:
7 => 49
2 => 4
3 => 9
5 => 25
2.4.6 字典应用:频数表
在基本的描述统计中, 经常需要对某个离散取值的变量计算其频数表, 即每个不同值出现的次数。 如果不利用字典类型, 可以先找到所有的不同值, 将每个值与一个序号对应, 然后建立一个一维数组计数, 每个数组元素与一个变量值对应。
利用字典,
我们不需要预先找到所有不同值,而是直接用字典计数,
每个键值是一个不同的变量值,
每个值是一个计数值。
对字典可以用get()函数提取某个键值对应的值,
并在键值不存在时返回指定的缺省值。
如:
sex = ["F", "M", "M", "F", "M"]
freqs = Dict()
for xi in sex
freqs[xi] = get(freqs, xi, 0) + 1
end
freqsDict{Any, Any} with 2 entries:
"M" => 3
"F" => 2
将上述的频数计算功能编写成一个函数如下:
function freqd(x)
y = Dict()
for xi in x
y[xi] = get(y, xi, 0) + 1
end
return y
end
freqd(sex)Dict{Any, Any} with 2 entries:
"M" => 3
"F" => 2
StatsBase包的countmap函数实现了上述的freqd的功能。
如:
using StatsBase
StatsBase.countmap(sex)Dict{String, Int64} with 2 entries:
"M" => 3
"F" => 2
可以看出StatsBase的版本更为合理, 其返回的字典的数据类型更加精确。
有时返回字典类型不方便使用, 可以返回取值和频数分别的列表:
function freq(x)
y = StatsBase.countmap(x)
return keys(y), values(y)
end
freq(sex)
## (["M", "F"], [3, 2])d3 = freq("disillusionment")
d3
## (['n', 'd', 'i', 's', 'l', 'u', 'o', 'm', 'e', 't'],
## [2, 1, 3, 2, 2, 1, 1, 1, 1, 1])因为字典的键必须是不可变类型,
所以freq()中数组x的元素必须是不可变类型。
2.5 集合类型
Julia中Set是集合类型。 集合是可变类型, 没有重复元素, 元素没有次序。
用Set()生成一个集合,如:
Set(1:3)Set{Int64} with 3 elements:
2
3
1
Set([1,2,3])Set{Int64} with 3 elements:
2
3
1
Set(['a', 'b', 'c', 'b'])Set{Char} with 3 elements:
'a'
'c'
'b'
Set("keep")Set{Char} with 3 elements:
'k'
'e'
'p'
Set()输入一个序列(字符串也是序列),
将序列的元素变成集合元素。
注意Set([1,2,3])正确而Set(1,2,3)错误。
因为字符串也是序列, 所以, 要生成只有一个字符串的集合, 也需要将其作为字符串的数组输入,如:
Set(["keep"])Set{String} with 1 element:
"keep"
支持集合的常见运算:
union(A, B)或A ∪ B:并集。∪输入为\cup<TAB>。intersect(A, B)或A ∩ B: 交集。∩输入为\cap<TAB>。setdiff(A, B)或A \ B:差集。symdiff(A, B):对称差集,即\((A \backslash B) \cup (B \backslash A)\)。issetequal(A, B): 集合相等。issubset(A, B)或A ⊆ B:子集,⊆输入为\subseteq<TAB>。⊈(\nsubseteq+TAB)表示非子集。⊇(\supseteq<TAB>):超集。⊉(\nsupseteq<TAB>)表示非超集。- 属于关系用
in,∈(\in+TAB),∋(\ni+TAB),∉(\notin+TAB),∌(\nni+TAB)表示。
例如, 判断某个单词的字母都在另一个单词的字母中:
Set("cat") ⊆ Set("atomic")
## true判断某个单词中有没有重复字母:
length(Set("keep")) < length("keep")
## truepush!(x, a)将元素a加入到集合x中。
对数组x, unique(x)返回由x的不同元素组成的数组。
2.6 自定义复合数据类型
类似于其它编程语言中的struct, class, Julia可以用mutable struct或者struct定义自己的复合数据类型。 例如, 为了表示平面上的一个矩形, 我们需要一个左下角坐标和长度、高度, 就可以定义如下的数据类型:
mutable struct Rectangle
xll::Real
yll::Real
width::Real
height::Real
end这定义了一个新的数据类型Rectangle。
命名的惯例是使用大写字母开头。
其中的xll, yll, width, height称为这个复合数据结构的“属性”。
生成这个类型的变量:
rect1 = Rectangle(0, 0, 2, 1)这表示左下角坐标为\((0,0)\),宽度为2,高度为1的一个矩形。
用变量名.属性名的格式访问其中的属性,如:
rect1 = Rectangle(0, 0, 2, 1)
rect1.width
## 2可以针对这样的自定义类型定义相应的运算和函数, 比如, 平移运算的函数:
function move(rect::Rectangle, offset)
Rectangle(rect.xll + offset[1],
rect.yll + offset[2],
rect.width, rect.height)
end测试:
rect2 = move(rect1, (20, 10))
## Rectangle(20, 10, 2, 1)用struct定义的复合数据,
其中的属性不允许修改。
用mutable struct定义的复合数据,
其中的属性是可以修改的。