C語(yǔ)言的枚舉
- C語(yǔ)言的枚舉寫法
enum 枚舉名 {
枚舉值1,
枚舉值2,
......
};
- 我們通過(guò)枚舉表示一周的七天
enum Week {
MON,TUS,WED,THU,FRI,SAT,SUN
};
- c語(yǔ)言中哎榴,枚舉的第一個(gè)成員默認(rèn)是為0嚷掠,后面的枚舉值一次類推地粪,也可以直接指定對(duì)應(yīng)的值,它后面的枚舉值依然是一次遞增
enum Week {
MON=3,TUS,WED,THU=9,FRI,SAT,SUN
};
// 這個(gè)枚舉對(duì)應(yīng)的值就是3宰译,4檐蚜,5,9沿侈,10闯第,11,12
Swift的枚舉
Swift枚舉的使用
- 同樣實(shí)現(xiàn)這個(gè)枚舉缀拭,其寫法如下
enum Week {
case MON
case TUS
case WED
case THU
case FRI
case SAT
case SUN
}
// 也可以直接一個(gè)case乡括,用逗號(hào)隔開(kāi)
enum Week {
case MON,TUS,WED,THU,FRI,SAT,SUN
}
- 這樣表達(dá)的是一個(gè)整型的枚舉,和c語(yǔ)言的是一致的智厌,
Swift
中也可以用枚舉表達(dá)String
enum Week : String {
case MON = "MON"
case TUS = "TUS"
case WED = "WED"
case THU = "THU"
case FRI = "FRI"
case SAT = "SAT"
case SUN = "SUN"
}
-
=
右邊就是Swift
中的RawValue
诲泌,如果我們沒(méi)有寫后面的字符串,會(huì)默認(rèn)的隱式RawValue分配
铣鹏,也可以對(duì)枚舉的成員單獨(dú)指定RawValue
- 查看sil文件敷扫,可以看到自動(dòng)生成了一個(gè)
init?(rawValue:)
方法,和一個(gè)get
方法
enum Week : String {
case MON
case TUS
case WED
case THU
case FRI
case SAT
case SUN
typealias RawValue = String
init?(rawValue: String)
var rawValue: String { get }
}
- 我們來(lái)看下
init?(rawValue:)
函數(shù)是什么時(shí)候調(diào)用的,通過(guò)符號(hào)斷點(diǎn)來(lái)觀察
image - 運(yùn)行發(fā)現(xiàn):
Week.MON.rawValue
并不會(huì)觸發(fā)init
函數(shù)葵第,只有調(diào)用Week(rawValue: "MON")
才會(huì)進(jìn)到符號(hào)斷點(diǎn)绘迁,再通過(guò)打印下面的代碼
print(Week(rawValue: "MON"))
print(Week(rawValue: "hello"))
// 打印結(jié)果
Optional(SwiftEnumerate.Week.MON)
nil
- 查看sil文件中
init
方法的實(shí)現(xiàn),首先我們會(huì)將枚舉值全部放到一個(gè)數(shù)組里面卒密,然后通過(guò)_findStringSwitchCase
方法匹配傳入的字符串缀台,如果匹配到了就返回一個(gè)index
,沒(méi)有匹配到就返回一個(gè)-1
哮奇,再循環(huán)遍歷所有的枚舉值膛腐,匹配到了就取出對(duì)應(yīng)的枚舉值,然后包裝成一個(gè)可選項(xiàng)的值返回鼎俘,如果都沒(méi)有匹配到返回的就是nil哲身,所以返回值是一個(gè)可選項(xiàng)。
// Week.init(rawValue:)
sil hidden @$s4main4WeekO8rawValueACSgSS_tcfC : $@convention(method) (@owned String, @thin Week.Type) -> Optional<Week> {
// %0 "rawValue" // users: %164, %158, %79, %3
// %1 "$metatype"
bb0(%0 : $String, %1 : $@thin Week.Type):
%2 = alloc_stack $Week, var, name "self" // users: %162, %154, %143, %132, %121, %110, %99, %88, %165, %159
debug_value %0 : $String, let, name "rawValue", argno 1 // id: %3
%4 = integer_literal $Builtin.Word, 7 // user: %6
// function_ref _allocateUninitializedArray<A>(_:) 創(chuàng)建一個(gè)元組贸伐,里面有枚舉值數(shù)組勘天,和指針
%5 = function_ref @$ss27_allocateUninitializedArrayySayxG_BptBwlF : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // user: %6
%6 = apply %5<StaticString>(%4) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // users: %8, %7
%7 = tuple_extract %6 : $(Array<StaticString>, Builtin.RawPointer), 0 // users: %80, %79
%8 = tuple_extract %6 : $(Array<StaticString>, Builtin.RawPointer), 1 // user: %9
%9 = pointer_to_address %8 : $Builtin.RawPointer to [strict] $*StaticString // users: %17, %69, %59, %49, %39, %29, %19
%10 = string_literal utf8 "MON" // user: %12 創(chuàng)建一個(gè)字符串
%11 = integer_literal $Builtin.Word, 3 // user: %16 它的長(zhǎng)度是3,我們?cè)趦?nèi)存中分配的大小
%12 = builtin "ptrtoint_Word"(%10 : $Builtin.RawPointer) : $Builtin.Word // user: %16
br bb1 // id: %13
bb1: // Preds: bb0
%14 = integer_literal $Builtin.Int8, 2 // user: %16
br bb2 // id: %15
bb2: // Preds: bb1
%16 = struct $StaticString (%12 : $Builtin.Word, %11 : $Builtin.Word, %14 : $Builtin.Int8) // user: %17
store %16 to %9 : $*StaticString // id: %17
%18 = integer_literal $Builtin.Word, 1 // user: %19
%19 = index_addr %9 : $*StaticString, %18 : $Builtin.Word // user: %27 獲取當(dāng)前數(shù)組中的值的地址捉邢,返回相對(duì)于首地址的第(%18)個(gè)元素的地址
%20 = string_literal utf8 "TUS" // user: %22
%21 = integer_literal $Builtin.Word, 3 // user: %26
%22 = builtin "ptrtoint_Word"(%20 : $Builtin.RawPointer) : $Builtin.Word // user: %26
br bb3 // id: %23
......
bb14: // Preds: bb13
%76 = struct $StaticString (%72 : $Builtin.Word, %71 : $Builtin.Word, %74 : $Builtin.Int8) // user: %77
store %76 to %69 : $*StaticString // id: %77
// function_ref _findStringSwitchCase(cases:string:)
%78 = function_ref @$ss21_findStringSwitchCase5cases6stringSiSays06StaticB0VG_SStF : $@convention(thin) (@guaranteed Array<StaticString>, @guaranteed String) -> Int // user: %79 遍歷之前創(chuàng)建的數(shù)組匹配就返回對(duì)應(yīng)的index
%79 = apply %78(%7, %0) : $@convention(thin) (@guaranteed Array<StaticString>, @guaranteed String) -> Int // users: %149, %138, %127, %116, %105, %94, %83, %147, %136, %125, %114, %103, %92, %81
release_value %7 : $Array<StaticString> // id: %80
debug_value %79 : $Int, let, name "$match" // id: %81
%82 = integer_literal $Builtin.Int64, 0 // user: %84
%83 = struct_extract %79 : $Int, #Int._value // user: %84
%84 = builtin "cmp_eq_Int64"(%82 : $Builtin.Int64, %83 : $Builtin.Int64) : $Builtin.Int1 // user: %85 循環(huán)比較當(dāng)前的返回值和index脯丝,根據(jù)結(jié)果往下跳轉(zhuǎn)不同的分支
cond_br %84, bb15, bb16 // id: %85
bb15: // Preds: bb14
%86 = metatype $@thin Week.Type
%87 = enum $Week, #Week.MON!enumelt // user: %89
%88 = begin_access [modify] [static] %2 : $*Week // users: %89, %90
store %87 to %88 : $*Week // id: %89
end_access %88 : $*Week // id: %90
br bb29 // id: %91
......
// 遍歷完全部枚舉值都沒(méi)有找到,就會(huì)返回一個(gè)none
bb28: // Preds: bb26
release_value %0 : $String // id: %158
dealloc_stack %2 : $*Week // id: %159
%160 = enum $Optional<Week>, #Optional.none!enumelt // user: %161
br bb30(%160 : $Optional<Week>) // id: %161
// 匹配成功就創(chuàng)建一個(gè)可選項(xiàng)返回
bb29: // Preds: bb27 bb25 bb23 bb21 bb19 bb17 bb15
%162 = load %2 : $*Week // user: %163
%163 = enum $Optional<Week>, #Optional.some!enumelt, %162 : $Week // user: %166
release_value %0 : $String // id: %164
dealloc_stack %2 : $*Week // id: %165
br bb30(%163 : $Optional<Week>) // id: %166
// %167 // user: %168
bb30(%167 : $Optional<Week>): // Preds: bb29 bb28
return %167 : $Optional<Week> // id: %168
} // end sil function '$s4main4WeekO8rawValueACSgSS_tcfC'
-
Swift
源碼中_findStringSwitchCase
的實(shí)現(xiàn)
/// The compiler intrinsic which is called to lookup a string in a table
/// of static string case values.
@_semantics("findStringSwitchCase")
public // COMPILER_INTRINSIC
func _findStringSwitchCase(
cases: [StaticString],
string: String) -> Int {
for (idx, s) in cases.enumerated() {
if String(_builtinStringLiteral: s.utf8Start._rawValue,
utf8CodeUnitCount: s._utf8CodeUnitCount,
isASCII: s.isASCII._value) == string {
return idx
}
}
return -1
}
- 再看下它是如何獲取
rawValue
的
// main 這里調(diào)用Week.rawValue.getter伏伐,返回一個(gè)字符串
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
alloc_global @$s4main1aSSvp // id: %2
%3 = global_addr @$s4main1aSSvp : $*String // user: %8
%4 = metatype $@thin Week.Type
%5 = enum $Week, #Week.MON!enumelt // user: %7 創(chuàng)建一個(gè)枚舉值 MON
// function_ref Week.rawValue.getter
%6 = function_ref @$s4main4WeekO8rawValueSSvg : $@convention(method) (Week) -> @owned String // user: %7
%7 = apply %6(%5) : $@convention(method) (Week) -> @owned String // user: %8
store %7 to %3 : $*String // id: %8
%9 = integer_literal $Builtin.Int32, 0 // user: %10
%10 = struct $Int32 (%9 : $Builtin.Int32) // user: %11
return %10 : $Int32 // id: %11
} // end sil function 'main'
// Week.rawValue.getter
sil hidden @$s4main4WeekO8rawValueSSvg : $@convention(method) (Week) -> @owned String {
// %0 "self" // users: %2, %1
bb0(%0 : $Week):
debug_value %0 : $Week, let, name "self", argno 1 // id: %1
switch_enum %0 : $Week, case #Week.MON!enumelt: bb1, case #Week.TUS!enumelt: bb2, case #Week.WED!enumelt: bb3, case #Week.THU!enumelt: bb4, case #Week.FRI!enumelt: bb5, case #Week.SAT!enumelt: bb6, case #Week.SUN!enumelt: bb7 // id: %2
// 匹配枚舉值巾钉,構(gòu)建字符串返回
bb1: // Preds: bb0
%3 = string_literal utf8 "MON" // user: %8
%4 = integer_literal $Builtin.Word, 3 // user: %8
%5 = integer_literal $Builtin.Int1, -1 // user: %8
%6 = metatype $@thin String.Type // user: %8
// function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
%7 = function_ref @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %8
%8 = apply %7(%3, %4, %5, %6) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %9
br bb8(%8 : $String) // id: %9
...... //省略掉其他case的判斷,只判斷為MON的時(shí)候
// %52 // user: %53
bb8(%52 : $String): // Preds: bb7 bb6 bb5 bb4 bb3 bb2 bb1
return %52 : $String // id: %53
} // end sil function '$s4main4WeekO8rawValueSSvg'
- 這種情況下枚舉的值都是字符串常量秘案,存儲(chǔ)的位置應(yīng)該是在mach-o文件的
__TEXT,__cstring
,可以看到潦匈,當(dāng)前我們指定enum
的RawValue
為String
之后阱高,系統(tǒng)自動(dòng)創(chuàng)建了一塊連續(xù)的內(nèi)存空間來(lái)默認(rèn)存放當(dāng)前case
對(duì)應(yīng)的字符串。
image
這里需要注意的一點(diǎn)就是枚舉值和
RawValue
是兩個(gè)不同的東?茬缩,我們沒(méi)有辦法把一個(gè)枚舉值分配一個(gè)String
的變量赤惊,即使這個(gè)enum
是String
類型的
關(guān)聯(lián)值
- 在
Swift
中,我們可以通過(guò)枚舉值表達(dá)一個(gè)復(fù)雜的信息凰锡,就是關(guān)聯(lián)值未舟,通過(guò)枚舉來(lái)表達(dá)一個(gè)形狀,有圓形掂为,?方形裕膀,圓形有半徑,?方形有寬勇哗,高昼扛,這個(gè)時(shí)候關(guān)聯(lián)值就顯得非常有用了
enum Shape {
case circle(radious:Double)
case rectangle(width:Double,height:Double)
}
- 我們使用了關(guān)聯(lián)值之后,就沒(méi)有
RawValue
欲诺,因?yàn)槲覀兠總€(gè)case
都有一組值來(lái)表達(dá)抄谐,所以不需要使用RawValue
渺鹦,這里我們使用的radious
、width
蛹含、height
是我們自己取的標(biāo)簽毅厚,可以省略的 - 創(chuàng)建一個(gè)關(guān)聯(lián)值的枚舉值
enum Shape {
case circle(radious:Double)
case rectangle(width:Double,height:Double)
}
var circle = Shape.circle(radious: 10.0)
var rectangle = Shape.rectangle(width: 15.0, height: 18.0)
枚舉的其他用法
模式匹配
- 使用
switch
匹配枚舉值時(shí),需要遍歷所有可能的情況浦箱,不然編譯器會(huì)報(bào)錯(cuò)吸耿,如果不想匹配所有的case
,可以使用defalut
關(guān)鍵字代表默認(rèn)的情況憎茂。
image - 關(guān)聯(lián)值的匹配
enum Shape {
case circle(radious:Double)
case rectangle(width:Double,height:Double)
}
var circle = Shape.circle(radious: 10.0)
var rectangle = Shape.rectangle(width: 15.0, height: 18.0)
switch circle {
case let .circle(radious): print("circle radious\(radious)")
case let .rectangle(width, height) : print("rectangle width:\(width),height:\(height)")
}
switch rectangle {
case .circle(let radious): print("circle radious\(radious)")
case .rectangle(let width,let height) : print("rectangle width:\(width),height:\(height)")
}
- 匹配單個(gè)關(guān)聯(lián)值
if case let Shape.circle(radious) = circle {
print(radious)
}
- 不同的
case
的相同的關(guān)聯(lián)值
enum Shape{
case circle(radious: Double, diameter: Double)
case rectangle(width: Double, height: Double)
case square(width: Double, width: Double)
}
let shape = Shape.circle(radious: 10.0, diameter: 20.0)
switch shape {
case let .circle(x, 20.0), let .square(x, 20.0):
print(x)
default:
break
}
- 使用通配符
switch shape {
case let .circle(_, x), let .square(x, _):
print(x)
default:
break
}
switch shape {
case let .circle(x, y), let .rectangle(y, x):
print("x=\(x),y=\(y)")
default:
break
}
枚舉的嵌套
- 枚舉中嵌套枚舉
enum CombineDirect{
enum BaseDirect{
case up
case down
case left
case right
}
case leftUp(combineElement1: BaseDirect, combineElement2: BaseDirect)
case rightUp(combineElement1: BaseDirect, combineElement2: BaseDirect)
case leftDown(combineElement1: BaseDirect, combineElement2: BaseDirect)
case rightDown(combineElement1: BaseDirect, combineElement2: BaseDirect)
}
結(jié)構(gòu)體中的嵌套
- 結(jié)構(gòu)體中嵌套枚舉
struct Skill {
enum KeyType {
case up
case down
case left
case right
}
let key : KeyType
}
枚舉中包含屬性和方法
-
枚舉中能夠包含計(jì)算屬性珍语,類型屬性,不能包含存儲(chǔ)屬性
image - 枚舉中定義實(shí)例方法竖幔、
static
方法
enum Week : Int {
case MON
case TUS
case WED
case THU
case FRI
case SAT
case SUN
mutating func nextDay() {
if self == .SUN {
self = Week(rawValue: 1)!
}else{
self = Week(rawValue: 1+self.rawValue)!
}
}
static func doSomething() {
print("111\(self.init(rawValue: 1))")
}
}
枚舉的大小
- 一個(gè)
case
時(shí)候的大小
enum noMean {
case a
}
print(MemoryLayout<noMean>.size)
print(MemoryLayout<noMean>.stride)
// 打印結(jié)果為:0 1
- 普通的
case
沒(méi)有關(guān)聯(lián)值的時(shí)候板乙,枚舉的大小為一個(gè)字節(jié),一個(gè)字節(jié)所能表示的最大值為255拳氢,如果我們的case
大于255的時(shí)候大小就會(huì)由Int8
->Int16
募逞,如果不夠就會(huì)繼續(xù)擴(kuò)大2倍
enum noMean {
case a
case b
}
print(MemoryLayout<noMean>.size)
print(MemoryLayout<noMean>.stride)
// 打印結(jié)果為:1 1
// 修改rawValue的類型 打印結(jié)果不變
enum noMean : String {
case a
case b
}
// 打印結(jié)果為:1 1
- 有關(guān)聯(lián)值時(shí)候的枚舉的大小取決于最大的
case
內(nèi)存大小,但是大家看每次應(yīng)該是16馋评, 24放接, 32 這樣的偶數(shù),但是這里每次都是多加1留特,這里是不是就是我們當(dāng)前case
的值啊纠脾,占用 1 字節(jié),只有一個(gè)case
的時(shí)候蜕青,默認(rèn)size
為0苟蹈。
enum Shape{
case circle(radious: Double)
}
print(MemoryLayout<Shape>.size)
print(MemoryLayout<Shape>.stride)
// 打印結(jié)果為 8,8
enum Shape{
case circle(radious: Double)
case rectangle(width: Double, height: Double)
}
print(MemoryLayout<Shape>.size)
print(MemoryLayout<Shape>.stride)
// 打印結(jié)果為17右核,24
-
打印內(nèi)存結(jié)構(gòu)
image
需要注意的一點(diǎn)是慧脱,由于字節(jié)對(duì)齊的原因,有時(shí)候`case`的1字節(jié)會(huì)被編譯器優(yōu)化加到前面的位置上贺喝,它的大小不會(huì)再+1
-
通過(guò)一段代碼來(lái)驗(yàn)證下剛剛的結(jié)論
image - 修改
circle
的radious
的類型為Int
image - 總結(jié)
- 1菱鸥,
RawValue
的枚舉大小默認(rèn)是一字節(jié)(UInt8),也就意味著當(dāng)前最大能存儲(chǔ)的大小是 255躏鱼。如果超過(guò)這個(gè)大小氮采,當(dāng)前枚舉的大小就會(huì)從UInt8
->UInt16
依次類推 - 2,關(guān)聯(lián)值的枚舉大小與最大的
case
的內(nèi)存大小相關(guān)染苛。
- 1菱鸥,
indirect關(guān)鍵字
- 我們想通過(guò)枚舉實(shí)現(xiàn)一個(gè)鏈表扳抽,就需要使用
indirect
關(guān)鍵字
image - 通過(guò)編譯器的修正后的寫法如下
indirect enum List<T> {
case end
case node(T, next : List<T>)
}
或
enum List<T> {
case end
indirect case node(T, next : List<T>)
}
- 枚舉是值類型,也就意味著它的大小在編譯期就確定了,那么這個(gè)過(guò)程中對(duì)于我們當(dāng)前這個(gè)
List
的大小就不能確定贸呢,從系統(tǒng)的?度镰烧,我不知道當(dāng)前要給這個(gè)枚舉分配多大的內(nèi)存空間。所以怎么辦楞陷?官方文檔上有如下解釋
You indicate that an enumeration case is recursive by writing indi rect before it, which tells the compiler to insert the necessary l ayer of indirection.
-
打印下它的大小
image -
打印下實(shí)例對(duì)象的內(nèi)存結(jié)構(gòu)
image - 查看sil文件怔鳖,
alloc_box
的本質(zhì)就是調(diào)用swift_allocObjet
,所以indirect
關(guān)鍵字其實(shí)就是通知編譯器固蛾,我當(dāng)前的枚舉是遞歸的结执,自然而然大小也就不確定,所以趕緊給我分配一塊堆區(qū)的內(nèi)存空間存放艾凯。
image