Swift学习笔记(四)--枚举,类与结构体

枚举(Enumerations)

枚举在Swift里面得到了很大的拓展, 使其变得更加简单, 易用且强大.

  1. 枚举语法(Enumeration Syntax)
    与ObjC一样, 枚举通过enum来声明, 例如:
enum CompassPoint {
    case North
    case South
    case East
    case West
}
// 如果想简单点, 写成一行也是可以的
enum Planet {
    case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}

获得枚举值也是很简单, 而且苹果还特意进行了一些简化:

var directionToHead = CompassPoint.West
directionToHead = .East // 根据类型就已经知道了East就是CompassPoint的East

如果你够好奇的话, 肯定已经猜到了这个简化还是类型推导的功劳, 下面的代码可以验证:

var directionToHead: CompassPoint = .East  
  1. 用Switch语句来匹配枚举值(Matching Enumeration Values with a Switch Statement)
    直接看代码应该会更清晰:
directionToHead = .South
switch directionToHead {
case .North:
    print("Lots of planets have a north")
case .South:
    print("Watch out for penguins")
case .East:
    print("Where the sun rises")
case .West:
    print("Where the skies are blue")
}
// 之前说过的, switch必须要涵盖全部范围, 所以除了枚举外, 一般都要有default, 
// 因为我们定义的枚举是可以穷举的, 所以全部写上就不会报错了, 
// 个人还是建议对枚举没有特殊要求不要加default, 这样编译器会强制我们处理所有的分支,
// 如果以后加了一个枚举值, 而且这个枚举用的比较多, 可以由编译器进行提醒
  1. 关联值(Associated Values)
    简单说来, 就是Swift里面的枚举并不单单是一个值可以, 它还可以关联(我感觉叫存储还容易理解些)一些别的值, 比如WWDC里面的例子:
enum TrainStatus {
    case OnTime
    case Delay(Int) // 晚点了几分钟
}
var status = TrainStatus.Delay(10) // 晚点了10分钟

