在 Swift 中,类和结构体是创建代码所需的两种最基本的构建块。虽然它们有很多相似之处,但它们也有一些关键的区别。本文将介绍 Swift 中的类和结构体,以及它们之间的差异。
类和结构体的定义
从语法上来看,类和结构体有很多相似之处。它们都使用 class
和 struct
关键字定义,分别后跟名称和大括号 {}
来表示它们的结构。下面是一个简单的类和结构体示例:
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
的类,包含了两个属性 name
和 age
,并且有一个初始化方法。我们还定义了一个名为 Point
的结构体,包含了两个属性 x
和 y
。
值得注意的是,在 Swift 中,结构体可以像类一样拥有属性、方法和初始化方法。这是与许多其他编程语言不同的地方。
类和结构体的实例化
类和结构体的实例化非常相似。它们都需要使用名称后跟小括号 ()
的方式来创建一个新的实例。例如,我们可以这样实例化 Person
和 Point
:
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
实例。
类和结构体的属性
类和结构体都可以定义它们自己的属性。在类或结构体中定义属性时,需要在属性的前面加上 var
或 let
关键字来指定该属性是否是可变的。例如,我们可以修改 Person
和 Point
,使其包含一些额外的属性:
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
基类,然后从中派生出 Car
和 Bicycle
两个子类:
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
基类中派生出 Car
和 Bicycle
两个子类,它们都继承了 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
的参数。然后我们创建了一个名为 somePoint
的 Point
结构体实例,并将其传递给 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
的参数。然后我们创建了一个名为 somePoint
的 Point
结构体实例,并将其传递给 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
的参数。然后我们创建了一个名为 somePerson
的 Person
类实例,并将其传递给 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
的参数。然后我们创建了一个名为 somePerson
的 Person
类实例,并将其传递给 modifyPerson
函数。
在 modifyPerson
函数中,我们通过指向 Person
实例的引用修改了它的属性值。最后,我们调用了 printPerson
函数来检查所做的更改是否已经生效,结果表明它们确实已经生效了。
类和结构体应用场景的区别
结构体实例总是通过值来传递,而类实例总是通过引用来传递。这意味着他们分别适用于不同类型的任务。
结构体是一种值类型,在 Swift 中使用非常广泛。以下是一些适合使用结构体的场景:
- 适合作为简单数据容器:对于只包含少量属性的数据类型,使用结构体更加合适。例如,
Point
、Size
、Rect
等。 - 存储小型数据:由于结构体是值类型,它们通常比类更加轻量级,更适合存储小型数据。这意味着当你需要在内存中存储大量数据时,结构体通常是更好的选择。
- 传递参数时,适合进行复制:当你将结构体作为参数传递给函数或方法时,它会被复制一份。这个过程称为值拷贝。由于结构体是值类型,所以它们在复制时效率很高,因此非常适合用作传递参数时进行复制的数据类型。
类是一种引用类型,在 Swift 中也得到了广泛的应用。以下是一些适合使用类的场景:
- 需要继承和多态:如果你需要创建一个基类,并派生出多个子类来扩展其功能,那么类是更合适的选择。由于结构体不支持继承,所以如果你需要使用继承和多态功能,那么必须使用类。
- 需要共享实例:当你需要让多个对象共享同一个状态时,使用类是更好的选择。由于类是引用类型,所以它们在内存中只有一份实例,可以被多个对象所引用。
- 需要动态分配和释放内存:类支持动态分配和释放内存,所以如果你需要在运行时动态分配和释放内存,那么必须使用类。
需要注意的是,虽然类通常比结构体更重,但这并不意味着你应该始终使用结构体来避免性能问题。在大多数情况下,选择数据类型应该优先考虑代码的可读性和清晰度,而不是性能。除非你确定性能问题是关键问题,否则不要过度优化代码。