Swift Tour Learn (七) -- Swift 語法(下標、繼承)

本章將會介紹

下標語法
下標用法
下標選項
定義一個基類
子類生成
重寫
防止重寫

下標

下標可以定義在類、結(jié)構(gòu)體和枚舉中,是訪問集合,列表或序列中元素的快捷方式。可以使用下標的索引,設置和獲取值,而不需要再調(diào)用對應的存取方法。舉例來說,用下標訪問一個Array實例中的元素可以寫作someArray[index],訪問Dictionary實例中的元素可以寫作someDictionary[key]。

一個類型可以定義多個下標,通過不同索引類型進行重載。下標不限于一維,你可以定義具有多個入?yún)⒌南聵藵M足自定義類型的需求。

1.下標語法

下標允許你通過在實例名稱后面的方括號中傳入一個或者多個索引值來對實例進行存取。語法類似于實例方法語法和計算型屬性語法的混合。與定義實例方法類似,定義下標使用subscript關(guān)鍵字,指定一個或多個輸入?yún)?shù)和返回類型;與實例方法不同的是,下標可以設定為讀寫或只讀。這種行為由 getter 和 setter 實現(xiàn),有點類似計算型屬性:

subscript(index: Int) -> Int {
    get {
      // 返回一個適當?shù)?Int 類型的值
    }

    set(newValue) {
      // 執(zhí)行適當?shù)馁x值操作
    }
}

newValue的類型和下標的返回類型相同。如同計算型屬性,可以不指定 setter 的參數(shù)(newValue)。如果不指定參數(shù),setter 會提供一個名為newValue的默認參數(shù)。

如同只讀計算型屬性,可以省略只讀下標的get關(guān)鍵字:

subscript(index: Int) -> Int {
    // 返回一個適當?shù)?Int 類型的值
}

下面代碼演示了只讀下標的實現(xiàn),這里定義了一個TimesTable結(jié)構(gòu)體,用來表示傳入整數(shù)的乘法表:

struct TimesTable {
    let multiplier: Int
    subscript(index: Int) -> Int {
        return multiplier * index
    }
}
let threeTimesTable = TimesTable(multiplier: 3)
print("six times three is \(threeTimesTable[6])")
// 打印 "six times three is 18"

在上例中,創(chuàng)建了一個TimesTable實例,用來表示整數(shù)3的乘法表。數(shù)值3被傳遞給結(jié)構(gòu)體的構(gòu)造函數(shù),作為實例成員multiplier的值。

你可以通過下標訪問threeTimesTable實例,例如上面演示的threeTimesTable[6]。這條語句查詢了3的乘法表中的第六個元素,返回3的6倍即18。

注意
TimesTable例子基于一個固定的數(shù)學公式,對threeTimesTable[someIndex]進行賦值操作并不合適,因此下標定義為只讀的。

2.下標用法

下標的確切含義取決于使用場景。下標通常作為訪問集合,列表或序列中元素的快捷方式。你可以針對自己特定的類或結(jié)構(gòu)體的功能來自由地以最恰當?shù)姆绞綄崿F(xiàn)下標。

例如,Swift 的Dictionary類型實現(xiàn)下標用于對其實例中儲存的值進行存取操作。為字典設值時,在下標中使用和字典的鍵類型相同的鍵,并把一個和字典的值類型相同的值賦給這個下標:

var numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
numberOfLegs["bird"] = 2

上例定義一個名為numberOfLegs的變量,并用一個包含三對鍵值的字典字面量初始化它。numberOfLegs字典的類型被推斷為[String: Int]。字典創(chuàng)建完成后,該例子通過下標將String類型的鍵bird和Int類型的值2添加到字典中。

注意
Swift 的Dictionary類型的下標接受并返回可選類型的值。上例中的numberOfLegs字典通過下標返回的是一個Int?或者說“可選的int”。Dictionary類型之所以如此實現(xiàn)下標,是因為不是每個鍵都有個對應的值,同時這也提供了一種通過鍵刪除對應值的方式,只需將鍵對應的值賦值為nil即可。

3.下標選項

下標可以接受任意數(shù)量的入?yún)ⅲ⑶疫@些入?yún)⒖梢允侨我忸愋汀O聵说姆祷刂狄部梢允侨我忸愋?。下標可以使用變量參?shù)和可變參數(shù),但不能使用輸入輸出參數(shù),也不能給參數(shù)設置默認值。

