8. 类和结构体 (Class and Structure)
在 Swift 中,类(Class)和结构体(Structure)是构建代码的基本组成部分。它们用于封装数据和功能。虽然类和结构体有许多相似之处,但也有一些重要的区别。
定义类和结构体
类(Class) :
- 类是引用类型。
- 可以继承另一个类的特性。
- 可以允许继承者重写定义的特性。
- 引用计数允许对类实例的多个引用。
结构体(Structure) :
- 结构体是值类型。
- 结构体不支持继承。
- 结构体在传递时总是被复制。
定义一个类:
class Dog {
var name: String
var breed: String
init(name: String, breed: String) {
self.name = name
self.breed = breed
}
}
定义一个结构体:
struct Point {
var x: Int
var y: Int
}
创建实例
创建类和结构体的实例非常相似:
let myDog = Dog(name: "Fido", breed: "Labrador")
let somePoint = Point(x: 10, y: 20)
9. 属性(Properties)
Swift 中的属性将值与特定的类、结构体或枚举关联。属性分为两种主要类型:存储属性和计算属性。
存储属性(Stored Properties)
存储属性将常量或变量存储为实例的一部分。类和结构体通常使用存储属性。
class Car {
var make: String
var model: String
let year: Int // 常量存储属性
init(make: String, model: String, year: Int) {
self.make = make
self.model = model
self.year = year
}
}
let car = Car(make: "Tesla", model: "Model X", year: 2020)
计算属性(Computed Properties)
计算属性不直接存储值,而是提供一个 getter 和一个可选的 setter 来间接获取和设置其他属性或变量的值。
struct Point {
var x = 0.0, y = 0.0
}
struct Size {
var width = 0.0, height = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
var center: Point {
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
set(newCenter) {
origin.x = newCenter.x - (size.width / 2)
origin.y = newCenter.y - (size.height / 2)
}
}
}
var square = Rect(origin: Point(x: 0.0, y: 0.0), size: Size(width: 10.0, height: 10.0))
let initialSquareCenter = square.center
square.center = Point(x: 15.0, y: 15.0)
属性观察器(Property Observers)
属性观察器监控和响应属性值的变化,每次属性的值被设置时都会调用属性观察器,即使新值与当前值相同。
class StepCounter {
var totalSteps: Int = 0 {
willSet {
print("将 totalSteps 设置为 \(newValue)")
}
didSet {
if totalSteps > oldValue {
print("新增了 \(totalSteps - oldValue) 步")
}
}
}
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
stepCounter.totalSteps = 360
全局和局部变量(Global and Local Variables)
全局变量是在函数、方法、闭包或任何类型之外定义的变量。局部变量是在函数、方法或闭包内部定义的。
类型属性(Type Properties)
类型属性是与类型本身相关联的属性,而不是与该类型的实例相关联。使用 static 关键字定义类型属性。对于类类型的计算类型属性,可以使用 class 关键字允许子类重写父类的实现。
代码示例:
struct SomeStructure {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 1
}
}
enum SomeEnumeration {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 6
}
}
class SomeClass {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 27
}
class var overrideableComputedTypeProperty: Int {
return 107
}
}
print(SomeStructure.storedTypeProperty)
SomeStructure.storedTypeProperty = "Another value."
print(SomeStructure.storedTypeProperty)
10. 方法(Methods)
方法是与某些特定类型相关联的函数。类、结构体和枚举都可以定义方法。
实例方法
实例方法是属于类、结构体或枚举类型实例的函数。
class Counter {
var count = 0
func increment() {
count += 1
}
func increment(by amount: Int) {
count += amount
}
func reset() {
count = 0
}
}
let counter = Counter()
counter.increment()
counter.increment(by: 5)
counter.reset()
类型方法
类型本身调用的方法称为类型方法。在方法的 func 关键字之前加上关键字 static 来指明类型方法。对于类类型的方法,可以使用关键字 class 来允许子类重写父类的实现方法。
class SomeClass {
class func someTypeMethod() {
// 这里是类型方法的实现部分
}
}
SomeClass.someTypeMethod()
self 属性
每个实例的方法都有一个隐含的属性叫做 self,self 完全等同于该实例本身。
struct Point {
var x = 0.0, y = 0.0
func isToTheRightOf(x: Double) -> Bool {
return self.x > x
}
}
let somePoint = Point(x: 4.0, y: 5.0)
if somePoint.isToTheRightOf(x: 1.0) {
print("这个点在 x 轴的右侧")
}
11. 下标 (Subscripts)
下标允许你通过索引值来访问一个集合、列表或序列中的元素,而无需使用额外的获取和设置方法。
基本下标
下标定义使用 subscript 关键字并指定一个或多个输入参数和返回类型,类似于实例方法。
struct TimesTable {
let multiplier: Int
subscript(index: Int) -> Int {
return multiplier * index
}
}
let threeTimesTable = TimesTable(multiplier: 3)
print("六的三倍是 \(threeTimesTable[6])") // 输出 "六的三倍是 18"
下标选项
下标可以接受任意数量的输入参数,并且这些输入参数可以是任意类型。下标的返回值也可以是任意类型。
struct Matrix {
let rows: Int, columns: Int
var grid: [Double]
init(rows: Int, columns: Int) {
self.rows = rows
self.columns = columns
grid = Array(repeating: 0.0, count: rows * columns)
}
subscript(row: Int, column: Int) -> Double {
get {
return grid[(row * columns) + column]
}
set {
grid[(row * columns) + column] = newValue
}
}
}
var matrix = Matrix(rows: 2, columns: 2)
matrix[0, 1] = 1.5
matrix[1, 0] = 3.2
12. 继承 (Inheritance)
继承是一个类获取另一个类的属性和方法的过程。Swift 中的类可以调用和访问超类的方法、属性,并且可以重写它们。
基础继承
一个类可以继承另一个类的特性,并且可以增加自己的特性。
class Vehicle {
var currentSpeed = 0.0
var description: String {
return "行驶速度为 \(currentSpeed) 英里每小时"
}
func makeNoise() {
// 默认实现为空
}
}
class Bicycle: Vehicle {
var hasBasket = false
}
let bicycle = Bicycle()
bicycle.hasBasket = true
bicycle.currentSpeed = 15.0
print("自行车: \(bicycle.description)")
重写
子类可以提供它们自己的实现,替换超类的方法、属性或下标。
class Train: Vehicle {
override func makeNoise() {
print("嘟嘟嘟")
}
}
let train = Train()
train.makeNoise() // 输出 "嘟嘟嘟"
防止重写
可以通过标记为 final 来防止方法、属性或下标被重写。
class Car: Vehicle {
final var gear = 1
override var description: String {
return super.description + " 在 \(gear) 挡"
}
}
// 尝试继承 Car 并重写 gear 属性将会失败
13. 构造和析构 (Initializer and Deinitializer)
Swift 中的构造过程是使用类、结构体或枚举类型的实例之前的准备过程。析构过程则是在实例释放发生之前进行的清理工作。
构造器(Initializer)
构造器是用于创建特定类型实例的特殊方法,用于初始化类、结构体或枚举的新实例。
class Rectangle {
var width: Double
var height: Double
init(width: Double, height: Double) {
self.width = width
self.height = height
}
}
let rectangle = Rectangle(width: 5.0, height: 10.0)
析构器(Deinitializer)
析构器只适用于类类型。当类的实例被释放之前,析构器被立即调用。
class Bank {
static var coinsInBank = 10000
static func distribute(coins numberOfCoinsRequested: Int) -> Int {
let numberOfCoinsToVend = min(numberOfCoinsRequested, coinsInBank)
coinsInBank -= numberOfCoinsToVend
return numberOfCoinsToVend
}
static func receive(coins: Int) {
coinsInBank += coins
}
}
class Player {
var coinsInPurse: Int
init(coins: Int) {
coinsInPurse = Bank.distribute(coins: coins)
}
deinit {
Bank.receive(coins: coinsInPurse)
}
}
var playerOne: Player? = Player(coins: 100)
playerOne = nil // 在此处调用 deinit
14. 扩展(Extensions)
扩展为现有的类、结构体、枚举或协议类型添加新功能。
扩展的功能
- 添加计算实例属性和计算类型属性。
- 定义实例方法和类型方法。
- 提供新的构造器。
- 定义下标。
- 定义和使用新的嵌套类型。
- 使现有的类型符合某协议。
代码示例:
extension Double {
var km: Double { return self * 1_000.0 }
var m: Double { return self }
var cm: Double { return self / 100.0 }
var mm: Double { return self / 1_000.0 }
}
let oneInch = 25.4.mm
print("One inch is \(oneInch) meters") // 输出 "One inch is 0.0254 meters"
15. 协议(Protocols)
协议定义了适用于类、结构体或枚举的方法、属性以及其他需求的蓝图。
协议的语法
- 协议可以要求遵守协议的类型提供特定的实例属性、实例方法、类型方法、操作符以及下标。
- 协议可以要求特定的构造器。
代码示例:
protocol FullyNamed {
var fullName: String { get }
}
struct Person: FullyNamed {
var fullName: String
}
let john = Person(fullName: "John Appleseed")
协议作为类型
- 协议本身不实现功能,但可以被作为一个功能完整的类型在程序中使用。
- 协议可以像其他普通类型一样使用,包括在函数、方法或构造器中作为参数类型或返回类型。
16. 可选类型(Optional Types)
可选类型在 Swift 中用于处理值可能为空(nil)的情况。
可选链(Optional Chaining)
可选链允许以一种安全的方式查询和调用属性、方法和下标。
-
使用可选链访问属性: 如果属性为
nil,可选链调用失败并返回nil。class Person { var residence: Residence? } class Residence { var numberOfRooms = 1 } let john = Person() if let roomCount = john.residence?.numberOfRooms { print("John's residence has \(roomCount) room(s).") } -
使用可选链调用方法: 方法返回值为
Void时,通过可选链调用它会返回Void?。class Dog { func bark() { print("Woof!") } } var myDog: Dog? myDog?.bark() // 什么都不会发生,因为 myDog 是 nil
可选绑定(Optional Binding)
通过可选绑定,可以检查可选类型是否有值,如果有,则将值赋给一个常量或变量。
if let actualRoomCount = john.residence?.numberOfRooms {
print("约翰的住所有 \(actualRoomCount) 个房间。")
} else {
print("无法获取房间数。")
}
隐式解包可选类型(Implicitly Unwrapped Optionals)
当可选类型的值一旦被设定后可以确定总是有值时,可以使用隐式解包可选类型。
let possibleString: String! = "一个隐式解包的可选字符串。"
let implicitString: String = possibleString // 不需要显式解包