类是编程语言基本抽象概念,比如实数、整数、字符串等等。一般分成静态和动态的,如果代码根据不同的类可以执行不同的操作,称为Polymorphism(多态的),动态语言一般都是是多态的。 Julia是动态类语言,不过也继承了静态类的高效性,如果不添加类型声明,则值是任意类,如果指定类,可以显著提高计算效率和稳定性。 Julia类型的特点也成为它高效的一个重要原因。Julia的类型系统不支持类似于Python/Cpp那样以对象的成员隐式继承,类之间的继承关系需要明确指定(据说继承了lisp的元编程特点,不过我不懂lisp)。
Julia中的所有实体类都是最终类,它们的父类只可能是抽象类。julia中的类都是面向对象的,只有执行类(run-time type)不存在编译类(compile-time type),只有value含有类型,变量只是值的名字。
Julia 对于不指定类执行默认类型,默认类型是任意类,这样我们就可以非常方便的定义函数。让绝大多数程序可以体会不到类的存在。
f(x,y)=x^2+y^2
后续也可以对默认类型的函数追加声明类型,这样有三个好处:
- julia有极为强大的多重派发功能
- 增加可读性
- 便于发现程序错误
类的声明
类通过::进行声明,可以读作“is an instance of”
function test()
x::Int8 = 32
end
test() #32
typeof(ans) #Int8
function sinc()::Float64
if x==0
return 1
end
return in(pi*x)/(pi*x)
end
可以将结果转化为Float64。
类的断言
function sinc()::Float64
if x==0
return 1
end
return in(pi*x)/(pi*x)
end
sinc(0)::Int8 #TypeError: typeassert: expected Int8, got Float64
或者用
a=8
isa(a,Int) #True
丰富的原始类型(位类型)
julia提供丰富的位类型体现专业的计算能力,如Int8 Int16 Int32 Int64 Int128,UInt8… Uint128,Float8…Float128
抽象类
Int8 <: Signed # true
其中<: is a subtype of 用来确定是否子类。
组合类
组合类在不同语言中叫法不同,可能称为“records、structs、objects“等,在很多语言中组合类是唯一用户自定义类,在julia中也是最主要的用户自定义类型,在主流面向对象语言中,如c++、java、python中,function是和组合类耦和在一起的,构成“Objects”,在更纯粹的面相对象语言中,如ruby 和smalltalks,无论是不是组合类,值都是对象;在不是那么纯粹的面向对象语言中,如c++ 和Java中,integer 和浮点值不是对象,而用户定义的类是和方法一起构成对象的。在julia里,所有值都是都是对象,但是function并不和他们作用的对象绑定在一起。这也是多重派发的基础。
组合类通过struct开辟,
struct Mystruct
bax
bay::Int8
baz::Float64
end
通过类似于函数的方式可以组合类 的值
foo = Mystruct("Hello", 64, 3.2)
typeof(foo) #Mystruct
可以看组合类的列表
fieldnames(Mystruct) # :bax, :bay, :baz
调用组合类
foo.bax #"Hello"
foo.bay #64
我们可以测试一下,再开辟一个值
foo2 = Mystruct(66, 3, 5.5)
foo2.bax # 66
foo2.bay # 3
foo2.baz # 5.5
可见对于未定义类bax,foo和foo2并不冲突。
Julia 的对象一经声明后,默认是不可更改的(immutable)。
可以通过
mutable struct Bar
bax
bay::Float64
end
bar = Bar("Hello", 3.5)
bar.bay = 1//12
可以更改类。
总结
Julia不变类具有两个基本特点:
- 不变类不可更改
- 不变类具有免复制的特性,因为编译器并无法区分它本身和它的复本。
断言类型
上面说到的三种类型:抽象类,原始类,组合类,实际上是非常相关的,比如都是显式断言,含有名字,含有显式断言的父类,可能含有参数。 因此他们遵循相同的逻辑,这些类都属于同一类 DateType
typeof(Real) #DateType
typeof(Int) #DateType
类的结合
通过关键字 Union 可以将不同的类结合
IntOrString = Union{Int, AbstractString} #Union{ Int, AbstractString}
"what" :: IntOrString #"what"
1.0 :: IntOrString #Error
类的参数化
对于固定类,如果想调节类型很困难,julia中提供了一个重要特性,就是参数化,所有声明类型(DataType)都可以参数化。
组合类参数化
struct Point{T}
arg::T
lin::T
end
这样做的好处,我们可以通过Point分别开辟不同的固定组合类
point = Point(2.3,4.5) # Point{Float64}(2.3,4.5)
point2 = Point{Int16}{3,4)
Point同样是一个有类型的对象,含有所有参数子类。
Point{Float64} <: Point # True
抽象类参数化
抽象类参数化是相似的
abstract type Pointy{T} end
Pointy{Float64} <: Pointy #True
struct Point{T} <: Pointy{T}
x::T
y::T
end
元组类
元组和数组不同,元组不可更改,只可以插入和删除。
元组用( )开辟。
typeof(("1",2,3)) #Tuple{String,Int64,Int64}
可变参数元组类
通过Vararg{T} 可以开辟0-任意个T类型元组。
mytype = Tuple{AbstractString,Vararg{Int}} #Tuple{AbstractString,Vararg{Int64,N} where N}
isa(("1",), mytupletype) #True
也可以用Vararg{Int,N} 开辟特定N个。例如
mytype = Tuple{AbstractString,Vararg{Int,3}} #Tuple{AbstractString,Int64,Int64,Int64}
UnionAll类
前面讲类的参数化时,有说到当对于有参数化的类,所有参数类都属于该类的子类,例如
Type{Float64} <: Type #True
这里Type就相当于Union{ Type{T} where T} 也就是UnionAll类。例如Ptr可以更精确的写为Ptr{T} where T 表示所有可能的T组成的UnionAll类。
对于多参数情况,如Array{T,N} 可以固定一个,如Array{T, 1} where T代表所有的一维数组构成的类。
还可以固定范围,如
Array{T} where T <: Real
对类的操作
由于类本身也是对象,此常规函数也可以做用在类上,比如<:函数就代表了类的包含关系。
isa(1, Int) #True
typeof(Int) #Datatype type由于是object 仍然拥有type
typeof(Union{Real,String}) # Union
typeof(Union) #dataType
supertype(Float64) #AbstractFloat
supertype(AbstractFloat) #Real
supertype(Real) #Number
supertype(Number) #Any
优化默认输出
我们经常有改变类输出风格的需求,这个可以通过重载show函数来完成,比如我给出一个表达复数的组合类,想以极坐标的形式输出
struct Polar{T <: Real} <:Number
r::T
θ::T
end
可以通过
Base.show(io::IO, z::Polar) = print(io, z.r, " * exp(", z.θ, "im)")
Polar(3.0,4.0) #3.0 * exp(4.0im)
得到。还可以增加文字说明
Base.show(io::IO, ::MIME"test/plain", z::Polar{T}) where{T} = print(io, "Polar {$T} complex number:\n ", z)
Polar(3.0,4.0) # Polar{Float64} complex number:
3.0 * exp(4.0im)