6.5.3 成员访问及不可变性
构造出复合类型的实例对象后,便可通过成员访问符(英文句号)访问其内部成员,形式为:
对象名.字段名
注意 “对象名”与“成员访问符”之间不能有空格。
对于上文中FooA对象的x变量,有:
julia> x.a 1 julia> x.b 2.5
但是如果试图修改其成员的内容,会报错:
julia> x.a = 10 ERROR: type FooA is immutable julia> x.b = 3.8 ERROR: type FooA is immutable
这是因为FooA在声明时,没有使用mutable进行标识。不过,前文中的FooB类型在声明时,是有mutable标识的。我们创建一个FooB对象,如下:
julia> y = FooB(2, 3.5) FooB(2, 3.5) julia> y.a 2 julia> y.b 3.5
再尝试修改其成员值:
julia> y.a = 2.9 # 类型由Int64变为Float64 2.9 julia> y.b = 2//3 2//3 julia> y FooB(2.9, 0.6666666666666666) # 有理数被提升为浮点数
可见,成员被成功修改了。
此外,由于y中的字段b限定了类型,所以即使修改时提供了别的类型,新的值也会保持类型不变。但这种操作需要保证提供的新值能被提升为限定的类型,否则会报错,例如:
julia> y.a = 3.2 # 字段a是Any类型,所以正常执行 3.2 julia> y.b = 3+2im # 字段b限定为浮点型,但给定的复数型是比浮点型“更大”的类型,故错误 ERROR: InexactError: Float64(Float64, 3 + 2im)
但是,可以将成员的类型限定为抽象类型,以提高其相容性,例如:
julia> mutable struct FooC a::Int32 b::Real # 限定为抽象类型,可以为实数的任意子类型 end julia> w = FooC(1, 2); julia> typeof(w.b) Int64 # 实际类型是Int64 julia> w.b = 3.2; julia> typeof(w.b) Float64 # 类型被改变
其中,定义的FooC成员b限定为Real类型,所以赋值为Int64或Float64类型均可。显然,成员b仍不能赋值为复数型,即:
julia> w.b = 3+2im # Complex{Int64} ERROR: InexactError: Real(Real, 3 + 2im)
会报错,因为复数不是Real类型的子类型。
如果成员有复合类型的Bar,将上例中的FooB对象y作为参数对其实例化,例如:
julia> z = Bar(y, 20) Bar(FooB(2.9, 0.6666666666666666), 20)
详细查看其成员内容,如下:
julia> z.m FooB(2.9, 0.6666666666666666) julia> z.n 20
同样,Bar对象z也是不可变的:
julia> z.m = FooB(2.3, 3.8) ERROR: type Bar is immutable julia> z.n = 30 ERROR: type Bar is immutable
但其内部的FooB却是可变的,即:
julia> z.m.a = 2.5 2.5 julia> z.m.b = 5.0 5.0
可见,struct结构的标识符mutable只控制着本层的不可变性,不会波及内层或外层,即不可变性不会在上下层之间传播。
不可变的复合类型是Julia对结构内权限进行控制的一种方式。相对于可变复合类型,编译器在处理不可变类型时能够高效地处理内部的存储结构,而且能够推导出代码中使用了哪些不可变对象,从而提高了编译效率。
另外,正因为不可变对象内部成员的值是恒定的,所以值的组合便可用于区分不同的对象。相对地,可变对象的内容随时都会改变,不变的仅是其在堆中的内存地址,所以地址是区分它们的唯一可信的依据。更需要注意的一点是,不可变对象在赋值或函数传参时均采用“值拷贝”的方式,而可变对象则会采用引用的方式。
提示 在开发过程中,使用可变对象还是不可变对象,可以参考以下两点:
·当两个对象具备相同的成员值集合时,是否需要识别为不同的事物。
·对象之间是否会随时独立地进行值的变换。
如果以上的答案都是“否”,则建议使用不可变类型。