Julia 構(gòu)造函數(shù)

2018-08-12 21:25 更新

構(gòu)造函數(shù)

構(gòu)造函數(shù)[1]是構(gòu)造新對(duì)象,即新復(fù)合類(lèi)型實(shí)例的函數(shù)。構(gòu)造類(lèi)型對(duì)象:

type Foo
  bar
  baz
end

julia> foo = Foo(1,2)
Foo(1,2)

julia> foo.bar
1

julia> foo.baz
2

遞歸數(shù)據(jù)結(jié)構(gòu) ,尤其是自引用的數(shù)據(jù)結(jié)構(gòu),常需要先構(gòu)造為非完整狀態(tài),再按步驟將其完善。我們有時(shí)也可能希望用更少或不同類(lèi)型的參數(shù)更方便的構(gòu)造對(duì)象。Julia 的構(gòu)造函數(shù)可以讓包括這些在內(nèi)的各種需求得到滿(mǎn)足。

[1] :關(guān)于命名:盡管“構(gòu)造函數(shù)”通常被用來(lái)描述創(chuàng)建新對(duì)象的函數(shù),它也經(jīng)常被濫用于特定的構(gòu)造方法。通常情況下,可以很容易地從上下文推斷出到底是“構(gòu)造函數(shù)”還是“構(gòu)造方法”。

外部構(gòu)造方法

構(gòu)造函數(shù)與 Julia 中的其它函數(shù)一樣,它的行為取決于它全部方法的行為的組合。因此,你可以通過(guò)定義新方法來(lái)給構(gòu)造函數(shù)增加新性能。下例給 Foo 添加了新構(gòu)造方法,僅輸入一個(gè)參數(shù),將該參數(shù)值賦給 barbaz 域:

Foo(x) = Foo(x,x)

julia> Foo(1)
Foo(1,1)

添加 Foo 的零參構(gòu)造方法,給 barbaz 域賦默認(rèn)值:

Foo() = Foo(0)

julia> Foo()
Foo(0,0)

這種追加的構(gòu)造方法被稱(chēng)為 外部 構(gòu)造方法。它僅能通過(guò)提供默認(rèn)值的方式,調(diào)用其它構(gòu)造方法來(lái)構(gòu)造實(shí)例。

內(nèi)部構(gòu)造方法

內(nèi)部 構(gòu)造方法與外部構(gòu)造方法類(lèi)似,但有兩個(gè)區(qū)別:

  1. 它在類(lèi)型聲明塊內(nèi)部被聲明,而不是像普通方法一樣在外部被聲明
  2. 它調(diào)用本地已存在的 new 函數(shù),來(lái)構(gòu)造聲明塊的類(lèi)型的對(duì)象

例如,要聲明一個(gè)保存實(shí)數(shù)對(duì)的類(lèi)型,且第一個(gè)數(shù)不大于第二個(gè)數(shù):

type OrderedPair
  x::Real
  y::Real

  OrderedPair(x,y) = x > y ? error("out of order") : new(x,y)
end

僅當(dāng) x <= y 時(shí),才會(huì)構(gòu)造 OrderedPair 對(duì)象:

julia> OrderedPair(1,2)
OrderedPair(1,2)

julia> OrderedPair(2,1)
ERROR: out of order
 in OrderedPair at none:5

所有的外部構(gòu)造方法,最終都會(huì)調(diào)用內(nèi)部構(gòu)造方法。

當(dāng)然,如果類(lèi)型被聲明為 immutable ,它的構(gòu)造函數(shù)的結(jié)構(gòu)就不能變了。這對(duì)判斷一個(gè)類(lèi)型是否應(yīng)該是 immutable 時(shí)很重要。

如果定義了內(nèi)部構(gòu)造方法,Julia 將不再提供默認(rèn)的構(gòu)造方法。默認(rèn)的構(gòu)造方法等價(jià)于一個(gè)自定義內(nèi)部構(gòu)造方法,它將對(duì)象的所有域作為參數(shù)(如果對(duì)應(yīng)域有類(lèi)型,應(yīng)為具體類(lèi)型),傳遞給 new ,最后返回結(jié)果對(duì)象:

type Foo
  bar
  baz

  Foo(bar,baz) = new(bar,baz)
end

這個(gè)聲明與前面未指明內(nèi)部構(gòu)造方法的 Foo 是等價(jià)的。下面兩者也是等價(jià)的,一個(gè)使用默認(rèn)構(gòu)造方法,一個(gè)寫(xiě)明了構(gòu)造方法:

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)部構(gòu)造方法能不寫(xiě)就不寫(xiě)。提供默認(rèn)值之類(lèi)的事兒,應(yīng)該寫(xiě)成外部構(gòu)造方法,由它們調(diào)用內(nèi)部構(gòu)造方法。

部分初始化

考慮如下遞歸類(lèi)型聲明:

type SelfReferential
  obj::SelfReferential
end

