構(gòu)造過程

本頁包含內(nèi)容:

[TOC]

構(gòu)造過程是使用類、結(jié)構(gòu)體或枚舉類型的實例之前的準備過程。在新實例可用前必須執(zhí)行這個過程,具體操作包含設(shè)置實例中每個存儲型屬性的初始值和執(zhí)行其他必須的設(shè)置或初始化工作。

通過定義構(gòu)造器來實現(xiàn)構(gòu)造過程,就像用來創(chuàng)建特定類型新實例的特殊方法。與Objective-C中的構(gòu)造器不同,Swift的構(gòu)造器無需返回值,它們的主要任務(wù)是保證新實例在第一次使用前完成正確的初始化。

類的實例也可以通過定義析構(gòu)器在實例釋放之前執(zhí)行特定的清除工作。

存儲屬性的初始賦值

類和結(jié)構(gòu)體在創(chuàng)建實例時,必須為所有存儲型屬性設(shè)置合適的初始值。存儲型屬性的值不能處于一個未知狀態(tài)。

你可以在構(gòu)造器中為存儲型屬性賦初值,也可以在定義屬性時為其設(shè)置默認值。

注意:當你為存儲型屬性設(shè)置默認值或者在構(gòu)造器中為其賦值時,它們的值是被直接設(shè)置的,不會觸發(fā)任何屬性觀察器。

構(gòu)造器

構(gòu)造器在創(chuàng)建某個特定類型的新實例時被調(diào)用。它的最簡形式類似于一個不帶任何參數(shù)的實例方法,以關(guān)鍵字init命名:

init() {
    // 在此處執(zhí)行構(gòu)造過程
}

下面例子中定義了一個用來保存華氏溫度的結(jié)構(gòu)體Fahrenheit,它擁有一個Double類型的存儲型屬性temperature

struct Fahrenheit {
    var temperature: Double
    init() {
        temperature = 32.0
    }
}

var f = Fahrenheit()
print("The default temperature is \(f.temperature)° Fahrenheit")
// 輸出 "The default temperature is 32.0° Fahrenheit"

這個結(jié)構(gòu)體定義了一個不帶參數(shù)的構(gòu)造器init,并在里面講存儲型屬性temperature的值初始化為32.0(華氏溫度下水的冰點)。

默認屬性值

如前所述,你可以在構(gòu)造器中為存儲型屬性設(shè)置初始值。同樣,你也可以在屬性聲明時為其設(shè)置默認值。

注意:如果一個屬性總是使用相同的初始值,那么為其設(shè)置一個默認值比每次都在構(gòu)造器中賦值要好。兩種方法的效果是一樣的,只不過使用默認值讓屬性的初始化和聲明結(jié)合得更緊密。使用默認值能讓你的構(gòu)造器更簡潔。更清晰,且能通過默認值自動推導(dǎo)出屬性的類型;同時,它也能讓你充分利用默認構(gòu)造器、構(gòu)造器繼承等特性,后續(xù)章節(jié)將講到。

你可以使用更簡短的方式在定義結(jié)構(gòu)體Fahrenheit時為屬性temperature設(shè)置默認值:

struct Fahrenheit {
    var temperature = 32.0
}

自定義構(gòu)造過程

你可以通過輸入?yún)?shù)和可選類型的屬性來自定義構(gòu)造過程,也可以在構(gòu)造過程中給常量屬性賦初值。

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

自定義構(gòu)造過程時,可以在定義中提供構(gòu)造參數(shù),指定參數(shù)值的類型和名字。構(gòu)造參數(shù)的功能和語法跟函數(shù)的方法的參數(shù)相同。

下面例子中定義了一個包含攝氏溫度的結(jié)構(gòu)體Celsius。它定義了兩個不同的構(gòu)造器:init(formFahrenheit:)init(formKelvin:),二者分別通過接受不同溫標下的溫度來創(chuàng)建新的實例:

struct Celsius {
    var temperatureInCelsius: Double
    init(formFahrenheit fahrenheit: Double) {
    temperatureInCelsius = (fahrenheit - 32.0) / 1.8
    }
    init(fromKelvin kelvin: Double) {
        temperatureInCelsius = kelvin - 273.15
    }
}

let boilingPointOfWater = Celsius(fromFahrenheit: 212.0)
// boilingPointOfWater.temperatureInCelsius 是 100.0
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsius 是 0.0

