枚举的基本你用法
enum Direction_1 {
case north, south, east, west
}
enum Direction {
case north
case south
case east
case west
}
var dir = Direction.west
dir = Direction.east
dir = .north
print(dir)
switch dir {
case .north:
print("north")
case .south:
print("south")
case .east:
print("east")
case .west:
print("west")
}
关联值(Associated Values)
关联值是直接存在枚举变量的内存里面的,这点要牢记
,对于一个有固定取值范围的变量,设计成枚举比较合适
enum Score {
case points(Int)
case grade(Character)
}
var score = Score.points(96)
score = .grade("A")
switch score {
case let .points(i):
print(i, "points")
case let .grade(i):
print("grade", i)
} // grade A
enum Date {
case digit(year: Int, month: Int, day: Int)
case string(String)
}
var date = Date.digit(year: 2020, month: 02, day: 29)
date = .string("2020-02-29")
switch date {
case let .digit(year, month, day):
print(year, month, day)
case let .string(dateStr):
print(dateStr)
} // "2020-02-29"
注意上看switch内部对
let/var
关键字的使用,如果下载枚举值左边,那么关联值只能统一绑定给let常量
或者var变量
case let .digit(year, month, day): //year、month、day都是let常量
case var .digit(year, month, day): //year、month、day都是var变量
如果let/var关键字写在关联值括号内,就比较灵活
case .digit(let year, var month, let day)
另外一些枚举举例
enum Password {
case number(Int, Int, Int, Int)
case gesture(String)
}
var pwd = Password.number(3, 5, 7, 9)
pwd = .gesture("3259")
switch pwd {
case let .number(n1 , n2 , n3 , n4 ): //数字密码
print("number is", n1, n2, n3, n4)
case let .gesture(pwdStr):// 字符串密码
print("gestrue is", pwdStr)
}
原始值(Raw Values)
枚举成员可以只用相同类型的默认值预先关联,这个默认值叫做 原始值
enum PokerSuit: Character { //这里的Character表示的是枚举值所关联的原始值
case spade = "️"
case heart = "️"
case diamond = "️"
case club = "️"
}
var suit = PokerSuit.spade
print(suit)
print(suit.rawValue)
print(PokerSuit.club.rawValue)
enum Grade: String {
case perfect = "A"
case great = "B"
case good = "C"
case bad = "D"
}
print(Grade.perfect.rawValue) // A
print(Grade.great.rawValue) // B
print(Grade.good.rawValue) // C
print(Grade.bad.rawValue) // D
隐式原始值(Implicitly Assigned Raw Values)
enum Direction1: String {
case north, south, east, west
}
print(Direction1.north.rawValue)
enum Direction2: String {
case north = "nor", south, east, west
}
print(Direction2.north.rawValue)//有赋值,就用赋值的字符串
print(Direction2.south.rawValue)//没赋值, 就用case名字符串
enum Season: Int {
case spring, summer, autumn, winter
}
print(Season.spring.rawValue)//0
print(Season.summer.rawValue)//1
print(Season.autumn.rawValue)//2
print(Season.winter.rawValue)//3
enum Season2: Int {
case spring = 2, summer, autumn = 6, winter
}
print(Season2.spring.rawValue) //2
print(Season2.summer.rawValue) //3
print(Season2.autumn.rawValue) //6
print(Season2.winter.rawValue) //7
递归枚举(Recursive Enumeration)
//书写方法一
indirect enum ArithExpr_1 {
case number(Int)
case sum(ArithExpr, ArithExpr)
case difference(ArithExpr, ArithExpr)
}
//书写方法二
enum ArithExpr {
case number(Int)
indirect case sum(ArithExpr, ArithExpr)
indirect case difference(ArithExpr, ArithExpr)
}
let five = ArithExpr.number(5)
let four = ArithExpr.number(4)
let two = ArithExpr.number(2)
let sum = ArithExpr.sum(five, four)
let difference = ArithExpr.difference(sum, two)
func calculate(_ expr: ArithExpr) -> Int{
switch expr {
case let .number(value):
return value
case let .sum(left, right):
return calculate(left) + calculate(right)
case let .difference(left, right):
return calculate(left) - calculate(right)
}
}
MemoryLayout
我们可以使用
MemoryLayout
来获取数据类型占用的内存大小,相当于C里面使用的sizeof
enum Password2 {
case number(Int, Int, Int, Int)
case other
}
MemoryLayout.stride //系统分配给变量的内存大小--40
MemoryLayout.size //实际被使用的内存大小--33
MemoryLayout.alignment //对其参数--8
var pd = Password2.number(9, 8, 7, 6)
pd = .other
print(pd) //"other/n"
MemoryLayout.stride(ofValue: pd) //40
MemoryLayout.size(ofValue: pd) //33
MemoryLayout.alignment(ofValue: pd) //8
枚举在内存中是如何存储的?
通过MemoryLayout,我们只能简单查看一些内存相关的信息,但还不足以看清枚举在内存中的具体细节,由于Xcode调试工具无法为我们提供枚举变量的内存地址,因此需要借助一些额外的工具,这里推介一下大牛李明杰的一个工具。
(1)首先来看下一种简单的情况~~~~~~~
enum TestEnum {
case test1, test2, test3
}
print("系统实际分配内存",MemoryLayout.stride)
print("实际使用的内存",MemoryLayout.size)
print("内存对齐参数",MemoryLayout.alignment)
var t = TestEnum.test1
print("枚举变量t的内存地址:",Mems.ptr(ofVal: &t)) //这里可以输出变量t的内存地址
t = .test2
t = .test3
print("Stop for debug")
Mems.ptr(ofVal: &t)
可以帮我们获得变量t的内存地址,准备好3个断点
然后将程序运行值
断点1
处,此时我们已经获得t
的内存地址,根据该地址,调出内存界面,我们来观察一下此时的内存细节
在继续走到断点2、断点3处,对比一下各自的内存情况如下
小结:enum TestEnum { case test1, test2, test3 }
- 系统为TestEnum类型的变量分配
1个字节
的内存空间- test1 、 test2、 test3 三个case对应在内存中用整数0、1、2来表示
(2)把场景调整为有Int
型原始值的情形如下~~~~~~~
enum TestEnum: Int {
case test1
case test2 = 3
case test3
case test4 = 10
case test5
}
print("系统实际分配内存",MemoryLayout.stride)
print("实际使用的内存",MemoryLayout.size)
print("内存对齐参数",MemoryLayout.alignment)
var t = TestEnum.test1
print("枚举变量t的内存地址:",Mems.ptr(ofVal: &t)) //这里可以输出变量t的内存地址
t = .test2
t = .test3
t = .test4
t = .test5
print("Stop for debug")
按照上面同样的方法,对比各自case
的内存情况如下
我们在查看一下各自
case
的rawValue
print("test1的rawValue:", TestEnum.test1.rawValue)
print("test2的rawValue:", TestEnum.test2.rawValue)
print("test2的rawValue:", TestEnum.test3.rawValue)
print("test2的rawValue:", TestEnum.test4.rawValue)
print("test2的rawValue:", TestEnum.test5.rawValue)
***********运行结果
test1的rawValue: 0
test2的rawValue: 3
test2的rawValue: 4
test2的rawValue: 10
test2的rawValue: 11
看得出,如果原始值类型为Int
:
- 那么在不手动设定的情况下,首个
case
的原始值默为整数0
,非首个case
的默认值为上一个case
的默认值+1
- 如果手动设定了,那么原始值即为设定值。
(3)看过了带Int
型原始值的情况之后,在看一下带String
型原始值的情况,改造如下~~~~~~~
enum TestEnum: String {
case test1
case test2 = "AA"
case test3 = "汉字"
case test4 = ""
case test5
}
print("系统实际分配内存",MemoryLayout.stride)
print("实际使用的内存",MemoryLayout.size)
print("内存对齐参数",MemoryLayout.alignment)
var t = TestEnum.test1
print("枚举变量t的内存地址:",Mems.ptr(ofVal: &t)) //这里可以输出变量t的内存地址
t = .test2
t = .test3
t = .test4
t = .test5
print("Stop for debug")
print("test1的rawValue:", TestEnum.test1.rawValue)
print("test2的rawValue:", TestEnum.test2.rawValue)
print("test2的rawValue:", TestEnum.test3.rawValue)
print("test2的rawValue:", TestEnum.test4.rawValue)
print("test2的rawValue:", TestEnum.test5.rawValue)
****************运行结果
系统实际分配内存 1
实际使用的内存 1
内存对齐参数 1
枚举变量t的内存地址: 0x0000000100008218
Stop for debug
test1的rawValue: test1
test2的rawValue: AA
test2的rawValue: 汉字
test2的rawValue:
test2的rawValue: test5
Program ended with exit code: 0
内存的情况这里省略,和上面Int
型的时候是一样的,根据调试输出的情况,我们可以看出
- 如果不设置原始值,那么
case
的原始值为该case名称
的字符串 - 如果设置了原始值,那吗
case
的原始值即为设定值
总结 带原始值的枚举
- 枚举变量本身的就占一个字节
- 枚举变量所对应的内存里所存放的具体值:对应第一个case为0,并且往后逐个+1
(4)带关联值的场景~~~~~~~
enum TestEnum {
case test1(a: Int, b: Int, c: Int)
case test2(d: Int, e: Int)
case test3(f: Int)
case test4(g: Bool)
case test5
}
print("系统实际分配内存",MemoryLayout.stride)
print("实际使用的内存",MemoryLayout.size)
print("内存对齐参数",MemoryLayout.alignment)
var t = TestEnum.test1(a: 1, b: 2, c: 3)
//这里可以输出变量t的内存地址
print("枚举变量t的内存地址:",Mems.ptr(ofVal: &t))
t = .test2(d: 4, e: 5)
t = .test3(f: 6)
t = .test4(g: true)
t = .test5
print("Stop for debug")
*****************运行结果
系统实际分配内存 32
实际使用的内存 25
内存对齐参数 8
枚举变量t的内存地址: 0x0000000100008208
接下来照例在过一遍内存,下面直接贴上内存查看的结果
t = test1(a: 1, b: 2, c: 3)
01 00 00 00 00 00 00 00 --> 对应a
02 00 00 00 00 00 00 00 --> 对应b
03 00 00 00 00 00 00 00 --> 对应c
00 00 00 00 00 00 00 00 --> 对应case test1
t = test2(d: 4, e: 5)
04 00 00 00 00 00 00 00 --> 对应d
05 00 00 00 00 00 00 00 --> 对应e
00 00 00 00 00 00 00 00 --> 此时没用到
01 00 00 00 00 00 00 00 --> 对应case test2
t = test3(f: 6)
06 00 00 00 00 00 00 00 --> 对应f
00 00 00 00 00 00 00 00 --> 此时没用到
00 00 00 00 00 00 00 00 --> 此时没用到
02 00 00 00 00 00 00 00 --> 对应case test3
t = test4(g: true)
01 00 00 00 00 00 00 00 --> 对应g
00 00 00 00 00 00 00 00 --> 此时没用到
00 00 00 00 00 00 00 00 --> 此时没用到
03 00 00 00 00 00 00 00 --> 对应case test4
t = test5
00 00 00 00 00 00 00 00 --> 此时没用到
00 00 00 00 00 00 00 00 --> 此时没用到
00 00 00 00 00 00 00 00 --> 此时没用到
04 00 00 00 00 00 00 00 --> 对应case test5
总结 带关联值的枚举
- 枚举变量的
成员case
的值只用了其内存空间的1字节来存放- 枚举的
case关联值
也存放在枚举变量的内存中- 系统为枚举的
case关联值
所分配的内存空间,必须保证可以放下所需内存最大的那个关联值- 枚举变量的内存空间里,先存放存放的是
case关联值
,成员case
的值被放在最后- 枚举变量的内存总空间按内存对齐参数进行补齐(计算机常识)
(5)一些极端场景~~~~~~~
enum TestEnum {
case test
}
print("系统实际分配内存",MemoryLayout.stride)
print("实际使用的内存",MemoryLayout.size)
print("内存对齐参数",MemoryLayout.alignment)
var t = TestEnum.test
print(print("枚举变量t的内存地址:",Mems.ptr(ofVal: &t)))
****************运行结果
系统实际分配内存 1
实际使用的内存 0
内存对齐参数 1
枚举变量t的内存地址: 0x0000000000000001
Program ended with exit code: 0
可以看到,系统确实是分配了1
个字节给枚举,但是实际上用到了0
个,因为一种情况不需要做任何区分,所以也就不需要存储,当然貌似没人会这么用,所以系统针对这种情况下的处理,就不难理解了。在看看带关联值的情况:
enum TestEnum {
case test(Int)
}
print("系统实际分配内存",MemoryLayout.stride)
print("实际使用的内存",MemoryLayout.size)
print("内存对齐参数",MemoryLayout.alignment)
var t = TestEnum.test(10)
print("枚举变量t的内存地址:",Mems.ptr(ofVal: &t))
print("Stop for debug")
***************运行结果
系统实际分配内存 8
实际使用的内存 8
内存对齐参数 8
枚举变量t的内存地址: 0x0000000100007200
Stop for debug
Program ended with exit code: 0
***************汇编结果
0A 00 00 00 00 00 00 00
可以看到系统直接分配了8
个字节来存储枚举里面的Int
型关联值,没有分配空间来存储成员case
的值,原因和上面很想,因为现在就是一种case
,没有必要再存储成员变量的值,只需要关心case关联值
就好。那如果有一个以上的case,是不是就会给成员case
分配空间了?咱们试试看,如下
enum TestEnum {
case other
case test(Int)
}
print("系统实际分配内存",MemoryLayout.stride)
print("实际使用的内存",MemoryLayout.size)
print("内存对齐参数",MemoryLayout.alignment)
var t = TestEnum.other
//Mem.memStr是大神李明杰提供的工具,文中有链接,可以帮我直接获取变量的内存里面的值
print("枚举变量t = other 时的内存情况: ",Mems.memStr(ofVal: &t))
t = TestEnum.test(10)
print("枚举变量t = test(10)时的内存地址:",Mems.memStr(ofVal: &t))
print("Stop for debug")
***************运行结果
系统实际分配内存 16
实际使用的内存 9
内存对齐参数 8
枚举变量t = other 时的内存情况: 0x0000000000000000 0x0000000000000001
枚举变量t = test(10)时的内存地址: 0x000000000000000a 0x0000000000000000
可以看出,只要case
大于1
个,除了Int
型关联值需要占用8
个字节外,枚举变量还使用了1
个字节来存储成员case
的值,根据内存对齐参数8
,系统给枚举变量分配了16
字节空间。上面的结果中,最后一个字节是用来存放成员case
的值也就是case other
对应了01
, case test
对应了00
,但是感觉顺序不太对,明明是other
在前,test
在后的,带着这个疑问,我们把用例改造如下
enum TestEnum {
case aaa
case test(Int)
case ccc
case test3(Int, Int)
case test2(Int,Int, Int)
case other
}
print("系统实际分配内存",MemoryLayout.stride)
print("实际使用的内存",MemoryLayout.size)
print("内存对齐参数",MemoryLayout.alignment)
var t = TestEnum.aaa
print("t = .aaa的内存情况: ",Mems.memStr(ofVal: &t))
t = TestEnum.test(10)
print("t = .test(10)的内存情况: ",Mems.memStr(ofVal: &t))
t = TestEnum.ccc
print("t = .ccc的内存情况: ",Mems.memStr(ofVal: &t))
t = TestEnum.test3(16, 32)
print("t = .test3(16, 32)的内存情况: ",Mems.memStr(ofVal: &t))
t = TestEnum.test2(20, 20, 20)
print("t = .test2(20, 20, 20)的内存情况:",Mems.memStr(ofVal: &t))
t = TestEnum.other
print("t = .other的内存情况: ",Mems.memStr(ofVal: &t))
print("Stop for debug")
*************************************运行结果
系统实际分配内存 32
实际使用的内存 25
内存对齐参数 8
t = .aaa的内存情况: 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000003
t = .test(10)的内存情况: 0x000000000000000a 0x0000000000000000 0x0000000000000000 0x0000000000000000
t = .ccc的内存情况: 0x0000000000000001 0x0000000000000000 0x0000000000000000 0x0000000000000003
t = .test3(16, 32)的内存情况: 0x0000000000000010 0x0000000000000020 0x0000000000000000 0x0000000000000001
t = .test2(20, 20, 20)的内存情况: 0x0000000000000014 0x0000000000000014 0x0000000000000014 0x0000000000000002
t = .other的内存情况: 0x0000000000000002 0x0000000000000000 0x0000000000000000 0x0000000000000003
Stop for debug
Program ended with exit code: 0
从上面的调试,又挖掘了一点小细节:
- 对于有关联值的
成员case
,它的case
值会根据定义的顺序,默认从0开始+1累加, - 其余所有不带关联值的
成员case
,它们的case
值相同,而且都等于最后一个可关联成员case 的值+1
关联值 VS 原始值rawValue
以上我们看清楚了简单枚举、关联值枚举、原始值枚举在内存中分别是如何存储的,可以看出,枚举的关联值和原始值又以下区别:
- 内存角度:关联值是直接存储在枚举变量内存里面的,而原始值则不是,因为原始值是通过
xx.rawValue
访问的,因此它的值完全不需要存储,可以在枚举定义完之后通过方法提供给外部。 - 使用角度:原始值必须在枚举定义的时候确定原始值类型,才能被使用
enum Direction
: String/Int/...
{...}
。关联值则必须在枚举定义的时候,确定好case
所对应的关联值类型 - 赋值:关联值只能在枚举
case
被赋值给变量的时候进行赋值,因为同一个case
每次被赋值给变量,都需要设定一个关联值,因此也可以说关联值是可以改变的,如下
enum Score {
case points(Int)
case grade(Character)
}
var score = Score.points(96)
score = .grade("A")
score = .grade("B") -->相同的case,不同的关联值
而原始值,只能在枚举定义的时候进行赋值,不赋值则系统会给定相应的默认值,也就是只有一次机会可以赋值,定义完枚举之后,就没有办法可以更改原始值了,示例如下
enum Grade: String {
case perfect = "A"
case great
case good = "C"
case bad = "D"
}
print(Grade.perfect.rawValue) --> A
print(Grade.great.rawValue) --> 定义时无赋值,系统默认为case的名称 great
print(Grade.good.rawValue) --> C
print(Grade.bad.rawValue) -> D