如果 aSelfReferential 的實(shí)例,則可以如下構(gòu)造第二個(gè)實(shí)例:

b = SelfReferential(a)

但是,當(dāng)沒(méi)有任何實(shí)例來(lái)為 obj 域提供有效值時(shí),如何構(gòu)造第一個(gè)實(shí)例呢?唯一的解決方法是構(gòu)造 obj 域未賦值的 SelfReferential 部分初始化實(shí)例,使用這個(gè)實(shí)例作為另一個(gè)實(shí)例(如它本身)中 obj 域的有效值。

構(gòu)造部分初始化對(duì)象時(shí),Julia 允許調(diào)用 new 函數(shù)來(lái)處理比該類(lèi)型域個(gè)數(shù)少的參數(shù),返回部分域未初始化的對(duì)象。這時(shí),內(nèi)部構(gòu)造函數(shù)可以使用這個(gè)不完整的對(duì)象,并在返回之前完成它的初始化。下例中,我們定義 SelfReferential 類(lèi)型時(shí),使用零參內(nèi)部構(gòu)造方法,返回一個(gè) obj 域指向它本身的實(shí)例:

type SelfReferential
  obj::SelfReferential

  SelfReferential() = (x = new(); x.obj = x)
end

此構(gòu)造方法可以運(yùn)行并構(gòu)造自引對(duì)象:

julia> x = SelfReferential();

julia> is(x, x)
true

julia> is(x, x.obj)
true

julia> is(x, x.obj.obj)
true

內(nèi)部構(gòu)造方法最好返回完全初始化的對(duì)象,但也可以返回部分初始化對(duì)象:

julia> type Incomplete
         xx
         Incomplete() = new()
       end

julia> z = Incomplete();

盡管可以構(gòu)造未初始化域的對(duì)象,但讀取未初始化的引用會(huì)報(bào)錯(cuò):

julia> z.xx
ERROR: access to undefined reference

這避免了持續(xù)檢查 null 值。但是,所有對(duì)象的域都是引用。Julia 認(rèn)為一些類(lèi)型是“普通數(shù)據(jù)”,即他們的數(shù)據(jù)都是獨(dú)立的,都不引用其他的對(duì)象。普通數(shù)據(jù)類(lèi)型是由位類(lèi)型或者其他普通數(shù)據(jù)類(lèi)型的不可變數(shù)據(jù)結(jié)構(gòu)所構(gòu)成的(例如 Int )。普通數(shù)據(jù)類(lèi)型的初始內(nèi)容是未定義的: ::

julia> type HasPlain
         n::Int
         HasPlain() = new()
       end

julia> HasPlain()
HasPlain(438103441441)

普通數(shù)據(jù)類(lèi)型所構(gòu)成的數(shù)組具有相同的行為。

可以在內(nèi)部構(gòu)造方法中,將不完整的對(duì)象傳遞給其它函數(shù),來(lái)委托完成全部初始化:

type Lazy
  xx

  Lazy(v) = complete_me(new(), v)
end

如果 complete_me 或其它被調(diào)用的函數(shù)試圖在初始化 Lazy 對(duì)象的 xx 域之前讀取它,將會(huì)立即報(bào)錯(cuò)。

參數(shù)化構(gòu)造方法

參數(shù)化構(gòu)造方法的例子:

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ù)化構(gòu)造方法等價(jià)于下面的聲明:

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)部構(gòu)造方法只定義 Point{T} 的方法,而非 Point 的構(gòu)造函數(shù)的方法。 Point 不是具體類(lèi)型,不能有內(nèi)部構(gòu)造方法。外部構(gòu)造方法定義了 Point 的構(gòu)造方法。

可以將整數(shù)值 1 “提升”為浮點(diǎn)數(shù) 1.0 ,來(lái)完成構(gòu)造:

julia> Point(x::Int64, y::Float64) = Point(convert(Float64,x),y);

這樣下例就可以正常運(yùn)行:

julia> Point(1,2.5)
Point{Float64}(1.0,2.5)

julia> typeof(ans)
Point{Float64} (constructor with 1 method)

但下例仍會(huì)報(bào)錯(cuò):

julia> Point(1.5,2)
ERROR: `Point{T<:Real}` has no method matching Point{T<:Real}(::Float64, ::Int64)

其實(shí)只需定義下列外部構(gòu)造方法:

julia> Point(x::Real, y::Real) = Point(promote(x,y)...);

promote 函數(shù)將它的所有參數(shù)轉(zhuǎn)換為相同類(lèi)型?,F(xiàn)在,所有的實(shí)數(shù)參數(shù)都可以正常運(yùn)行:

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)

案例:分?jǐn)?shù)

下面是 rational.jl 文件的開(kāi)頭部分,它實(shí)現(xiàn)了 Julia 的分?jǐn)?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

復(fù)數(shù)分?jǐn)?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
以上內(nèi)容是否對(duì)您有幫助:
在線(xiàn)筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)