第一個構(gòu)造器擁有一個構(gòu)造參數(shù),其外部名字為fromFahrenheit,內(nèi)部名字為fahrenheit;第二個構(gòu)造器也擁有一個構(gòu)造參數(shù),其外部名字為fromKelvin,內(nèi)部名字為kelvin。這兩個構(gòu)造器都將唯一的參數(shù)值轉(zhuǎn)換成攝氏溫度值,并保存在屬性 temperatureInCelsius 中。

參數(shù)的內(nèi)部名稱和外部名稱

跟函數(shù)和方法參數(shù)相同,構(gòu)造參數(shù)也擁有一個在構(gòu)造器內(nèi)部內(nèi)部使用的參數(shù)名字和一個在調(diào)用時使用的外部參數(shù)名字。

然而,構(gòu)造器并不像函數(shù)和方法那樣在括號前有一個可辨別的名字。因此在調(diào)用構(gòu)造器時,主要通過構(gòu)造器中的參數(shù)名和類型來確定應(yīng)該被調(diào)用的構(gòu)造器。正因為參數(shù)如此重要,如果你在定義構(gòu)造器時沒有提供參數(shù)的外部名字,Swift會為構(gòu)造器的每個參數(shù)自動生成一個跟內(nèi)部名字相同的外部名。

以下例子中定義了一個結(jié)構(gòu)體 Color,它包含了三個常量:red、greenblue。這些屬性可以存儲 0.01.0之間的值,用來指示顏色中紅、綠、藍成分的含量。

Color 提供了一個構(gòu)造器,其中包含三個Double類型的構(gòu)造參數(shù)。Color 也提供了第二個構(gòu)造器,它只包含名為whiteDouble 類型的參數(shù),它被用于給上述三個構(gòu)造參數(shù)賦予同樣的值。

struct Color {
    let red, green, blue: Double
    init(red: Double, green: Double, blue: Double) {
        self.red   = red
        self.green = green
        self.blue  = blue
    }
    init(white: Double) {
        red   = white
        green = white
        blue  = white
    }
}

兩種構(gòu)造器都能通過提供的初始參數(shù)值來創(chuàng)建一個新的Color實例:

let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
let halfGray = Color(white: 0.5)

注意,如果不通過外部參數(shù)名字傳值,你是沒法調(diào)用這個構(gòu)造器的。只要構(gòu)造器定義了某個外部參數(shù)名,你就必須使用它,忽略它將導(dǎo)致編譯錯誤:

let veryGreen = Color(0.0, 1.0, 0.0)
// 報編譯時錯誤,需要外部名稱

不帶外部名的構(gòu)造器參數(shù)

如果你不希望為構(gòu)造器的某個參數(shù)提供外部名字,你可以使用下劃線(_)來顯式描述它的外部名,以此重寫上面所說的默認行為。

下面是之前 Celsius 例子的擴展,跟之前相比添加了一個帶有 Double 類型參數(shù)的構(gòu)造器,其外部名用 _ 代替:

struct Celsius {
    var temperatureInCelsius: Double
    init(fromFahrenheit fahrenheit: Double) {
        temperatureInCelsius = (fahrenheit - 32.0) / 1.8
    }
    init(fromKelvin kelvin: Double) {
        temperatureInCelsius = kelvin - 273.15
    }
    init(_ celsius: Double){
        temperatureInCelsius = celsius
    }
}

let bodyTemperature = Celsius(37.0)
// bodyTemperature.temperatureInCelsius 為 37.0

調(diào)用 Celsius(37.0) 意圖明確,不需要外部參數(shù)名稱。因此適合使用 init(_ celsius: Double) 這樣的構(gòu)造器,從而可以通過提供 Double 類型的參數(shù)值調(diào)用構(gòu)造器,而不需要加上外部名。

可選屬性類型

如果你定制的類型包含一個邏輯上允許取值為空的存儲型屬性 - - 無論是因為它無法再初始化時賦值,還是因為它在之后某個時間點可以賦值為空 - - 你都需要將它定義為可選類型。可選類型的屬性將自動初始化為nil,表示這個屬性是有意在初始化時設(shè)置為空的。

下面的例子中定義了類SurveyQuestion,它包含一個可選字符串屬性response

class SurveQuestion {
    var text: String
    var response: String?
    init(text: String) {
        self.text = text
    }
    func ask() {
        print(text)
    }
}

let cheeseQuestion = SurveyQuestion(text: "Do you like cheese?")
cheeseQuestion.ask()
// 輸出 "Do you like cheese?"
cheeseQuestion.response = "Yes, I do like cheese."

調(diào)查問題的答案在回答前是無法確定的,因此我們將屬性 response 聲明為 String? 類型,或者說是可選字符串類型。當 SurveyQuestion 實例化時,它將自動賦值為nil,表明此字符串暫時還沒有值。