一個類或結(jié)構(gòu)體可以根據(jù)自身需要提供多個下標實現(xiàn),使用下標時將通過入?yún)⒌臄?shù)量和類型進行區(qū)分,自動匹配合適的下標,這就是下標的重載。

雖然接受單一入?yún)⒌南聵耸亲畛R姷模部梢愿鶕?jù)情況定義接受多個入?yún)⒌南聵恕@缦吕x了一個Matrix結(jié)構(gòu)體,用于表示一個Double類型的二維矩陣。Matrix結(jié)構(gòu)體的下標接受兩個整型參數(shù):

struct Matrix {
    let rows: Int, columns: Int
    var grid: [Double]
    init(rows: Int, columns: Int) {
        self.rows = rows
        self.columns = columns
        grid = Array(repeatElement(0.0, count: rows * columns))
    }
    func indexIsValidForRow(row: Int, column: Int) -> Bool {
        return row >= 0 && row < rows && column >= 0 && column < columns
    }
    subscript(row: Int, column: Int) -> Double {
        get {
            assert(indexIsValidForRow(row, column: column), "Index out of range")
            return grid[(row * columns) + column]
        }
        set {
            assert(indexIsValidForRow(row, column: column), "Index out of range")
            grid[(row * columns) + column] = newValue
        }
    }
}

Matrix提供了一個接受兩個入?yún)⒌臉?gòu)造方法,入?yún)⒎謩e是rows和columns,創(chuàng)建了一個足夠容納rows * columns個Double類型的值的數(shù)組。通過傳入數(shù)組長度和初始值0.0到數(shù)組的構(gòu)造器,將矩陣中每個位置的值初始化為0.0。

你可以通過傳入合適的row和column的數(shù)量來構(gòu)造一個新的Matrix實例:

var matrix = Matrix(rows: 2, columns: 2)

上例中創(chuàng)建了一個Matrix實例來表示兩行兩列的矩陣。該Matrix實例的grid數(shù)組按照從左上到右下的閱讀順序?qū)⒕仃嚤馄交鎯Γ?/p>

將row和column的值傳入下標來為矩陣設值,下標的入?yún)⑹褂枚禾柗指簦?/p>

matrix[0, 1] = 1.5
matrix[1, 0] = 3.2

上面兩條語句分別調(diào)用下標的 setter 將矩陣右上角位置(即row為0、column為1的位置)的值設置為1.5,將矩陣左下角位置(即row為1、column為0的位置)的值設置為3.2:


Matrix下標的 getter 和 setter 中都含有斷言,用來檢查下標入?yún)ow和column的值是否有效。為了方便進行斷言,Matrix包含了一個名為indexIsValidForRow(_:column:)的便利方法,用來檢查入?yún)ow和column的值是否在矩陣范圍內(nèi),斷言在下標越界時觸發(fā):

let someValue = matrix[2, 2]
// 斷言將會觸發(fā),因為 [2, 2] 已經(jīng)超過了 matrix 的范圍
4.下標總結(jié)
下標

// 下標語法
struct TimesTable {
    let multiplier: Int
    subscript(index: Int) -> Int {
        return multiplier * index
    }
}
let threeTimesTable = TimesTable(multiplier: 3)
print("\(threeTimesTable[6])")


struct Matrix {
    let rows: Int, columns: Int
    var grid: [Double]

    init(rows: Int, columns: Int) {
        self.rows = rows
        self.columns = columns
        grid = Array(repeatElement(0.0, count: rows * columns))
    }

    func indexIsValidForRow(row: Int, column: Int) -> Bool {
        return row >= 0 && row < rows && column >= 0 && column < columns
    }

    subscript(row: Int, column: Int) -> Double {
        get {
            assert(indexIsValidForRow(row: row, column: column), "Index out of range")
            return grid[(row * columns) + column]
        }
        set {
            assert(indexIsValidForRow(row: row, column: column), "Index out of range")
            grid[(row * columns) + column] = newValue
        }
    }
}

var matrix = Matrix(rows: 2, columns: 2)
print(matrix.grid)
matrix[0, 1] = 1.5
matrix[0, 0] = 1.0
print(matrix.grid)

