枚举的基本用法
枚举的名称建议大写开头,成员名小写开头
- 枚举定义
enum Direction {
case north
case east
case south
case west
}
// 或者
enum Direction {
case north, east, south, west
}
- 枚举值使用
var dir = Direction.north
dir = .east
print(dir) // east
switch(dir) {
case .north:
print("north")
case .east:
print("east")
case .south:
print("south")
case .west:
print("west")
}
- 关联值
有时会将枚举的成员值跟其他类型的值关联存储在一起,可以理解为直接将值存储在枚举里。
关联值应用的例子
enum Score {
case points(Int)
case grade(Character)
}
var score = Score.points(99)
score = .grade("A")
switch (score) {
case let .points(i):
print("分数:\(i)")
case let .grade(i):
print("等级:\(i)")
}
enum Date {
case digit(year: Int, month: Int, day: Int)
case string(String) // 参数标签可省略
}
var date = Date.digit(year: 2022, month: 3, day: 11)
date = .string("2022-3-11")
switch (date) {
case .digit(let year, let month, let day): // 也可以写case let .digit(year, month, day):
print("日期是:\(year)-\(month)-\(day)")
case let .string(value): // let也可以用var,表示value可以修改的
print("日期是:\(value)")
}
- 原始值(Raw Values)
枚举成员可以使用相同类型的默认值预先关联,这个默认值叫做:原始值
enum Week : Int { // Int表示枚举的原始值的类型
case monday = 1
case tuesday = 2
case wednesday = 3
case thuesday = 4
case friday = 5
}
var week = Week.monday
print(week) // monday
print(week.rawValue) // 1
print(Week.tuesday.rawValue) // 2
-
原始值的隐式赋值
如果枚举的原始值类型是Int、String , Swift会自动分配原始值
enum Direction : String {
case north // 默认关联值为"north"
case south // 默认关联值为"south"
}
// 等价于
enum Direction : String {
case north = "north"
case south = "south"
}
Int型的关联值,默认从0开始,下一个枚举值会递增
enum Week : Int {
case monday // 关联值默认为0
case tuesday // 关联值默认为1
case wednesday // 关联值默认为2
}
enum Week : Int {
case monday = 1 // 关联值为1
case tuesday // 递增1关联值默认为2
case wednesday = 5 // 关联值为5
}
递归枚举
递归枚举需要加关键字indirect,加在enum前面或者有递归的成员case前面都行。
indirect enum Calculate {
case num(Int)
case add(Calculate, Calculate)
case reduce(Calculate, Calculate)
}
func calculateMethod(exp: Calculate) -> Int {
switch (exp) {
case let .num(value):
return value
case let .add(left, right):
return calculateMethod(exp: left) + calculateMethod(exp: right)
case let .reduce(left, right):
return calculateMethod(exp: left) - calculateMethod(exp: right)
}
}
var num1 = Calculate.num(10)
var num2 = Calculate.num(20)
var sum = Calculate.add(num1, num2)
var reduce = Calculate.reduce(num1, num2)
calculateMethod(exp: sum) // 30
calculateMethod(exp: reduce) // -10
MemoryLayout
可以使用MemoryLayout获取数据类型占用的内存大小
- MemoryLayout后面可以接一个泛型,获取泛型的实际占用大小、分配大小、内存对齐大小
MemoryLayout<Int>.size // 8字节 实际占用大小
MemoryLayout<Int>.stride // 8字节 分配大小
MemoryLayout<Int>.alignment // 8字节 内存对齐大小
解释:因为Int类型在64位系统上是占用64位的,也就是8字节
- MemoryLayout后面也可以传入一个具体的值,获取值的类型实际占用大小、分配大小、内存对齐大小
enum Password {
case number(Int, Int, Int, Int)
case other
}
var psw = Password.number(1, 2, 3, 4)
var other = Password.other
MemoryLayout.size(ofValue: psw) // 实际占用大小33字节
MemoryLayout.stride(ofValue: psw) // 分配大小40字节
MemoryLayout.alignment(ofValue: psw) // 内存对齐大小8字节
MemoryLayout.size(ofValue: other) // 实际占用大小33字节
MemoryLayout.stride(ofValue: other) // 分配大小40字节
MemoryLayout.alignment(ofValue: other) // 内存对齐大小8字节
解释:
Password枚举类型有2个成员,number成员有4个Int型关联值,number需要开辟4*8=32个字节空间用来存储,所以number占用32字节。枚举还需要一个字节用来存储每个枚举的序号,比如number存的序号是0,other存的序号是1。所以枚举实际占用32+1 = 33个字节。
因为内存对齐大小是8字节,也就是说虽然实际只占用33个字节,但是要内存对齐,至少是8的倍数,所以要分配40个字节。
- 再来看一个例子
enum Numbers : Int {
case one = 10, two = 20, three
}
let num = Numbers.one
MemoryLayout.size(ofValue: num) // 实际占用大小1字节
MemoryLayout.stride(ofValue: num) // 分配大小1字节
MemoryLayout.alignment(ofValue: num) // 内存对齐大小1字节
解释:
枚举Numbers的成员都没有关联值,只有原始值,这样的成员不需要开辟空间来存储值,它内部仅仅只存一个序号,one里存的是0,two里存的是1,使用rawValue方法的时候才会返回它对应的原始值。所以即使它的类型是Int型,也只占用一个字节,一个字节就够存储这些序号了。
enum Numbers {
case one
}
let num = Numbers.one
MemoryLayout.size(ofValue: num) // 实际占用大小0字节
MemoryLayout.stride(ofValue: num) // 分配大小1字节
MemoryLayout.alignment(ofValue: num) // 内存对齐大小1字节
解释:
枚举只有一个枚举值,根本就不需要区分,所以实际只占用0字节。但是内存对齐最少为1字节,所以得分配1个字节空间。
enum Numbers {
case one(Int)
}
let num = Numbers.one(10)
MemoryLayout.size(ofValue: num) // 实际占用大小8字节
MemoryLayout.stride(ofValue: num) // 分配大小8字节
MemoryLayout.alignment(ofValue: num) // 内存对齐大小8字节
解释:
枚举只有一个枚举值,根本就不需要区分,所以不需要分配一个字节空间来存储编号。实际占用空间就是关联值Int的8字节,内存对齐8字节,分配大小8字节。
总结:
枚举用一个字节空间来存储编号(只有一个枚举值的不分配),如果有关联值,根据关联值数量和类型再开辟空间存储关联值。
汇编探究枚举结构
首先定义一个枚举,并生成p1和p2两个枚举值。
enum Password {
case number(Int, Int, Int, Int)
case other
}
var p1 = Password.number(10, 20, 30, 40)
var p2 = Password.other
断点后查看汇编指令,生成p1和p2的部分如下所示:
0x1000035d8 <+24>: movq $0xa, 0x4a8d(%rip)
0x1000035e3 <+35>: movq $0x14, 0x4a8a(%rip)
0x1000035ee <+46>: movq $0x1e, 0x4a87(%rip)
0x1000035f9 <+57>: movq $0x28, 0x4a84(%rip)
0x100003604 <+68>: movb $0x0, 0x4a85(%rip)
0x10000360b <+75>: movq $0x0, 0x4a82(%rip)
0x100003616 <+86>: movq $0x0, 0x4a7f(%rip)
0x100003621 <+97>: movq $0x0, 0x4a7c(%rip)
0x10000362c <+108>: movq $0x0, 0x4a79(%rip)
0x100003637 <+119>: movb $0x1, 0x4a7a(%rip)
提示:
- rip中存储的是指令的地址,准确的说是CPU将要执行的下一条指令的地址。
- 汇编最左边的就是指令的地址。
- movq是值移动操作,且占8字节,movb也是值移动操作,占1字节。
汇编解读:
1、将值0xa赋值给 (0x4a8d + rip地址 = 0x4a8d + 0x1000035e3 = 0x100008070)所在的地址中。
2、将值0x14赋值给 (0x4a8a + rip地址 = 0x4a8a + 0x1000035ee = 0x100008078)所在的地址中。
3、将值0x1e赋值给 (0x4a87 + rip地址 = 0x4a87 + 0x1000035f9 = 0x100008080)所在的地址中。
4、将值0x28赋值给 (0x4a84 + rip地址 = 0x4a84 + 0x100003604 = 0x100008088)所在的地址中。
5、将值0x0赋值给 (0x4a85 + rip地址 = 0x4a85 + 0x10000360b = 0x100008090)所在的地址中。
6、将值0x0赋值给 (0x4a82 + rip地址 = 0x4a82 + 0x100003616 = 0x100008098)所在的地址中。
7、将值0x0赋值给 (0x4a7f + rip地址 = 0x4a7f + 0x100003621 = 0x1000080a0)所在的地址中。
8、将值0x0赋值给 (0x4a7c + rip地址 = 0x4a7c + 0x10000362c = 0x1000080a8)所在的地址中。
9、将值0x0赋值给 (0x4a79 + rip地址 = 0x4a79 + 0x100003637 = 0x1000080b0)所在的地址中。
10、将值0x1赋值给 (0x4a7a + rip地址 = 0x4a7a + 0x10000363e = 0x1000080b8)所在的地址中。
总结:
- 第1~4句指令就是将10、20、30、40赋值给枚举的存储空间地址里,因为是movq操作指令,所以每个占8字节。 第5句指令是将0赋值给枚举的成员值地址里,因为是movb操作指令,所以占用1字节。
- 第1~4句指令就是将0、0、0、0赋值给枚举的存储空间地址里,因为是movq操作指令,所以每个占8字节。第5句指令是将1赋值给枚举的成员值地址里,因为是movb操作指令,所以占用1字节。
- 所以可知枚举是有一个字节存储成员值,另外会根据关联值类型和个数来开辟一定空间来存储关联值。
查看枚举的内存
0x100008070其实就是枚举p1的地址值,在查看地址的地方输入这个地址查看内存分布。
内存查看.png可以看到前面4个8字节存的分别是10、20、30、40。最后1个字节存的是0,因为要内存对齐所以也占了8个字节。
枚举p2紧跟在p1后面,前面4个8字节存的都是0,最后8字节第一个字节存的是1。
内存的分布也可以证实上面所说的结论。
网友评论