構(gòu)造過程中常量屬性的賦值

你可以在構(gòu)造過程中的任意時間點給常量屬性指定一個值,只要在構(gòu)造過程結(jié)束時是一個確定的值。一旦常量屬性被賦值,它將永遠不可更改。

注意:對于類的實例來說,它的常量屬性只能在定義它的類的構(gòu)造過程中修改;不能再子類中修改。

你可以修改上面的SurveQuestion示例,用常量屬性替代變量屬性text,表示問題內(nèi)容在SurveQuestion的實例被創(chuàng)建之后不會再被修改。盡管text屬性現(xiàn)在是常量,我們?nèi)匀豢梢栽陬惖臉?gòu)造器中設(shè)置它的值:

class SurveyQuestion {
    let text: String
    var response: String?
    init(text: String) {
        self.text = text
    }
    func ask() {
        print(text)
    }
}

let beetsQuestion = SurveyQuestion(text: "How about beets?")
beetsQuestion.ask()
// 輸出 "How about beets?"
beetsQuestion.response = "I also like beets. (But not with cheese.)"

默認構(gòu)造器

如果結(jié)構(gòu)體或類的所有屬性都有默認值,同時沒有自定義的構(gòu)造器,那么Swift會給這些結(jié)構(gòu)體或類提供一個默認構(gòu)造器。這個構(gòu)造器將簡單地創(chuàng)建一個所有屬性值都設(shè)置為默認值的實例。

下面的例子中創(chuàng)建了一個類ShoppingListItem,它封裝了購物清單中的某一物品的屬性:名字(name)、數(shù)量(quantity)和購買狀態(tài):

class ShoppingListItem {
    var name: String?
    var quantity = 1
    var purchased = false
}

var item = ShoppingListItem()

由于shoppingListItem類中的所有屬性都有默認值,且它是沒有父類的基類,它將自動獲得一個可以為所有屬性設(shè)置默認值的默認構(gòu)造器。

上面例子中使用默認構(gòu)造器創(chuàng)造了一個 ShoppingListItem 類的實例(使用 ShoppingListItem() 形式的構(gòu)造器語法),并將其賦值給變量 item

結(jié)構(gòu)體的逐一成員構(gòu)造器

除了上面提到的默認構(gòu)造器,如果結(jié)構(gòu)體沒有提供自定義的構(gòu)造器,它們將自動獲得一個逐一成員構(gòu)造器,即使結(jié)構(gòu)體的存儲型屬性沒有默認值。

逐一成員構(gòu)造器是用來初始化結(jié)構(gòu)體新實例里成員屬性的快捷方法。我們在調(diào)用逐一成員構(gòu)造器時,通過與成員屬性名相同的參數(shù)名進行傳值來完成對成員屬性的初始賦值。

下面的例子中,定義了一個結(jié)構(gòu)體Size,它包含兩個屬性widthheight。Swift可以根據(jù)這兩個屬性的初始賦值為0.0自動推導(dǎo)出它們的類型為Double

結(jié)構(gòu)體Size自動獲得了一個逐一成員構(gòu)造器init(width:height:)。你可以用它來創(chuàng)建新的Size實例:

struct Size {
    var width = 0.0, height = 0.0
}

let twoByTwo = Size(width: 2.0, height: 2.0)

值類型的構(gòu)造器代理

構(gòu)造器可以通過調(diào)用其他構(gòu)造器來完成實例的部分構(gòu)造過程。這一過程稱為構(gòu)造器代理,它能避免多個構(gòu)造器間的代碼重復(fù)。

構(gòu)造器代理的實現(xiàn)規(guī)則和形式在值類型和類類型中有所不同。值類型不支持繼承,所以構(gòu)造器代碼的過程相對簡單,因為它們只能代理給自己的其他構(gòu)造器。類則不同,它可以繼承自其它類,這意味著類有責任保證其所有繼承的存儲型屬性在構(gòu)造時也能正確的初始化。

對于值類型,你可以使用self.init在自定義的構(gòu)造器中引用相同類型中的其它構(gòu)造器。并且你只能在構(gòu)造器內(nèi)部調(diào)用self.init。

請注意:如果你為某個值類型定義了一個自定義的構(gòu)造器,你將無法訪問到默認構(gòu)造器(如果是結(jié)構(gòu)體,還將無法訪問逐一成員構(gòu)造器)。這種限制可以防止你為值類型增加一個額外的且十分復(fù)雜的構(gòu)造器之后,仍然有人錯誤的使用自動生成的構(gòu)造器。

