構造函數(shù)[1]是構造新對象,即新復合類型實例的函數(shù)。構造類型對象:
type Foo
bar
baz
end
julia> foo = Foo(1,2)
Foo(1,2)
julia> foo.bar
1
julia> foo.baz
2
遞歸數(shù)據(jù)結構 ,尤其是自引用的數(shù)據(jù)結構,常需要先構造為非完整狀態(tài),再按步驟將其完善。我們有時也可能希望用更少或不同類型的參數(shù)更方便的構造對象。Julia 的構造函數(shù)可以讓包括這些在內(nèi)的各種需求得到滿足。
[1] :關于命名:盡管“構造函數(shù)”通常被用來描述創(chuàng)建新對象的函數(shù),它也經(jīng)常被濫用于特定的構造方法。通常情況下,可以很容易地從上下文推斷出到底是“構造函數(shù)”還是“構造方法”。
構造函數(shù)與 Julia 中的其它函數(shù)一樣,它的行為取決于它全部方法的行為的組合。因此,你可以通過定義新方法來給構造函數(shù)增加新性能。下例給 Foo
添加了新構造方法,僅輸入一個參數(shù),將該參數(shù)值賦給 bar
和 baz
域:
Foo(x) = Foo(x,x)
julia> Foo(1)
Foo(1,1)
添加 Foo
的零參構造方法,給 bar
和 baz
域賦默認值:
Foo() = Foo(0)
julia> Foo()
Foo(0,0)
這種追加的構造方法被稱為 外部 構造方法。它僅能通過提供默認值的方式,調(diào)用其它構造方法來構造實例。
內(nèi)部 構造方法與外部構造方法類似,但有兩個區(qū)別:
new
函數(shù),來構造聲明塊的類型的對象例如,要聲明一個保存實數(shù)對的類型,且第一個數(shù)不大于第二個數(shù):
type OrderedPair
x::Real
y::Real
OrderedPair(x,y) = x > y ? error("out of order") : new(x,y)
end
僅當 x <= y
時,才會構造 OrderedPair
對象:
julia> OrderedPair(1,2)
OrderedPair(1,2)
julia> OrderedPair(2,1)
ERROR: out of order
in OrderedPair at none:5
所有的外部構造方法,最終都會調(diào)用內(nèi)部構造方法。
當然,如果類型被聲明為 immutable
,它的構造函數(shù)的結構就不能變了。這對判斷一個類型是否應該是 immutable 時很重要。
如果定義了內(nèi)部構造方法,Julia 將不再提供默認的構造方法。默認的構造方法等價于一個自定義內(nèi)部構造方法,它將對象的所有域作為參數(shù)(如果對應域有類型,應為具體類型),傳遞給 new
,最后返回結果對象:
type Foo
bar
baz
Foo(bar,baz) = new(bar,baz)
end
這個聲明與前面未指明內(nèi)部構造方法的 Foo
是等價的。下面兩者也是等價的,一個使用默認構造方法,一個寫明了構造方法:
type T1
x::Int64
end
type T2
x::Int64
T2(x) = new(x)
end
julia> T1(1)
T1(1)
julia> T2(1)
T2(1)
julia> T1(1.0)
T1(1)
julia> T2(1.0)
T2(1)
內(nèi)部構造方法能不寫就不寫。提供默認值之類的事兒,應該寫成外部構造方法,由它們調(diào)用內(nèi)部構造方法。
考慮如下遞歸類型聲明:
type SelfReferential
obj::SelfReferential
end
如果 a
是 SelfReferential
的實例,則可以如下構造第二個實例:
b = SelfReferential(a)
但是,當沒有任何實例來為 obj
域提供有效值時,如何構造第一個實例呢?唯一的解決方法是構造 obj
域未賦值的 SelfReferential
部分初始化實例,使用這個實例作為另一個實例(如它本身)中 obj
域的有效值。
構造部分初始化對象時,Julia 允許調(diào)用 new
函數(shù)來處理比該類型域個數(shù)少的參數(shù),返回部分域未初始化的對象。這時,內(nèi)部構造函數(shù)可以使用這個不完整的對象,并在返回之前完成它的初始化。下例中,我們定義 SelfReferential
類型時,使用零參內(nèi)部構造方法,返回一個 obj
域指向它本身的實例:
type SelfReferential
obj::SelfReferential
SelfReferential() = (x = new(); x.obj = x)
end
此構造方法可以運行并構造自引對象:
julia> x = SelfReferential();
julia> is(x, x)
true
julia> is(x, x.obj)
true
julia> is(x, x.obj.obj)
true
內(nèi)部構造方法最好返回完全初始化的對象,但也可以返回部分初始化對象:
julia> type Incomplete
xx
Incomplete() = new()
end
julia> z = Incomplete();
盡管可以構造未初始化域的對象,但讀取未初始化的引用會報錯:
julia> z.xx
ERROR: access to undefined reference
這避免了持續(xù)檢查 null
值。但是,所有對象的域都是引用。Julia 認為一些類型是“普通數(shù)據(jù)”,即他們的數(shù)據(jù)都是獨立的,都不引用其他的對象。普通數(shù)據(jù)類型是由位類型或者其他普通數(shù)據(jù)類型的不可變數(shù)據(jù)結構所構成的(例如 Int
)。普通數(shù)據(jù)類型的初始內(nèi)容是未定義的: ::
julia> type HasPlain
n::Int
HasPlain() = new()
end
julia> HasPlain()
HasPlain(438103441441)
普通數(shù)據(jù)類型所構成的數(shù)組具有相同的行為。
可以在內(nèi)部構造方法中,將不完整的對象傳遞給其它函數(shù),來委托完成全部初始化:
type Lazy
xx
Lazy(v) = complete_me(new(), v)
end
如果 complete_me
或其它被調(diào)用的函數(shù)試圖在初始化 Lazy
對象的 xx
域之前讀取它,將會立即報錯。
參數(shù)化構造方法的例子:
julia> type Point{T<:Real}
x::T
y::T
end
## implicit T ##
julia> Point(1,2)
Point{Int64}(1,2)
julia> Point(1.0,2.5)
Point{Float64}(1.0,2.5)
julia> Point(1,2.5)
ERROR: `Point{T<:Real}` has no method matching Point{T<:Real}(::Int64, ::Float64)
## explicit T ##
julia> Point{Int64}(1,2)
Point{Int64}(1,2)
julia> Point{Int64}(1.0,2.5)
ERROR: InexactError()
julia> Point{Float64}(1.0,2.5)
Point{Float64}(1.0,2.5)
julia> Point{Float64}(1,2)
Point{Float64}(1.0,2.0)
上面的參數(shù)化構造方法等價于下面的聲明:
type Point{T<:Real}
x::T
y::T
Point(x,y) = new(x,y)
end
Point{T<:Real}(x::T, y::T) = Point{T}(x,y)
內(nèi)部構造方法只定義 Point{T}
的方法,而非 Point
的構造函數(shù)的方法。 Point
不是具體類型,不能有內(nèi)部構造方法。外部構造方法定義了 Point
的構造方法。
可以將整數(shù)值 1
“提升”為浮點數(shù) 1.0
,來完成構造:
julia> Point(x::Int64, y::Float64) = Point(convert(Float64,x),y);
這樣下例就可以正常運行:
julia> Point(1,2.5)
Point{Float64}(1.0,2.5)
julia> typeof(ans)
Point{Float64} (constructor with 1 method)
但下例仍會報錯:
julia> Point(1.5,2)
ERROR: `Point{T<:Real}` has no method matching Point{T<:Real}(::Float64, ::Int64)
其實只需定義下列外部構造方法:
julia> Point(x::Real, y::Real) = Point(promote(x,y)...);
promote
函數(shù)將它的所有參數(shù)轉換為相同類型。現(xiàn)在,所有的實數(shù)參數(shù)都可以正常運行:
julia> Point(1.5,2)
Point{Float64}(1.5,2.0)
julia> Point(1,1//2)
Point{Rational{Int64}}(1//1,1//2)
julia> Point(1.0,1//2)
Point{Float64}(1.0,0.5)
下面是 rational.jl 文件的開頭部分,它實現(xiàn)了 Julia 的分數(shù):
immutable Rational{T<:Integer} <: Real
num::T
den::T
function Rational(num::T, den::T)
if num == 0 && den == 0
error("invalid rational: 0//0")
end
g = gcd(den, num)
num = div(num, g)
den = div(den, g)
new(num, den)
end
end
Rational{T<:Integer}(n::T, d::T) = Rational{T}(n,d)
Rational(n::Integer, d::Integer) = Rational(promote(n,d)...)
Rational(n::Integer) = Rational(n,one(n))
//(n::Integer, d::Integer) = Rational(n,d)
//(x::Rational, y::Integer) = x.num // (x.den*y)
//(x::Integer, y::Rational) = (x*y.den) // y.num
//(x::Complex, y::Real) = complex(real(x)//y, imag(x)//y)
//(x::Real, y::Complex) = x*y'//real(y*y')
function //(x::Complex, y::Complex)
xy = x*y'
yy = real(y*y')
complex(real(xy)//yy, imag(xy)//yy)
end
復數(shù)分數(shù)的例子:
julia> (1 + 2im)//(1 - 2im)
-3//5 + 4//5*im
julia> typeof(ans)
Complex{Rational{Int64}} (constructor with 1 method)
julia> ans <: Complex{Rational}
false
更多建議: