C语言的枚举
- C语言的枚举写法
enum 枚举名 {
枚举值1,
枚举值2,
......
};
- 我们通过枚举表示一周的七天
enum Week {
MON,TUS,WED,THU,FRI,SAT,SUN
};
- c语言中,枚举的第一个成员默认是为0,后面的枚举值一次类推,也可以直接指定对应的值,它后面的枚举值依然是一次递增
enum Week {
MON=3,TUS,WED,THU=9,FRI,SAT,SUN
};
// 这个枚举对应的值就是3,4,5,9,10,11,12
Swift的枚举
Swift枚举的使用
- 同样实现这个枚举,其写法如下
enum Week {
case MON
case TUS
case WED
case THU
case FRI
case SAT
case SUN
}
// 也可以直接一个case,用逗号隔开
enum Week {
case MON,TUS,WED,THU,FRI,SAT,SUN
}
- 这样表达的是一个整型的枚举,和c语言的是一致的,
Swift
中也可以用枚举表达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
,如果我们没有写后面的字符串,会默认的隐式RawValue分配
,也可以对枚举的成员单独指定RawValue
- 查看sil文件,可以看到自动生成了一个
init?(rawValue:)
方法,和一个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 }
}
- 我们来看下
init?(rawValue:)
函数是什么时候调用的,通过符号断点来观察
- 运行发现:
Week.MON.rawValue
并不会触发init
函数,只有调用Week(rawValue: "MON")
才会进到符号断点,再通过打印下面的代码
print(Week(rawValue: "MON"))
print(Week(rawValue: "hello"))
// 打印结果
Optional(SwiftEnumerate.Week.MON)
nil
- 查看sil文件中
init
方法的实现,首先我们会将枚举值全部放到一个数组里面,然后通过_findStringSwitchCase
方法匹配传入的字符串,如果匹配到了就返回一个index
,没有匹配到就返回一个-1
,再循环遍历所有的枚举值,匹配到了就取出对应的枚举值,然后包装成一个可选项的值返回,如果都没有匹配到返回的就是nil,所以返回值是一个可选项。
// 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>(_:) 创建一个元组,里面有枚举值数组,和指针
%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 创建一个字符串
%11 = integer_literal $Builtin.Word, 3 // user: %16 它的长度是3,我们在内存中分配的大小
%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 获取当前数组中的值的地址,返回相对于首地址的第(%18)个元素的地址
%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 遍历之前创建的数组匹配就返回对应的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 循环比较当前的返回值和index,根据结果往下跳转不同的分支
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
......
// 遍历完全部枚举值都没有找到,就会返回一个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
// 匹配成功就创建一个可选项返回
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
的实现
/// 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 这里调用Week.rawValue.getter,返回一个字符串
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 创建一个枚举值 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
// 匹配枚举值,构建字符串返回
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的时候
// %52 // user: %53
bb8(%52 : $String): // Preds: bb7 bb6 bb5 bb4 bb3 bb2 bb1
return %52 : $String // id: %53
} // end sil function '$s4main4WeekO8rawValueSSvg'
- 这种情况下枚举的值都是字符串常量,存储的位置应该是在mach-o文件的
__TEXT,__cstring
,可以看到,当前我们指定enum
的RawValue
为String
之后,系统自动创建了一块连续的内存空间来默认存放当前case
对应的字符串。
这里需要注意的一点就是枚举值和
RawValue
是两个不同的东⻄,我们没有办法把一个枚举值分配一个String
的变量,即使这个enum
是String
类型的
关联值
- 在
Swift
中,我们可以通过枚举值表达一个复杂的信息,就是关联值,通过枚举来表达一个形状,有圆形,⻓方形,圆形有半径,⻓方形有宽,高,这个时候关联值就显得非常有用了
enum Shape {
case circle(radious:Double)
case rectangle(width:Double,height:Double)
}
- 我们使用了关联值之后,就没有
RawValue
,因为我们每个case
都有一组值来表达,所以不需要使用RawValue
,这里我们使用的radious
、width
、height
是我们自己取的标签,可以省略的 - 创建一个关联值的枚举值
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
匹配枚举值时,需要遍历所有可能的情况,不然编译器会报错,如果不想匹配所有的case
,可以使用defalut
关键字代表默认的情况。
- 关联值的匹配
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)")
}
- 匹配单个关联值
if case let Shape.circle(radious) = circle {
print(radious)
}
- 不同的
case
的相同的关联值
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)
}
结构体中的嵌套
- 结构体中嵌套枚举
struct Skill {
enum KeyType {
case up
case down
case left
case right
}
let key : KeyType
}
枚举中包含属性和方法
-
枚举中能够包含计算属性,类型属性,不能包含存储属性
- 枚举中定义实例方法、
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))")
}
}
枚举的大小
- 一个
case
时候的大小
enum noMean {
case a
}
print(MemoryLayout<noMean>.size)
print(MemoryLayout<noMean>.stride)
// 打印结果为:0 1
- 普通的
case
没有关联值的时候,枚举的大小为一个字节,一个字节所能表示的最大值为255,如果我们的case
大于255的时候大小就会由Int8
->Int16
,如果不够就会继续扩大2倍
enum noMean {
case a
case b
}
print(MemoryLayout<noMean>.size)
print(MemoryLayout<noMean>.stride)
// 打印结果为:1 1
// 修改rawValue的类型 打印结果不变
enum noMean : String {
case a
case b
}
// 打印结果为:1 1
- 有关联值时候的枚举的大小取决于最大的
case
内存大小,但是大家看每次应该是16, 24, 32 这样的偶数,但是这里每次都是多加1,这里是不是就是我们当前case
的值啊,占用 1 字节,只有一个case
的时候,默认size
为0。
enum Shape{
case circle(radious: Double)
}
print(MemoryLayout<Shape>.size)
print(MemoryLayout<Shape>.stride)
// 打印结果为 8,8
enum Shape{
case circle(radious: Double)
case rectangle(width: Double, height: Double)
}
print(MemoryLayout<Shape>.size)
print(MemoryLayout<Shape>.stride)
// 打印结果为17,24
-
打印内存结构
需要注意的一点是,由于字节对齐的原因,有时候`case`的1字节会被编译器优化加到前面的位置上,它的大小不会再+1
-
通过一段代码来验证下刚刚的结论
- 修改
circle
的radious
的类型为Int
- 总结
- 1,
RawValue
的枚举大小默认是一字节(UInt8),也就意味着当前最大能存储的大小是 255。如果超过这个大小,当前枚举的大小就会从UInt8
->UInt16
依次类推 - 2,关联值的枚举大小与最大的
case
的内存大小相关。
- 1,
indirect关键字
- 我们想通过枚举实现一个链表,就需要使用
indirect
关键字
- 通过编译器的修正后的写法如下
indirect enum List<T> {
case end
case node(T, next : List<T>)
}
或
enum List<T> {
case end
indirect case node(T, next : List<T>)
}
- 枚举是值类型,也就意味着它的大小在编译期就确定了,那么这个过程中对于我们当前这个
List
的大小就不能确定,从系统的⻆度,我不知道当前要给这个枚举分配多大的内存空间。所以怎么办?官方文档上有如下解释
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.
-
打印下它的大小
-
打印下实例对象的内存结构
- 查看sil文件,
alloc_box
的本质就是调用swift_allocObjet
,所以indirect
关键字其实就是通知编译器,我当前的枚举是递归的,自然而然大小也就不确定,所以赶紧给我分配一块堆区的内存空间存放。