首页 Swift中的类和结构体
文章
取消

Swift中的类和结构体

在 Swift 中,类和结构体是创建代码所需的两种最基本的构建块。虽然它们有很多相似之处,但它们也有一些关键的区别。本文将介绍 Swift 中的类和结构体,以及它们之间的差异。

类和结构体的定义

从语法上来看,类和结构体有很多相似之处。它们都使用 classstruct 关键字定义,分别后跟名称和大括号 {} 来表示它们的结构。下面是一个简单的类和结构体示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 类定义
class Person {
    var name: String
    var age: Int
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

// 结构体定义
struct Point {
    var x: Double
    var y: Double
}

这里我们定义了一个名为 Person 的类,包含了两个属性 nameage,并且有一个初始化方法。我们还定义了一个名为 Point 的结构体,包含了两个属性 xy

值得注意的是,在 Swift 中,结构体可以像类一样拥有属性、方法和初始化方法。这是与许多其他编程语言不同的地方。

类和结构体的实例化

类和结构体的实例化非常相似。它们都需要使用名称后跟小括号 () 的方式来创建一个新的实例。例如,我们可以这样实例化 PersonPoint

1
2
3
4
5
// 实例化 Person 类
let person = Person(name: "Tom", age: 25)

// 实例化 Point 结构体
let point = Point(x: 10.0, y: 20.0)

在上面的示例中,我们使用类的初始化方法来创建一个新的 Person 实例,使用结构体的初始化方法来创建一个新的 Point 实例。

类和结构体的属性

类和结构体都可以定义它们自己的属性。在类或结构体中定义属性时,需要在属性的前面加上 varlet 关键字来指定该属性是否是可变的。例如,我们可以修改 PersonPoint,使其包含一些额外的属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 修改 Person 类
class Person {
    var name: String
    var age: Int
    var address: String // 新增属性
    
    init(name: String, age: Int, address: String) {
        self.name = name
        self.age = age
        self.address = address
    }
}

// 修改 Point 结构体
struct Point {
    var x: Double
    var y: Double
    let id: Int // 新增属性
}

在上面的示例中,我们添加了一个 address 属性到 Person 类中,并且添加了一个 id 属性到 Point 结构体中。需要注意的是,如果我们想要在结构体中添加不可变的属性,必须使用 let 关键字。

类和结构体的方法

类和结构体也可以定义方法。方法是类和结构体用来执行特定任务的函数。一个方法可以访问和修改属性,以及使用其他方法来实现其功能。例如,我们可以向 Person 类中添加一个打招呼的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 添加方法到 Person 类
class Person {
    var name: String
    var age: Int
    var address: String
    
    init(name: String, age: Int, address: String) {
        self.name = name
        self.age = age
        self.address = address
    }
    
    func sayHello() { // 新增方法
        print("Hello, my name is \(name)")
    }
}

在上面的示例中,我们添加了一个名为 sayHello 的方法到 Person 类中。这个方法会输出一个问候语句,告诉我们这个人的名字。

与属性一样,结构体也可以定义方法。例如,我们可以向 Point 结构体中添加一个计算它所表示的点与另一个点之间距离的方法:

1
2
3
4
5
6
7
8
9
10
11
12
// 添加方法到 Point 结构体
struct Point {
    var x: Double
    var y: Double
    let id: Int
    
    func distance(to point: Point) -> Double { // 新增方法
        let dx = x - point.x
        let dy = y - point.y
        return sqrt(dx * dx + dy * dy)
    }
}

在上面的示例中,我们添加了一个名为 distance 的方法到 Point 结构体中。这个方法接受一个类型为 Point 的参数,并返回当前点与传入点之间的距离。

需要注意的是,如果你想要在方法内修改结构体的属性值,那么这个方法必须被标记为 mutating。这是因为结构体是值类型,它们的实例默认情况下是不可变的。但是类不需要这样,因为它们是引用类型,它们的实例默认情况下是可变的。

类和结构体的继承

继承是一种代码重用机制,允许你定义一个基类,并从中派生出更具体的子类。在 Swift 中,只有类支持继承。使用 class 关键字定义一个基类,并使用 superclass 关键字来指定一个类的父类。例如,我们可以定义一个 Vehicle 基类,然后从中派生出 CarBicycle 两个子类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 定义 Vehicle 基类
class Vehicle {
    var currentSpeed = 0.0
    
