swift 的结构体中是否可以修改属性?

396 阅读2分钟

在 Swift 中,结构体(struct)的属性是否可以修改,取决于结构体实例是通过变量(var)还是常量(let)声明的。以下是详细分析:


1. 结构体是值类型

Swift 中的结构体是值类型,这意味着:

  • 当结构体实例被赋值给变量或常量时,会复制整个结构体的值
  • 如果通过变量(var)声明结构体实例,可以直接修改其属性。
  • 如果通过常量(let)声明结构体实例,则所有属性都变成只读的,无法修改。

示例

struct Person {
    var name: String
    var age: Int
}

// 通过变量声明结构体实例(可修改)
var person1 = Person(name: "90后晨仔", age: 30)
person1.name = "Bob"      // ✅ 允许修改
person1.age = 35          // ✅ 允许修改

struct Person {
    let name: String
    let age: Int
}

// 通过常量声明结构体实例(不可修改)
let person2 = Person(name: "90后晨仔", age: 25)
person2.name = "Dave"     // ❌ 编译错误:Cannot assign to property: 'name' is a 'let' constant
person2.age = 30          // ❌ 编译错误:Cannot assign to property: 'age' is a 'let' constant

2. 使用 mutating 关键字在方法中修改属性

如果需要在结构体的方法内部修改属性,必须使用 mutating 关键字标记该方法。这是因为:

  • 在方法内部,默认情况下 self 是不可变的(类似常量)。
  • mutating 告诉编译器该方法会修改结构体的属性。

示例

struct Counter {
    var count: Int = 0
    
    // 通过 mutating 关键字允许修改属性
    mutating func increment() {
        count += 1
    }
    
    // 不使用 mutating 的方法无法修改属性
    func printCount() {
        print("Count: $count)")
    }
}

var counter = Counter()
counter.increment()       // ✅ 允许调用 mutating 方法
counter.printCount()      // ✅ 输出: Count: 1

let constantCounter = Counter()
constantCounter.increment() // ❌ 编译错误:CCannot use mutating member on immutable value: 'constantCounter' is a 'let' constant

3. 常量结构体实例的限制

  • 即使结构体的属性是 var,如果结构体实例是通过 let 声明的,所有属性都变为只读
  • 这与类(class)不同:类是引用类型,即使通过 let 声明实例,只要属性是 var,仍然可以修改。

对比示例

// 结构体(值类型)
struct Point {
    var x: Int
    var y: Int
}

let point = Point(x: 10, y: 20)
point.x = 30  // ❌ 编译错误:Cannot assign to property: 'self' is immutable

// 类(引用类型)
class Location {
    var x: Int = 10
    var y: Int = 20
}

let location = Location()
location.x = 30  // ✅ 允许修改(即使通过 let 声明)

4. 为什么结构体需要 mutating 关键字?

  • 值类型的不可变性:结构体的副本独立存在,修改副本不会影响原始数据。如果允许在方法内部直接修改属性,会导致行为不一致。
  • 显式控制mutating 关键字明确标识方法会修改结构体的状态,提高代码可读性。

对比:类 vs 结构体

特性类(引用类型)结构体(值类型)
修改常量实例的属性允许(只要属性是 var不允许
方法中修改属性不需要 mutating需要 mutating
赋值行为浅拷贝(共享同一内存地址)深拷贝(复制值)

5. 总结

  • 可以修改结构体属性的条件
    • 结构体实例是通过 var 声明的。
    • 在方法内部修改属性时,使用 mutating 关键字。
  • 不能修改的场景
    • 结构体实例是通过 let 声明的。
    • 在非 mutating 方法中尝试修改属性。

最佳实践

  • 对于简单数据模型(如坐标、尺寸、配置项),优先使用结构体,利用其值类型特性保证数据安全性。
  • 如果需要频繁修改状态或共享数据,使用类(class)。

完整示例代码

struct User {
    var name: String
    var age: Int
    
    // 修改属性的方法需要 mutating
    mutating func updateName(newName: String) {
        name = newName
    }
    
    // 不修改属性的方法不需要 mutating
    func printInfo() {
        print("Name: $name), Age: $age)")
    }
}

// 通过 var 声明结构体实例(可修改)
var user1 = User(name: "Alice", age: 30)
user1.updateName(newName: "Bob")  // ✅ 允许调用 mutating 方法
user1.printInfo()                 // ✅ 输出: Name: Bob, Age: 30

// 通过 let 声明结构体实例(不可修改)
let user2 = User(name: "Charlie", age: 25)
user2.updateName(newName: "Dave") // ❌ 编译错误:Cannot use mutating member on immutable value