// matrix[2, 2] = 19

繼承

一個類可以繼承另一個類的方法,屬性和其它特性。當一個類繼承其它類時,繼承類叫子類,被繼承類叫超類(或父類)。在 Swift 中,繼承是區(qū)分「類」與其它類型的一個基本特征。

在 Swift 中,類可以調(diào)用和訪問超類的方法,屬性和下標,并且可以重寫這些方法,屬性和下標來優(yōu)化或修改它們的行為。Swift 會檢查你的重寫定義在超類中是否有匹配的定義,以此確保你的重寫行為是正確的。

可以為類中繼承來的屬性添加屬性觀察器,這樣一來,當屬性值改變時,類就會被通知到??梢詾槿魏螌傩蕴砑訉傩杂^察器,無論它原本被定義為存儲型屬性還是計算型屬性。

1.定義一個基類

不繼承于其它類的類,稱之為基類。

注意
Swift 中的類并不是從一個通用的基類繼承而來。如果你不為你定義的類指定一個超類的話,這個類就自動成為基類。

下面的例子定義了一個叫Vehicle的基類。這個基類聲明了一個名為currentSpeed,默認值是0.0的存儲屬性(屬性類型推斷為Double)。currentSpeed屬性的值被一個String類型的只讀計算型屬性description使用,用來創(chuàng)建車輛的描述。

Vehicle基類也定義了一個名為makeNoise的方法。這個方法實際上不為Vehicle實例做任何事,但之后將會被Vehicle的子類定制:

class Vehicle {
    var currentSpeed = 0.0
    var description: String {
        return "traveling at \(currentSpeed) miles per hour"
    }
    func makeNoise() {
        // 什么也不做-因為車輛不一定會有噪音
    }
}

您可以用初始化語法創(chuàng)建一個Vehicle的新實例,即類名后面跟一個空括號:

let someVehicle = Vehicle()

現(xiàn)在已經(jīng)創(chuàng)建了一個Vehicle的新實例,你可以訪問它的description屬性來打印車輛的當前速度:

print("Vehicle: \(someVehicle.description)")
// 打印 "Vehicle: traveling at 0.0 miles per hour"

Vehicle類定義了一個通用特性的車輛類,實際上沒什么用處。為了讓它變得更加有用,需要完善它從而能夠描述一個更加具體類型的車輛。

2.子類生成

子類生成指的是在一個已有類的基礎上創(chuàng)建一個新的類。子類繼承超類的特性,并且可以進一步完善。你還可以為子類添加新的特性。

為了指明某個類的超類,將超類名寫在子類名的后面,用冒號分隔:

class SomeClass: SomeSuperclass {
    // 這里是子類的定義
}

下一個例子,定義一個叫Bicycle的子類,繼承成父類Vehicle:

class Bicycle: Vehicle {
    var hasBasket = false
}

新的Bicycle類自動獲得Vehicle類的所有特性,比如currentSpeed和description屬性,還有它的makeNoise()方法。

除了它所繼承的特性,Bicycle類還定義了一個默認值為false的存儲型屬性hasBasket(屬性推斷為Bool)。

默認情況下,你創(chuàng)建任何新的Bicycle實例將不會有一個籃子(即hasBasket屬性默認為false),創(chuàng)建該實例之后,你可以為特定的Bicycle實例設置hasBasket屬性為ture:

let bicycle = Bicycle()
bicycle.hasBasket = true

你還可以修改Bicycle實例所繼承的currentSpeed屬性,和查詢實例所繼承的description屬性:

bicycle.currentSpeed = 15.0
print("Bicycle: \(bicycle.description)")
// 打印 "Bicycle: traveling at 15.0 miles per hour"

子類還可以繼續(xù)被其它類繼承,下面的示例為Bicycle創(chuàng)建了一個名為Tandem(雙人自行車)的子類:

class Tandem: Bicycle {
    var currentNumberOfPassengers = 0
}

Tandem從Bicycle繼承了所有的屬性與方法,這又使它同時繼承了Vehicle的所有屬性與方法。Tandem也增加了一個新的叫做currentNumberOfPassengers的存儲型屬性,默認值為0。