那么假如要取这个值怎么办呢? 继续看:
```
switch status {
case .OnTime:
print("The train is ontime")
case .Delay(let minutes):
print("Delayed (minutes) minutes")

}
// 如果有多个参数, 常规写法是.Delay(let minutes, let station, let reason):
// 所以苹果又帮我们简化了, 可以直接 case let .Delay(minutes, station, reason):


4. 原始值(Raw Values)
和别的语言不同, Swift不会对默认对枚举进行一个"赋初值"的动作, 也就是说, CompassPoint.North和0并不是天然绑定的, 除非我们自己手动写上. 例如:

enum CompassPoint: Int { // 注意这里要写类型了的
case North = 1 // 后面的不写就递增, 当然了, 仅限数值类型的(Double, Float也行)
case South // 第一个都不写就是0
case East // 如果是String的话, 默认就是其字面值, 如.South就是"South"
case West // 其它类型都是全部指定原始值
}


如果有了原始值, 那么我们就能通过原始值来获取枚举了, 例如:

let direction = CompassPoint(rawValue: 1) // .North
// 注意:如果你的rawValue超过了枚举范围, 就会返回nil, 所以这个方式初始化出来的枚举值是Optional的, 使用前要拆包


5. 递归枚举(Recursive Enumerations)
听起来貌似很负责的样子, 为此苹果又来了一个关键字叫, indirect case(必须搭配case或者enum使用, 单独一个indirect是不成关键字的). 其实它的本质就是如果我在枚举里面还要使用这个枚举类型怎么办? 回顾一下C语言实现链表的时候, struct Node里面还有struct Node, 所有我们的next要用struct Node *的形式. 同样的, 这里的枚举也会遇到这个问题, 但是Swift已经没有指针了, 自然就要引入新的关键字了.

直接看官网的例子吧:

enum ArithmeticExpression {
case Number(Int)
// 如果去掉indirect就会报错, 提示你加入
indirect case Addition(ArithmeticExpression, ArithmeticExpression)
indirect case Multiplication(ArithmeticExpression, ArithmeticExpression)
}
// 当然, 多个indirect是可以简化的, 直接写在enum前面, 告知一下
indirect enum ArithmeticExpression {
case Number(Int)
case Addition(ArithmeticExpression, ArithmeticExpression)
case Multiplication(ArithmeticExpression, ArithmeticExpression)
}


使用起来的大概是这样的, 感受一下:

func evaluate(expression: ArithmeticExpression) -> Int {
switch expression {
case .Number(let value):
return value
case .Addition(let left, let right):
return evaluate(left) + evaluate(right)
case .Multiplication(let left, let right):
return evaluate(left) * evaluate(right)
}
}
// evaluate (5 + 4) * 2
let five = ArithmeticExpression.Number(5)
let four = ArithmeticExpression.Number(4)
let sum = ArithmeticExpression.Addition(five, four)
let product = ArithmeticExpression.Multiplication(sum, ArithmeticExpression.Number(2))
print(evaluate(product))
// prints "18"


这个东西, 一般用的可能不多, 但是要用的时候要是发现编不过还是很郁闷的, 顺便提一句, Swift里面貌似不能用struct来实现链表了, 试过给struct加indirect会报错, 哈哈, 不过可以用class...如果有方法可以用struct实现, 请一定要指点我一下...

6. 枚举可以有运算属性(Computed Properties)和函数
在Swift里面, 属性分为两种, 一种叫存储属性(Store Properties), 其实就是我们一般的属性, 一种叫运算属性, 二者的区别, 我总结起来就是看有没有实体, 如果有实体的话, 就是存储属性, 否则就是运算属性, 举个例子, 很多类,结构体或者枚举的description都是运算属性:

enum CompassPoint: Float {
var description:String {
return "value is (self)" // 运算属性分get和set, 目前这种写法是get, 以后再讲set
}
case North
case South
case East
case West
func desc() {
print("value is (self)")
}
}
var cp: CompassPoint = .South
cp.description // 打印出"value is South"
cp.desc() // 打印出"value is South\n"

想象力丰富的同学肯定想到了, 虽然不能有存储属性, 但是我们可以有关联值, 从某种程度上来代替存储属性来实现一些方案也是可以的.


好了枚举差不多到这里, 具体细节参考[官方文档](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Enumerations.html#//apple_ref/doc/uid/TP40014097-CH12-ID145)



###类与结构体(Classes and Structures)
这一章只对类和结构体进行一些简单的介绍, 着重会介绍苹果对使用类还是结构体的一些指导意见.

1. 类与结构体对比(Comparing Classes and Structures)
二者都能实现的:
a. 定义存储属性
b. 定义方法
c. 定义下标(subscripts)来访问它们的值
d. 定义初始化方法
e. 可以拓展默认实现
f. 可以实现协议

和大多数语言一样, 下面是只有类能实现的:
a. 继承
b. 类型转换允许你在运行时检查和解释实例类型
c. 析构方法(Deinitializers)允许你在释放类实例持有的一些资源
d. 引用计数

2. 基本语法
和其它语言没有多大区别, 直接看例子吧:

//定义类和结构体:
struct Resolution {
var width = 0
var height = 0
}
class VideoMode {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String?
}
// 获取二者实例:
let someResolution = Resolution()
let someVideoMode = VideoMode()
// 访问属性:
print("The width of someResolution is (someResolution.width)")
print("The width of someVideoMode is (someVideoMode.resolution.width)")
// 设置属性:
someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now (someVideoMode.resolution.width)")

// 结构体的成员初始化函数:
let vga = Resolution(width: 640, height: 480) // 编译器自动合成

3. 结构体和枚举值是值类型:
用例子可以证明之:

// 结构体:
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
cinema.width = 2048
print("cinema is now (cinema.width) pixels wide")
// prints "cinema is now 2048 pixels wide"
print("hd is still (hd.width) pixels wide")
// prints "hd is still 1920 pixels wide"

// 枚举:
enum CompassPoint {
case North, South, East, West
}
var currentDirection = CompassPoint.West
let rememberedDirection = currentDirection
currentDirection = .East
if rememberedDirection == .West {
print("The remembered direction is still .West")
}
// prints "The remembered direction is still .West"


4. 类是引用类型
这个例子也不需要举了, 肯定是这样的, 主要讲下接下来的2个细节吧:
a. 全等操作符(===), 也有叫恒等的: 引入这个东西还是因为没有指针啊, 所以需要一个操作符来比较2个实例是不是同一个东西, 所以就有了===. 这个和相等操作符的区别是, 相等操作符会比较其内容, 而不是地址.

b. 苹果特意解释了一些关于指针的事情: Swift中指向引用类型的常/变量和C语言的指针很类似, 但是却不是直接指向内存中的地址, 并且我们也不需要写*号来表示我们要创建一个引用类型, 引用类型和Swift里面的其它类型的定义是基本一致的. (感觉说了等于没说, 其实就是告诉我们, 没指针了)

5. 类与结构体的选择(Choosing Between Classes and Structures)
苹果给出了一系列的指导意见, 如果满足下面的一个或者多个条件, 就考虑用结构体吧:
a. 这个结构的主要目的是为了封装一些相对简单的数据
b. 这个结构值被赋值或者被传递的时候, 它被复制是合理的
c. 这个结构内部的存储属性类型也是值类型, 并且它们也期望被复制而不是被引用
d. 这个结构不需要从其它类型中集成属性

下面是一些例子:
a. 几何图形: 可能只会封装width和height, 它们都是Double类型的
b. 表示范围: 可能只会封装start和length, 它们都是Int类型的
c. 3D坐标系: 可能只会封装x,y,z 它们都是Double类型的

6. 字符串,数组和字典的赋值和复制(Assignment and Copy Behavior for Strings, Arrays, and Dictionaries)
如之前所说, 这三种类型都是值类型, 查看一下定义就知道它们是用结构体实现的. 需要注意的是, 虽说这三个值类型在我们的代码中总是会被复制的, 然而, Swift会很智能地只在真正需要复制的时候才会复制. Swift会管理所有的值复制来优化性能, 所以我们不需要不敢赋值, 唯恐影响性能.

对苹果的声明, 我做了一个测试, 可惜并没有得到预想的结果, 不知道是不是测试的姿势不对, 也可能是没开启优化选项? 测试代码和结果如下:

let str = "123"
let str2 = str
print("(str)(str2)")
let str3 = str
let str4 = str
print("(str3)
(str4)")
let str5 = str
let str6 = str
print("(str5)_(str6)")
print("======")

上面的代码, 如果是有优化的话, 上面6个String应该只用一份就够了, 但是我得到的结果是这样的:

(lldb) po unsafeAddressOf("123")
▿ 0x00007fdd58519f10
- pointerValue : 140588646244112

(lldb) po unsafeAddressOf(str)
▿ 0x00007fdd5860fba0
- pointerValue : 140588647250848 // a

(lldb) po unsafeAddressOf(str2)
▿ 0x00007fdd5860fba0
- pointerValue : 140588647250848 // a

(lldb) po unsafeAddressOf(str3)
▿ 0x00007fdd5b9669b0
- pointerValue : 140588701084080 // b

(lldb) po unsafeAddressOf(str4)
▿ 0x00007fdd5b9669b0
- pointerValue : 140588701084080 // b

(lldb) po unsafeAddressOf(str5)
▿ 0x00007fdd5b96a310

  • pointerValue : 140588701098768

(lldb) po unsafeAddressOf(str6)
▿ 0x00007fdd58401b70

  • pointerValue : 140588645096304
从地址可以看出来, 6份按道理同样的字符串, 只有2块区域被复用了, 而且只有一次, 说明内存里面是有4份一样是String(如果不算"123"这个全局变量), 如果有正确的测试姿势, 请回复一下哈. (误!)
// 2016-3-17更正:
上面用unsafeAddressOf打出不同的地址应该是和String实现的方式有关, 实际上真正字符串数据存储的地方在String._core._baseAddress(在Debug模式下, 展开String可以看到), 因此上面这个例子不能说明苹果没有优化, 实际上, 对于值类型, 苹果的原则是"copy on write", 也就是说, 只有在真正修改的时候才会执行copy操作, 可以通过下面的代码进行验证:

let str = "123"
var str2 = str // 这个时候可以看到str2._core._baseAddress与str1的相同, 直到...
str2.appendContentsOf("4") // str2._core._baseAddress被修改了


好了, 结构体和类的基本概念就到这, 明天出差, 之后再更新咯~~~
细节参考[官方文档](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ClassesAndStructures.html#//apple_ref/doc/uid/TP40014097-CH13-ID82)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容