1. 引言
在 Swift 中,结构体(Struct)是构建代码模块的核心类型之一。与类(Class)不同,结构体是值类型,这一特性让它在 Swift 开发中扮演着极其重要的角色。无论是定义一个坐标点、一个网络请求的响应模型,还是业务中的领域对象,结构体都无处不在。本文将从基础语法出发,结合丰富的代码实例,带你全面掌握 Swift 结构体的定义、使用、进阶特性以及它与类的区别。
2. 结构体的基本定义与实例化
结构体通过struct关键字定义。一个最简单的结构体可以只包含存储属性,Swift 会自动为结构体生成一个「成员逐一构造器」(Memberwise Initializer),方便我们直接创建实例。
// 定义一个表示坐标点的结构体 struct Point { var x: Double var y: Double } // 使用自动生成的成员逐一构造器创建实例 let origin = Point(x: 0.0, y: 0.0) var target = Point(x: 10.5, y: 20.0) print("原点坐标:\(origin.x), \(origin.y)") print("目标坐标:\(target.x), \(target.y)") // 修改结构体实例的属性(需要 var 声明) target.x = 15.0 print("更新后的目标坐标:\(target.x), \(target.y)")在上面的例子中,我们没有编写任何初始化方法,但 Swift 自动提供了Point(x:y:)构造器。需要注意的是,如果结构体的属性使用let声明,则该属性在实例创建后不可修改。
3. 存储属性与计算属性
结构体可以同时拥有存储属性(Stored Properties)和计算属性(Computed Properties)。计算属性不直接存储值,而是通过计算得到,常用于派生数据。
// 定义一个表示矩形的结构体 struct Rectangle { var width: Double var height: Double // 计算属性:面积 var area: Double { return width * height } // 计算属性:周长 var perimeter: Double { return 2 * (width + height) } // 只读计算属性可以省略 get 关键字 var isSquare: Bool { return width == height } } let rect = Rectangle(width: 5.0, height: 3.0) print("面积:\(rect.area)") print("周长:\(rect.perimeter)") print("是否为正方形:\(rect.isSquare)") let square = Rectangle(width: 4.0, height: 4.0) print("正方形面积:\(square.area),是否为正方形:\(square.isSquare)")计算属性每次访问时都会重新计算,因此它不能用于频繁读取且计算开销较大的场景。如果计算开销大,可以考虑使用存储属性加自定义方法,或者使用lazy关键字。
4. 方法(Methods)
结构体可以定义实例方法和类型方法。实例方法操作实例自身的属性,而类型方法(使用static关键字)则与具体实例无关。
// 定义一个表示温度的结构体 struct Temperature { var celsius: Double // 实例方法:转换为华氏温度 func toFahrenheit() -> Double { return celsius * 9 / 5 + 32 } // 实例方法:转换为开尔文温度 func toKelvin() -> Double { return celsius + 273.15 } // 类型方法:从华氏温度创建实例 static func fromFahrenheit(_ fahrenheit: Double) -> Temperature { return Temperature(celsius: (fahrenheit - 32) * 5 / 9) } // 类型方法:从开尔文温度创建实例 static func fromKelvin(_ kelvin: Double) -> Temperature { return Temperature(celsius: kelvin - 273.15) } } let bodyTemp = Temperature(celsius: 36.5) print("体温华氏度:\(bodyTemp.toFahrenheit())") print("体温开尔文:\(bodyTemp.toKelvin())") let boilingPoint = Temperature.fromFahrenheit(212) print("水的沸点(摄氏):\(boilingPoint.celsius)")类型方法非常适合作为「工厂方法」,用来提供多种创建实例的入口,让调用方的语义更加清晰。
5. 值类型与引用类型的区别
结构体是值类型(Value Type),而类是引用类型(Reference Type)。这是 Swift 中最重要的概念之一。值类型在赋值或传递时会被复制,而引用类型则共享同一份实例。
// 结构体:值类型 struct Position { var x: Int var y: Int } var posA = Position(x: 1, y: 2) var posB = posA // 这里会复制一份独立的副本 posB.x = 100 print("posA.x = \(posA.x)") // 输出 1,posA 不受影响 print("posB.x = \(posB.x)") // 输出 100 // 类:引用类型 class Car { var speed: Int = 0 } let carA = Car() let carB = carA // 这里共享同一个实例 carB.speed = 120 print("carA.speed = \(carA.speed)") // 输出 120,carA 和 carB 指向同一对象 print("carB.speed = \(carB.speed)") // 输出 120理解值类型与引用类型的区别,有助于避免在开发中遇到「意外修改」的问题。当你希望数据被独立复制、互不影响时,优先使用结构体;当你需要共享状态、使用继承时,则选择类。
6. 结构体中的 mutating 方法
默认情况下,结构体的实例方法不能修改自身的属性。如果需要在方法中修改属性,必须将方法标记为mutating。这是因为结构体是值类型,修改属性实际上是在修改实例本身。
// 定义一个计数器结构体 struct Counter { var count = 0 // mutating 方法:增加计数 mutating func increment() { count += 1 } // mutating 方法:重置计数 mutating func reset() { count = 0 } // mutating 方法:按指定步长增加 mutating func increment(by amount: Int) { count += amount } } var counter = Counter() counter.increment() counter.increment(by: 5) print("当前计数:\(counter.count)") // 输出 6 counter.reset() print("重置后计数:\(counter.count)") // 输出 0注意:只有使用var声明的结构体实例才能调用mutating方法。如果使用let声明实例,则无法调用任何mutating方法。
7. 自定义构造器(Initializers)
除了自动生成的成员逐一构造器,我们还可以为结构体自定义构造器,以便在初始化时执行额外的逻辑或提供默认值。
// 定义一个表示用户的结构体 struct User { var name: String var age: Int var email: String // 自定义构造器:带默认邮箱 init(name: String, age: Int) { self.name = name self.age = age self.email = "\(name.lowercased())@example.com" } // 自定义构造器:完整参数 init(name: String, age: Int, email: String) { self.name = name self.age = age self.email = email } // 自定义构造器:带年龄默认值 init(name: String) { self.name = name self.age = 0 self.email = "\(name.lowercased())@example.com" } } let user1 = User(name: "Alice", age: 25) print("用户1:\(user1.name), \(user1.age), \(user1.email)") let user2 = User(name: "Bob", age: 30, email: "bob@company.com") print("用户2:\(user2.name), \(user2.age), \(user2.email)") let user3 = User(name: "Charlie") print("用户3:\(user3.name), \(user3.age), \(user3.email)")一旦自定义了构造器,Swift 就不会再自动生成成员逐一构造器。如果仍然需要它,可以在扩展(Extension)中显式保留。
8. 扩展(Extension)与协议(Protocol)
结构体可以通过扩展来增加功能,也可以遵循协议(Protocol)来满足特定的接口要求。这使得结构体在保持轻量的同时,具备强大的扩展能力。
// 定义一个基础结构体 struct Book { var title: String var author: String var pages: Int } // 通过扩展添加计算属性和方法 extension Book { var description: String { return "《\(title)》作者:\(author),共 \(pages) 页" } func isLongBook() -> Bool { return pages > 300 } } // 定义一个协议 protocol Summarizable { func summary() -> String } // 让结构体遵循协议 extension Book: Summarizable { func summary() -> String { return "\(title) - \(author)" } } let book = Book(title: "Swift 编程实战", author: "张三", pages: 420) print(book.description) print("是否长篇小说:\(book.isLongBook())") print("摘要:\(book.summary())")通过扩展遵循协议,可以让结构体的职责更加清晰,也便于代码的模块化组织。
9. 结构体与类的选择建议
在 Swift 开发中,Apple 官方推荐优先使用结构体。以下是一些选择建议:
- 优先使用结构体:当数据量较小、不需要继承、且希望值语义时,优先选择结构体。
- 使用类:当需要继承、需要引用语义(共享状态)、或者需要与 Objective-C 互操作时,选择类。
- 结合使用:在实际项目中,常常用结构体定义数据模型,用类管理生命周期和共享状态。
// 实际项目中的常见组合用法 struct Product { let id: Int let name: String var price: Double } class ShoppingCart { var items: [Product] = [] func add(_ product: Product) { items.append(product) } var totalPrice: Double { return items.reduce(0) { $0 + $1.price } } } let phone = Product(id: 1, name: "智能手机", price: 5999.0) let case_ = Product(id: 2, name: "手机壳", price: 99.0) let cart = ShoppingCart() cart.add(phone) cart.add(case_) print("购物车总价:\(cart.totalPrice) 元")在这个例子中,Product使用结构体表示不可变的数据模型,ShoppingCart使用类来管理可变的购物车状态,两者各司其职。
10. 总结
Swift 结构体是值类型的代表,具有定义简洁、自动构造器、计算属性、方法、扩展和协议遵循等丰富特性。理解结构体的值语义以及与类的区别,是写出健壮 Swift 代码的关键。在实际开发中,建议优先使用结构体来定义数据模型,仅在确实需要引用语义或继承时使用类。希望本文的代码实例能帮助你更好地掌握 Swift 结构体的使用。