如果你創(chuàng)建了一個Tandem的實例,你可以使用它所有的新屬性和繼承的屬性,還能查詢從Vehicle繼承來的只讀屬性description:

let tandem = Tandem()
tandem.hasBasket = true
tandem.currentNumberOfPassengers = 2
tandem.currentSpeed = 22.0
print("Tandem: \(tandem.description)")
// 打?。?Tandem: traveling at 22.0 miles per hour"
3.重寫

子類可以為繼承來的實例方法,類方法,實例屬性,或下標提供自己定制的實現(xiàn)。我們把這種行為叫重寫。

如果要重寫某個特性,你需要在重寫定義的前面加上override關(guān)鍵字。這么做,你就表明了你是想提供一個重寫版本,而非錯誤地提供了一個相同的定義。意外的重寫行為可能會導致不可預知的錯誤,任何缺少override關(guān)鍵字的重寫都會在編譯時被診斷為錯誤。

override關(guān)鍵字會提醒 Swift 編譯器去檢查該類的超類(或其中一個父類)是否有匹配重寫版本的聲明。這個檢查可以確保你的重寫定義是正確的。

訪問超類的方法,屬性及下標

當你在子類中重寫超類的方法,屬性或下標時,有時在你的重寫版本中使用已經(jīng)存在的超類實現(xiàn)會大有裨益。比如,你可以完善已有實現(xiàn)的行為,或在一個繼承來的變量中存儲一個修改過的值。

在合適的地方,你可以通過使用super前綴來訪問超類版本的方法,屬性或下標:

  • 在方法someMethod()的重寫實現(xiàn)中,可以通過super.someMethod()來調(diào)用超類版本的someMethod()方法。
  • 在屬性someProperty的 getter 或 setter 的重寫實現(xiàn)中,可以通過super.someProperty來訪問超類版本的someProperty屬性。
  • 在下標的重寫實現(xiàn)中,可以通過super[someIndex]來訪問超類版本中的相同下標。

重寫方法

在子類中,你可以重寫繼承來的實例方法或類方法,提供一個定制或替代的方法實現(xiàn)。下面的例子定義了Vehicle的一個新的子類,叫Train,它重寫了從Vehicle類繼承來的makeNoise()方法:

class Train: Vehicle {
    override func makeNoise() {
        print("Choo Choo")
    }
}

如果你創(chuàng)建一個Train的新實例,并調(diào)用了它的makeNoise()方法,你就會發(fā)現(xiàn)Train版本的方法被調(diào)用:

let train = Train()
train.makeNoise()
// 打印 "Choo Choo"

重寫屬性

你可以重寫繼承來的實例屬性或類型屬性,提供自己定制的 getter 和 setter,或添加屬性觀察器使重寫的屬性可以觀察屬性值什么時候發(fā)生改變

  • 重寫屬性的 Getters 和 Setters

你可以提供定制的 getter(或 setter)來重寫任意繼承來的屬性,無論繼承來的屬性是存儲型的還是計算型的屬性。子類并不知道繼承來的屬性是存儲型的還是計算型的,它只知道繼承來的屬性會有一個名字和類型。你在重寫一個屬性時,必需將它的名字和類型都寫出來。這樣才能使編譯器去檢查你重寫的屬性是與超類中同名同類型的屬性相匹配的。

你可以將一個繼承來的只讀屬性重寫為一個讀寫屬性,只需要在重寫版本的屬性里提供 getter 和 setter 即可。但是,你不可以將一個繼承來的讀寫屬性重寫為一個只讀屬性。

注意
如果你在重寫屬性中提供了 setter,那么你也一定要提供 getter。如果你不想在重寫版本中的 getter 里修改繼承來的屬性值,你可以直接通過super.someProperty來返回繼承來的值,其中someProperty是你要重寫的屬性的名字。

以下的例子定義了一個新類,叫Car,它是Vehicle的子類。這個類引入了一個新的存儲型屬性叫做gear,默認值為整數(shù)1。Car類重寫了繼承自Vehicle的description屬性,提供包含當前檔位的自定義描述:

class Car: Vehicle {
    var gear = 1
    override var description: String {
        return super.description + " in gear \(gear)"
    }
}

