第7章:枚举与结构体 - 自定义类型
7.1 枚举 (Enum) - 限定取值范围
想象一个交通灯,只有三种状态:红、黄、绿。用枚举可以清晰表达:
enum TrafficLight {
case red
case yellow
case green
}
// 使用
var light = TrafficLight.red
light = .green // 可以简写,因为类型已知
为什么要用枚举?
- 编译器检查:不会出现无效值
- 代码自文档:看到
TrafficLight就知道有哪些可能 - Switch 必须处理所有 case,不会遗漏
带原始值的枚举
enum CompassDirection: String {
case north = "N"
case south = "S"
case east = "E"
case west = "W"
}
let direction = CompassDirection.north
print(direction.rawValue) // "N"
// 从原始值创建(可能失败,返回可选类型)
let possibleDirection = CompassDirection(rawValue: "N") // Optional(CompassDirection.north)
let unknownDirection = CompassDirection(rawValue: "X") // nil
原始值类型: String、Character、Int、Float、Double
enum Planet: Int {
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
// venus = 2, earth = 3... 自动递增
关联值 - 枚举的超能力
enum Barcode {
case upc(Int, Int, Int, Int) // 关联4个整数
case qrCode(String) // 关联1个字符串
}
var productBarcode = Barcode.upc(8, 85909, 51226, 3)
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")
// 用 switch 提取关联值
switch productBarcode {
case .upc(let numberSystem, let manufacturer, let product, let check):
print("UPC: (numberSystem), (manufacturer), (product), (check)")
case .qrCode(let productCode):
print("QR code: (productCode)")
}
// 如果所有 case 都用 let,可以提到 switch 前面
switch productBarcode {
case let .upc(numberSystem, manufacturer, product, check):
print("UPC (numberSystem)")
case let .qrCode(productCode):
print("QR (productCode)")
}
什么时候用关联值?
- 不同 case 需要携带不同信息
- 比如网络请求结果:成功时带数据,失败时带错误信息
enum NetworkResult<T> {
case success(T)
case failure(Error)
}
递归枚举
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)
}
}
// (5 + 4) * 2
let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let two = ArithmeticExpression.number(2)
let product = ArithmeticExpression.multiplication(sum, two)
print(evaluate(product)) // 18
indirect 关键字告诉编译器在枚举 case 中存储引用而不是值。
7.2 结构体 (Struct) - 轻量级数据容器
结构体是值类型,适合存储简单的数据:
struct Resolution {
var width = 0
var height = 0
}
var hd = Resolution(width: 1920, height: 1080)
var cinema = hd // 复制一份
cinema.width = 2048
print(hd.width) // 1920(没变!)
print(cinema.width) // 2048
值类型的特点:
- 赋值时会复制
- 修改互不影响
- 线程安全(没有共享状态)
结构体的初始化
struct Person {
var name: String
var age: Int
// 自动生成:逐一成员初始化器
// init(name: String, age: Int)
}
let person = Person(name: "Alice", age: 25)
Swift 自动为结构体生成初始化器,不需要手动写!
结构体的方法
struct Point {
var x = 0.0, y = 0.0
// 实例方法
func distanceTo(_ other: Point) -> Double {
let dx = x - other.x
let dy = y - other.y
return sqrt(dx * dx + dy * dy)
}
// 修改属性的方法需要 mutating 关键字
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
x += deltaX
y += deltaY
}
}
var point = Point(x: 0, y: 0)
point.moveBy(x: 10, y: 10)
print(point) // Point(x: 10.0, y: 10.0)
为什么需要 mutating?
- 结构体是值类型,默认不可变
- mutating 表示这个方法会修改结构体的值
计算属性
struct Rectangle {
var width: Double
var height: Double
// 存储属性
var area: Double {
return width * height
}
// 带 getter 和 setter 的计算属性
var diagonal: Double {
get {
return sqrt(width * width + height * height)
}
set {
// newValue 是传入的新值
let ratio = width / height
height = newValue / sqrt(ratio * ratio + 1)
width = height * ratio
}
}
}
var rect = Rectangle(width: 3, height: 4)
print(rect.area) // 12
print(rect.diagonal) // 5
rect.diagonal = 10 // 修改对角线,自动调整宽高
print(rect.width) // 6
print(rect.height) // 8
属性观察器
struct StepCounter {
var totalSteps: Int = 0 {
willSet {
print("准备把 totalSteps 从 (totalSteps) 改为 (newValue)")
}
didSet {
if totalSteps > oldValue {
print("增加了 (totalSteps - oldValue) 步")
}
}
}
}
var counter = StepCounter()
counter.totalSteps = 100 // 增加了 100 步
counter.totalSteps = 150 // 增加了 50 步
7.3 结构体 vs 类 - 什么时候用哪个?
| 特性 | 结构体 (Struct) | 类 (Class) |
|---|---|---|
| 类型 | 值类型 | 引用类型 |
| 继承 | 不支持 | 支持 |
| 多态 | 不支持 | 支持 |
| 内存 | 栈(快) | 堆(相对慢) |
| 线程安全 | 天然安全 | 需要额外处理 |
| 自动初始化器 | 有 | 没有(必须自己写或继承) |
优先使用结构体,除非你需要:
- 继承
- 需要多个引用指向同一个实例
- 需要生命周期管理(deinit)
常见的结构体:
- CGPoint、CGSize、CGRect
- String、Array、Dictionary、Set
- Date
第8章:类与面向对象编程
8.1 类的定义
类是引用类型,多个变量可以引用同一个实例:
class Person {
var name: String
var age: Int = 0
// 必须写初始化器
init(name: String) {
self.name = name
}
func greet() {
print("Hello, I'm (name)")
}
}
let person1 = Person(name: "Alice")
let person2 = person1 // 引用同一个对象
person2.name = "Bob"
print(person1.name) // Bob(变了!)
8.2 初始化器详解
指定初始化器 (Designated Initializer)
class Vehicle {
var numberOfWheels: Int
var color: String
// 指定初始化器:负责初始化所有属性
init(numberOfWheels: Int, color: String) {
self.numberOfWheels = numberOfWheels
self.color = color
}
}
规则:
- 必须确保所有属性都被初始化
- 必须先初始化自己的属性,再调用父类的初始化器
- 一个类至少有一个指定初始化器
便利初始化器 (Convenience Initializer)
class Vehicle {
var numberOfWheels: Int
var color: String
init(numberOfWheels: Int, color: String) {
self.numberOfWheels = numberOfWheels
self.color = color
}
// 便利初始化器:必须调用同一个类的其他初始化器
convenience init(color: String) {
self.init(numberOfWheels: 4, color: color)
}
convenience init() {
self.init(color: "Black")
}
}
let car = Vehicle() // 4个轮子,黑色
便利初始化器的用途:
- 提供默认值
- 简化调用
- 从其他数据源创建(比如从 JSON)
8.3 继承
class Bicycle: Vehicle {
var hasBasket: Bool
init(hasBasket: Bool, color: String) {
self.hasBasket = hasBasket
super.init(numberOfWheels: 2, color: color) // 调用父类初始化器
}
}
let bike = Bicycle(hasBasket: true, color: "Red")
print(bike.numberOfWheels) // 2
重写 (Override)
class Car: Vehicle {
var licensePlate: String
init(licensePlate: String, color: String) {
self.licensePlate = licensePlate
super.init(numberOfWheels: 4, color: color)
}
// 重写方法
override func description() -> String {
return "(super.description()) - Plate: (licensePlate)"
}
// 重写属性
override var numberOfWheels: Int {
didSet {
print("Wheel count changed to (numberOfWheels)")
}
}
}
final 关键字:阻止重写
class Vehicle {
final var numberOfWheels: Int // 子类不能重写
final func description() -> String { // 子类不能重写
return "Vehicle with (numberOfWheels) wheels"
}
}
8.4 多态
class Animal {
func makeSound() {
print("Some sound")
}
}
class Dog: Animal {
override func makeSound() {
print("Woof!")
}
}
class Cat: Animal {
override func makeSound() {
print("Meow!")
}
}
// 多态:同一个方法调用,不同实现
let animals: [Animal] = [Dog(), Cat(), Animal()]
for animal in animals {
animal.makeSound()
}
// Woof!
// Meow!
// Some sound
8.5 类型检查和转换
let animals: [Animal] = [Dog(), Cat(), Dog()]
// is:检查类型
for animal in animals {
if animal is Dog {
print("这是一只狗")
}
}
// as?:向下转型(可能失败,返回可选类型)
for animal in animals {
if let dog = animal as? Dog {
dog.fetchBall() // Dog 特有的方法
}
}
// as!:强制向下转型(确定成功时用)
let definitelyADog = animals[0] as! Dog
// as:向上转型(总是安全)
let animal = Dog() as Animal
8.6 析构器 (Deinitializer)
class FileHandler {
let filename: String
var fileHandle: FileHandle?
init(filename: String) {
self.filename = filename
self.fileHandle = FileHandle(forReadingAtPath: filename)
print("打开文件: (filename)")
}
deinit {
fileHandle?.closeFile()
print("关闭文件: (filename)")
}
}
func processFile() {
let handler = FileHandler(filename: "data.txt")
// 处理文件...
} // handler 超出作用域,自动调用 deinit
processFile()
// 打开文件: data.txt
// 关闭文件: data.txt