注意:假如你希望默認構(gòu)造器、逐一成員構(gòu)造器以及你自己的自定義構(gòu)造器都能用來創(chuàng)建實例,可以將自定義的構(gòu)造器寫到擴展(extension)中,而不是寫在值類型的原始定義中。

下面例子將定義一個結(jié)構(gòu)體 Rect,用來代表幾何矩形。這個例子需要兩個輔助的結(jié)構(gòu)體 SizePoint,它們各自為其所有的屬性提供了默認初始值 0.0。

struct Size {
    var width = 0.0, height = 0.0
}

struct Point {
    var x = 0.0, y = 0.0
}

你可以通過下面三種方式為Rect創(chuàng)建實例:

  • 使用含有默認值的originsize屬性來初始化;
  • 提供指定的originsize實例來初始化;
  • 提供指定的centersize來初始化;

在下面的Rect結(jié)構(gòu)體定義中,我們?yōu)檫@三種方式提供了三個自定義的構(gòu)造器:

struct Rect {
    var origin = Ponit()
    var size = Size()
    init() {}
    
    init(origin: Point, size: Size) {
        self.origin = origin
        self.size = size
    }
    
    init(center: Point, size: Size) {
        let originX = center.x - (size.width / 2.0)
        let originY = center.y - (size.height / 2.0)
        self.init(origin: Point(x: originX, y: originY), size: size)
    }
}

第一個Rect構(gòu)造器init(),在功能上跟沒有自定義構(gòu)造器時自動獲得的默認構(gòu)造器是一樣的。這個構(gòu)造器是一個空函數(shù),使用一對大括號{}來表示。調(diào)用這個構(gòu)造器將返回一個Rect實例,它的originsize屬性都使用定義時的默認值Point(x: 0.0, y: 0.0)Size(width: 0.0, height: 0.0)

let basicRect = Rect()
// basicRect 的 origin 是 (0.0, 0.0),size 是 (0.0, 0.0)

第二個Rect構(gòu)造器init(origin:size:),在功能上跟結(jié)構(gòu)體在沒有自定義構(gòu)造器是獲得的逐一成員構(gòu)造器是一樣的。這個構(gòu)造器只是簡單地將originsize的參數(shù)值賦給對應(yīng)的存儲型屬性:

let originRect = Rect(origin: Point(x: 2.0, y: 2.0), size: Size(width: 5.0, height: 5.0))
// originRect 的 origin 是 (2.0, 2.0),size 是 (5.0, 5.0)

第三個Rect 構(gòu)造器 init(center:size:) 稍微復(fù)雜一點。它先通過 centersize 的值計算出 origin 的坐標,然后再調(diào)用(或者說代理給)init(origin:size:) 構(gòu)造器來將新的 originsize 值賦值到對應(yīng)的屬性中:

let centerRect = Rect(center: Point(x: 4.0, y: 4.0), size: Size(width: 3.0, height: 3.0))
// centerRect 的 origin 是 (2.5, 2.5),size 是 (3.0, 3.0)

構(gòu)造器init(center:size:)可以直接將originsize的新值賦值給對應(yīng)的屬性中。然而,構(gòu)造器init(center:size:)通過使用提供了相關(guān)功能的現(xiàn)有構(gòu)造函數(shù)將會更加便捷。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本章將會介紹 存儲屬性的初始賦值自定義構(gòu)造過程默認構(gòu)造器值類型的構(gòu)造器代理類的繼承和構(gòu)造過程可失敗構(gòu)造器必要構(gòu)造器...
    寒橋閱讀 832評論 0 0
  • 構(gòu)造過程是使用類、結(jié)構(gòu)體或枚舉類型的實例之前的準備過程。在新實例可用前必須執(zhí)行這個過程,具體操作包括設(shè)置實例中每個...
    莽原奔馬668閱讀 747評論 0 3
  • 構(gòu)造過程是為了使用某個類、結(jié)構(gòu)體或枚舉類型的實例而進行的準備過程。其實就是初始化。構(gòu)造過程是通過定義構(gòu)造器(Ini...
    冰三尺閱讀 390評論 0 0
  • ?構(gòu)造過程是使用類、結(jié)構(gòu)體或枚舉類型一個實例的準備過程。在新實例可用前必須執(zhí)行這個過程,具體操作包括設(shè)置實例中每個...
    EndEvent閱讀 731評論 0 3
  • 七月買的書。下定決心要學好英語了。然后是瑜伽。接著可能是化妝。 先將英語學好再說。
    偉幾聲閱讀 215評論 0 0

友情鏈接更多精彩內(nèi)容