Julia 構造函數(shù)

2018-08-12 21:25 更新

構造函數(shù)

構造函數(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ù)值賦給 barbaz 域:

Foo(x) = Foo(x,x)

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

添加 Foo 的零參構造方法,給 barbaz 域賦默認值:

Foo() = Foo(0)

julia> Foo()
Foo(0,0)

這種追加的構造方法被稱為 外部 構造方法。它僅能通過提供默認值的方式,調(diào)用其它構造方法來構造實例。

內(nèi)部構造方法

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

  1. 它在類型聲明塊內(nèi)部被聲明,而不是像普通方法一樣在外部被聲明
  2. 它調(diào)用本地已存在的 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

如果 aSelfReferential 的實例,則可以如下構造第二個實例:

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ù)化構造方法

參數(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)

案例:分數(shù)

下面是 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
以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號