    func makeNoise() {
        // 空实现
    }
}

// 继承自 Vehicle 基类的 Car 子类
class Car: Vehicle {
    var numberOfWheels = 4
}

// 继承自 Vehicle 基类的 Bicycle 子类
class Bicycle: Vehicle {
    var hasBasket = false
}

在上面的示例中,我们定义了一个名为 Vehicle 的基类,包含一个 currentSpeed 属性和一个 makeNoise 方法。然后我们从 Vehicle 基类中派生出 CarBicycle 两个子类,它们都继承了 Vehicle 中的属性和方法。我们还为 Car 子类添加了一个名为 numberOfWheels 的新属性。

需要注意的是,一个类只能继承自一个父类,但它可以实现多个协议。

结构体的值类型特性

Swift 中的结构体是值类型,这意味着当你将一个结构体传递给函数或方法时,它会被复制一次。这个过程被称为值拷贝。例如,假设我们有一个 Point 结构体实例,然后我们将它传递给一个函数:

1
2
3
4
5
6
func printPoint(_ point: Point) {
    print("(\(point.x), \(point.y))")
}

let somePoint = Point(x: 2.0, y: 3.0)
printPoint(somePoint)

在上面的示例中,我们定义了一个名为 printPoint 的函数,它接受一个类型为 Point 的参数。然后我们创建了一个名为 somePointPoint 结构体实例,并将其传递给 printPoint 函数。

在将 somePoint 传递给 printPoint 函数时,Swift 会在内存中复制一份 somePoint 的值,并将其传递给 printPoint 函数。这意味着 printPoint 函数获得的 Point 实例是 somePoint 的一个完全独立的副本。如果在 printPoint函数中修改 Point 实例的属性,那么这些修改不会影响到原始的 somePoint 实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func printAndModifyPoint(_ point: Point) {
    print("Original Point: (\(point.x), \(point.y))")
    var modifiedPoint = point
    modifiedPoint.x += 1.0
    modifiedPoint.y -= 2.0
    print("Modified Point: (\(modifiedPoint.x), \(modifiedPoint.y))")
}

let somePoint = Point(x: 2.0, y: 3.0)
printAndModifyPoint(somePoint)

// 输出:
// Original Point: (2.0, 3.0)
// Modified Point: (3.0, 1.0)

在上面的示例中,我们定义了一个名为 printAndModifyPoint 的函数,它接受一个类型为 Point 的参数。然后我们创建了一个名为 somePointPoint 结构体实例,并将其传递给 printAndModifyPoint 函数。

printAndModifyPoint 函数中,我们首先打印出原始的 Point 实例的值。然后我们复制了一份 Point 实例,并对副本进行了一些更改。需要注意的是,我们不能直接修改原始的 Point 实例,因为它是不可变的。最后,我们打印出了修改后的 Point 实例的值。

类的引用类型特性

与结构体不同,Swift 中的类是引用类型。这意味着当你将一个类实例传递给函数或方法时,它不会被复制。相反,在内存中只有一个类实例,但可以有许多指向该实例的引用。例如,假设我们有一个 Person 类实例,然后我们将它传递给一个函数:

1
2
3
4
5
6
func printPerson(_ person: Person) {
    print("Name: \(person.name), Age: \(person.age), Address: \(person.address)")
}

let somePerson = Person(name: "Tom", age: 25, address: "Beijing")
printPerson(somePerson)

在上面的示例中,我们定义了一个名为 printPerson 的函数,它接受一个类型为 Person 的参数。然后我们创建了一个名为 somePersonPerson 类实例,并将其传递给 printPerson 函数。

在将 somePerson 传递给 printPerson 函数时,Swift 不会在内存中复制 somePerson 的值。相反,printPerson 函数获取的是指向 somePerson 实例的引用。如果在 printPerson 函数中修改 Person 实例的属性,那么这些修改将影响到原始的 somePerson 实例。

1
2
3
4
5
6
7
8
9
10
11
12
func modifyPerson(_ person: Person) {
    person.name = "Jerry"
    person.age += 1
    person.address = "Shanghai"
}

let somePerson = Person(name: "Tom", age: 25, address: "Beijing")
modifyPerson(somePerson)
printPerson(somePerson)

// 输出:
// Name: Jerry, Age: 26, Address: Shanghai

在上面的示例中,我们定义了一个名为 modifyPerson 的函数,它接受一个类型为 Person 的参数。然后我们创建了一个名为 somePersonPerson 类实例,并将其传递给 modifyPerson 函数。

modifyPerson 函数中,我们通过指向 Person 实例的引用修改了它的属性值。最后,我们调用了 printPerson 函数来检查所做的更改是否已经生效,结果表明它们确实已经生效了。

类和结构体应用场景的区别

结构体实例总是通过值来传递,而类实例总是通过引用来传递。这意味着他们分别适用于不同类型的任务。

结构体是一种值类型,在 Swift 中使用非常广泛。以下是一些适合使用结构体的场景:

  • 适合作为简单数据容器:对于只包含少量属性的数据类型,使用结构体更加合适。例如,PointSizeRect 等。
  • 存储小型数据:由于结构体是值类型,它们通常比类更加轻量级,更适合存储小型数据。这意味着当你需要在内存中存储大量数据时,结构体通常是更好的选择。
  • 传递参数时,适合进行复制:当你将结构体作为参数传递给函数或方法时,它会被复制一份。这个过程称为值拷贝。由于结构体是值类型,所以它们在复制时效率很高,因此非常适合用作传递参数时进行复制的数据类型。

类是一种引用类型,在 Swift 中也得到了广泛的应用。以下是一些适合使用类的场景:

  • 需要继承和多态:如果你需要创建一个基类,并派生出多个子类来扩展其功能,那么类是更合适的选择。由于结构体不支持继承,所以如果你需要使用继承和多态功能,那么必须使用类。
  • 需要共享实例:当你需要让多个对象共享同一个状态时,使用类是更好的选择。由于类是引用类型,所以它们在内存中只有一份实例,可以被多个对象所引用。
  • 需要动态分配和释放内存:类支持动态分配和释放内存,所以如果你需要在运行时动态分配和释放内存,那么必须使用类。

需要注意的是,虽然类通常比结构体更重,但这并不意味着你应该始终使用结构体来避免性能问题。在大多数情况下,选择数据类型应该优先考虑代码的可读性和清晰度,而不是性能。除非你确定性能问题是关键问题,否则不要过度优化代码。

本文由作者按照 CC BY 4.0 进行授权