類和結構體
- 一、結構體和類對比
- 1、類型定義的語法
- 2、結構體和類的實例
- 3、屬性訪問
- 4、結構體類型的成員逐一構造器
- 二、結構體和枚舉是值類型
- 三、類是引用類型
- 1、恒等運算符
- 2、指針
結構體和類作為一種通用而又靈活的結構,成為了人們構建代碼的基礎。你可以使用定義常量、變量和函數的語法,為你的結構體和類定義屬性、添加方法。
與其他編程語言所不同的是,Swift 并不要求你為自定義的結構體和類的接口與實現代碼分別創建文件。你只需在單一的文件中定義一個結構體或者類,系統將會自動生成面向其它代碼的外部接口。
注意
通常一個類的實例被稱為對象。然而相比其他語言,Swift 中結構體和類的功能更加相近,本章中所討論的大部分功能都可以用在結構體或者類上。因此,這里會使用實例這個更通用的術語。
一、結構體和類對比
Swift 中結構體和類有很多共同點。兩者都可以:
- 定義屬性用于存儲值
- 定義方法用于提供功能
- 定義下標操作用于通過下標語法訪問它們的值
- 定義構造器用于設置初始值
- 通過擴展以增加默認實現之外的功能
- 遵循協議以提供某種標準功能
與結構體相比,類還有如下的附加功能:
- 繼承允許一個類繼承另一個類的特征
- 類型轉換允許在運行時檢查和解釋一個類實例的類型
- 析構器允許一個類實例釋放任何其所被分配的資源
- 引用計數允許對一個類的多次引用
類支持的附加功能是以增加復雜性為代價的。作為一般準則,優先使用結構體,因為他們更容易理解,僅在適當或必要時才使用類。實際上,這意味著你的大多數自定義數據類型都會是結構體和枚舉。
1、類型定義的語法
結構體和類有著相似的定義方式。你通過 struct
關鍵字引入結構體,通過 class
關鍵字引入類,并將它們的具體定義放在一對大括號中:
struct SomeStructure {// 在這里定義結構體
}
class SomeClass {// 在這里定義類
}
注意
每當你定義一個新的結構體或者類時,你都是定義了一個新的 Swift 類型。請使用UpperCamelCase
這種方式來命名類型(如這里的SomeClass
和SomeStructure
),以便符合標準 Swift 類型的大寫命名風格(如String
,Int
和Bool
)。請使用lowerCamelCase
這種方式來命名屬性和方法(如frameRate
和incrementCount
),以便和類型名區分。
以下是定義結構體和定義類的示例:
struct Resolution {var width = 0var height = 0
}
class VideoMode {var resolution = Resolution()var interlaced = falsevar frameRate = 0.0var name: String?
}
在上面的示例中定義了一個名為 Resolution
的結構體,用來描述基于像素的分辨率。這個結構體包含了名為 width
和 height
的兩個存儲屬性。存儲屬性是與結構體或者類綁定的,并存儲在其中的常量或變量。當這兩個屬性被初始化為整數 0
的時候,它們會被推斷為 Int
類型。
在上面的示例還定義了一個名為 VideoMode
的類,用來描述視頻顯示器的某個特定視頻模式。這個類包含了四個可變的存儲屬性。第一個, resolution
,被初始化為一個新的 Resolution
結構體的實例,屬性類型被推斷為 Resolution
。新 VideoMode
實例同時還會初始化其它三個屬性,它們分別是初始值為 false
的 interlaced
(意為“非隔行視頻”),初始值為 0.0
的 frameRate
,以及值為可選 String
的 name
。因為 name
是一個可選類型,它會被自動賦予一個默認值 nil
,意為“沒有 name 值”。
2、結構體和類的實例
Resolution
結構體和 VideoMode
類的定義僅描述了什么是 Resolution
和 VideoMode
。它們并沒有描述一個特定的分辨率(resolution)或者視頻模式(video mode)。為此,你需要創建結構體或者類的一個實例。
創建結構體和類實例的語法非常相似:
let someResolution = Resolution()
let someVideoMode = VideoMode()
結構體和類都使用構造器語法來創建新的實例。構造器語法的最簡單形式是在結構體或者類的類型名稱后跟隨一對空括號,如 Resolution()
或 VideoMode()
。通過這種方式所創建的類或者結構體實例,其屬性均會被初始化為默認值。構造過程 章節會對類和結構體的初始化進行更詳細的討論。
3、屬性訪問
你可以通過使用點語法訪問實例的屬性。其語法規則是,實例名后面緊跟屬性名,兩者以點號(.
)分隔,不帶空格:
print("The width of someResolution is \(someResolution.width)")
// 打印 "The width of someResolution is 0"
在上面的例子中,someResolution.width
引用 someResolution
的 width
屬性,返回 width
的初始值 0
。
你也可以訪問子屬性,如 VideoMode
中 resolution
屬性的 width
屬性:
print("The width of someVideoMode is \(someVideoMode.resolution.width)")
// 打印 "The width of someVideoMode is 0"
你也可以使用點語法為可變屬性賦值:
someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
// 打印 "The width of someVideoMode is now 1280"
4、結構體類型的成員逐一構造器
所有結構體都有一個自動生成的成員逐一構造器,用于初始化新結構體實例中成員的屬性。新實例中各個屬性的初始值可以通過屬性的名稱傳遞到成員逐一構造器之中:
let vga = Resolution(width: 640, height: 480)
注意:
與結構體不同,類實例沒有默認的成員逐一構造器。
二、結構體和枚舉是值類型
值類型是這樣一種類型,當它被賦值給一個變量、常量或者被傳遞給一個函數的時候,其值會被拷貝。
在之前的章節中,你已經大量使用了值類型。實際上,Swift 中所有的基本類型:整數(integer)、浮點數(floating-point number)、布爾值(boolean)、字符串(string)、數組(array)和字典(dictionary),都是值類型,其底層也是使用結構體實現的。
Swift 中所有的結構體和枚舉類型都是值類型。這意味著它們的實例,以及實例中所包含的任何值類型的屬性,在代碼中傳遞的時候都會被復制。
注意
標準庫定義的集合,例如數組,字典和字符串,都對復制進行了優化以降低性能成本。新集合不會立即復制,而是跟原集合共享同一份內存,共享同樣的元素。在集合的某個副本要被修改前,才會復制它的元素。而你在代碼中看起來就像是立即發生了復制。
請看下面這個示例,其使用了上一個示例中的 Resolution
結構體:
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
在以上示例中,聲明了一個名為 hd
的常量,其值為一個初始化為全高清視頻分辨率(1920 像素寬,1080 像素高)的 Resolution
實例。
然后示例中又聲明了一個名為 cinema
的變量,并將 hd
賦值給它。因為 Resolution
是一個結構體,所以會先創建一個現有實例的副本,然后將副本賦值給 cinema
。盡管 hd
和 cinema
有著相同的寬(width)和高(height),但是在幕后它們是兩個完全不同的實例。
下面,為了符合數碼影院放映的需求(2048 像素寬,1080 像素高),cinema
的 width
屬性被修改為稍微寬一點的 2K 標準:
cinema.width = 2048
查看 cinema
的 width
屬性,它的值確實改為了 2048
:
print("cinema is now \(cinema.width) pixels wide")
// 打印 "cinema is now 2048 pixels wide"
然而,初始的 hd
實例中 width
屬性還是 1920
:
print("hd is still \(hd.width) pixels wide")
// 打印 "hd is still 1920 pixels wide"
將 hd
賦值給 cinema
時,hd
中所存儲的值會拷貝到新的 cinema
實例中。結果就是兩個完全獨立的實例包含了相同的數值。由于兩者相互獨立,因此將 cinema
的 width
修改為 2048 并不會影響 hd
中的 width
的值,如下圖所示:
枚舉也遵循相同的行為準則:
enum CompassPoint {case north, south, east, westmutating func turnNorth() {self = .north}
}
var currentDirection = CompassPoint.west
let rememberedDirection = currentDirection
currentDirection.turnNorth()
print("The current direction is \(currentDirection)")
print("The remembered direction is \(rememberedDirection)")
// 打印 "The current direction is north"
// 打印 "The remembered direction is west"
當 rememberedDirection
被賦予了 currentDirection
的值,實際上它被賦予的是值的一個拷貝。賦值過程結束后再修改 currentDirection
的值并不影響 rememberedDirection
所儲存的原始值的拷貝。
三、類是引用類型
與值類型不同,引用類型在被賦予到一個變量、常量或者被傳遞到一個函數時,其值不會被拷貝。因此,使用的是已存在實例的引用,而不是其拷貝。
請看下面這個示例,其使用了之前定義的 VideoMode
類:
let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0
以上示例中,聲明了一個名為 tenEighty
的常量,并讓其引用一個 VideoMode
類的新實例。它的視頻模式(video mode
)被賦值為之前創建的 HD 分辨率(1920*1080
)的一個拷貝。然后將它設置為隔行視頻,名字設為 “1080i
”,并將幀率設置為 25.0
幀每秒。
接下來,將 tenEighty
賦值給一個名為 alsoTenEighty
的新常量,并修改 alsoTenEighty
的幀率:
let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0
因為類是引用類型,所以 tenEight
和 alsoTenEight
實際上引用的是同一個 VideoMode
實例。換句話說,它們是同一個實例的兩種叫法,如下圖所示:
通過查看 tenEighty
的 frameRate
屬性,可以看到它正確地顯示了底層的 VideoMode
實例的新幀率 30.0
:
print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// 打印 "The frameRate property of theEighty is now 30.0"
這個例子也顯示了為何引用類型更加難以理解。如果 tenEighty
和 alsoTenEighty
在你代碼中的位置相距很遠,那么就很難找到所有修改視頻模式的地方。無論在哪使用 tenEighty
,你都要考慮使用 alsoTenEighty
的代碼,反之亦然。相反,值類型就更容易理解了,因為你的源碼中與同一個值交互的代碼都很近。
需要注意的是 tenEighty
和 alsoTenEighty
被聲明為常量而不是變量。然而你依然可以改變 tenEighty.frameRate
和 alsoTenEighty.frameRate
,這是因為 tenEighty
和 alsoTenEighty
這兩個常量的值并未改變。它們并不“存儲”這個 VideoMode
實例,而僅僅是對 VideoMode
實例的引用。所以,改變的是底層 VideoMode
實例的 frameRate
屬性,而不是指向 VideoMode
的常量引用的值。
1、恒等運算符
因為類是引用類型,所以多個常量和變量可能在幕后同時引用同一個類實例。(對于結構體和枚舉來說,這并不成立。因為它們作為值類型,在被賦予到常量、變量或者傳遞到函數時,其值總是會被拷貝。)
判定兩個常量或者變量是否引用同一個類實例有時很有用。為了達到這個目的,Swift 提供了兩個恒等運算符:
- 相同(
===
) - 不相同(
!==
)
使用這兩個運算符檢測兩個常量或者變量是否引用了同一個實例:
if tenEighty === alsoTenEighty {print("tenEighty and alsoTenEighty refer to the same VideoMode instance.")
}
// 打印 "tenEighty and alsoTenEighty refer to the same VideoMode instance."
請注意,“相同”(用三個等號表示,===
)與“等于”(用兩個等號表示,==
)的不同。“相同”表示兩個類類型(class type)的常量或者變量引用同一個類實例。“等于”表示兩個實例的值“相等”或“等價”,判定時要遵照設計者定義的評判標準。
當在定義你的自定義結構體和類的時候,你有義務來決定判定兩個實例“相等”的標準
2、指針
如果你有 C,C++ 或者 Objective-C 語言的經驗,那么你也許會知道這些語言使用指針來引用內存中的地址。Swift 中引用了某個引用類型實例的常量或變量,與 C 語言中的指針類似,不過它并不直接指向某個內存地址,也不要求你使用星號(*)來表明你在創建一個引用。相反,Swift 中引用的定義方式與其它的常量或變量的一樣。