重寫的description屬性首先要調(diào)用super.description返回Vehicle類的description屬性。之后,Car類版本的description在末尾增加了一些額外的文本來提供關(guān)于當前檔位的信息。

如果你創(chuàng)建了Car的實例并且設置了它的gear和currentSpeed屬性,你可以看到它的description返回了Car中的自定義描述:

let car = Car()
car.currentSpeed = 25.0
car.gear = 3
print("Car: \(car.description)")
// 打印 "Car: traveling at 25.0 miles per hour in gear 3"
  • 重寫屬性觀察器

你可以通過重寫屬性為一個繼承來的屬性添加屬性觀察器。這樣一來,當繼承來的屬性值發(fā)生改變時,你就會被通知到,無論那個屬性原本是如何實現(xiàn)的。

注意
你不可以為繼承來的常量存儲型屬性或繼承來的只讀計算型屬性添加屬性觀察器。這些屬性的值是不可以被設置的,所以,為它們提供willSet或didSet實現(xiàn)是不恰當。
此外還要注意,你不可以同時提供重寫的 setter 和重寫的屬性觀察器。如果你想觀察屬性值的變化,并且你已經(jīng)為那個屬性提供了定制的 setter,那么你在 setter 中就可以觀察到任何值變化了。

下面的例子定義了一個新類叫AutomaticCar,它是Car的子類。AutomaticCar表示自動擋汽車,它可以根據(jù)當前的速度自動選擇合適的擋位:

class AutomaticCar: Car {
    override var currentSpeed: Double {
        didSet {
            gear = Int(currentSpeed / 10.0) + 1
        }
    }
}

當你設置AutomaticCar的currentSpeed屬性,屬性的didSet觀察器就會自動地設置gear屬性,為新的速度選擇一個合適的擋位。具體來說就是,屬性觀察器將新的速度值除以10,然后向下取得最接近的整數(shù)值,最后加1來得到檔位gear的值。例如,速度為35.0時,擋位為4:

let automatic = AutomaticCar()
automatic.currentSpeed = 35.0
print("AutomaticCar: \(automatic.description)")
// 打印 "AutomaticCar: traveling at 35.0 miles per hour in gear 4"
4.防止重寫

你可以通過把方法,屬性或下標標記為final來防止它們被重寫,只需要在聲明關(guān)鍵字前加上final修飾符即可(例如:final var,final func,final class func,以及final subscript)。

如果你重寫了帶有final標記的方法,屬性或下標,在編譯時會報錯。在類擴展中的方法,屬性或下標也可以在擴展的定義里標記為 final 的。

你可以通過在關(guān)鍵字class前添加final修飾符(final class)來將整個類標記為 final 的。這樣的類是不可被繼承的,試圖繼承這樣的類會導致編譯報錯。

5.繼承總結(jié)
繼承

// 定義一個基類
class Vechicle {
    var currentSpeed = 0.0
    var description: String {
        return "traveling at \(currentSpeed) miles per hour"
    }

    func makeNoise() {
        // 什么也不做,有的車輛不一定會有噪音
    }
}

// 初始化語法創(chuàng)建實例
let someVechicle = Vechicle()
print(someVechicle.description)

// 子類生成
class Bicycle: Vechicle {
    var hasBasket = false
}
let bicycle = Bicycle()
bicycle.hasBasket = true
bicycle.currentSpeed = 15.0
print(bicycle.description)

class Tandem: Bicycle {
    var currentNumberOfPassengers = 0
}
let tandem = Tandem()
tandem.hasBasket = true
tandem.currentNumberOfPassengers = 2
tandem.currentSpeed = 22.0
print(tandem.description)

// 重寫方法
class Train: Vechicle {
    override func makeNoise() {
        print("Choo Choo")
    }
}
let train = Train()
train.makeNoise()

// 重寫屬性
class Car: Vechicle {
    var gear = 1
    override var description: String {
        return super.description + " in gear \(gear)"
    }
}
let car = Car()
car.currentSpeed = 25.0
car.gear = 3
print(car.description)

// 重寫屬性觀察器
class AutomaticCar: Car {
    override var currentSpeed: Double {
        didSet {
            gear = Int((currentSpeed / 10.0) + 1)
        }
    }
}
let automatic = AutomaticCar()
automatic.currentSpeed = 35.0
print(automatic.gear)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容