Swift学习39:结构体

张建 lol

结构体

  • Swift 结构体构建代码 所用的 一种 通用且灵活的 构造体

  • Swift 结构体值类型,通过 复制 的方式在代码中 传递,因此它的值是不可修改的

类和结构体对比

Swift 中类和结构体有很多共同点:

  • 定义 属性 用于 存储值
  • 定义 方法 用于 提供功能
  • 定义 下标脚本 用于访问值
  • 定义 初始化器 用于 生成初始化值
  • 通过 扩展增加默认实现的功能
  • 符合 协议对某类提供标准功能

与结构体相对,类还有如下的 附加 功能:

  • class 定义 属性,必须赋值或包装成Optional。struct 定义属性不需要要赋值
  • class 是 引用类型,浅拷贝。struct 是 值类型,深拷贝。
  • class 可以继承。struct 不能继承
  • class 有 引用计数,可以 多次引用。struct 没有引用计数
  • class 有 类型转换,允许在 运行时检查和解释一个类实例的类型。struct 没有
  • class 有 解构器 允许一个 类实例释放任何其所被分配的资源
  • struct 分配在 中,系统分配是否内存。class 分配在 中,动态分配释放内存
  • struct 比 class 更轻量级, 只访问一次拿到数据, 访问两次拿到数据(第一次获取指针,第二次获取数据)

作用

结构体可以 定义属性(常量let、变量var)添加方法,从而 扩展结构体的功能

语法

通过关键字 struct 来定义结构体:

1
2
3
struct 结构体名 {
// 定义 属性和方法等
}

定义存储属性

struct 定义存储属性,允许不设置默认值

1
2
3
4
5
struct Person {
// 存储属性
let age: Int
var name: String
}

实例化

1
2
3
4
5
6
7
8
// 实例化
let p = Person(age: 32, name: "ZJ")
print(p.age)
print(p.name)

=======
32
ZJ

延迟存储属性

  • 延迟存储属性的初始值在第一次使用时才进行计算

  • 声明前面标注 lazy 关键字来表示一个 延迟存储属性

  • lazy 修饰的属性,必须要初始化

示例:

1
2
3
4
5
6
7
8
9
10
// 延迟存储属性
struct Manager {
// lazy 必须被初始化
lazy var name:String = ""
}
var m = Manager(name: "ZJ")
print(m.name)

=======
ZJ

定义计算属性

  • 除了 存储属性 还能定义 计算属性,而它实际 并不是存储值

  • 相反,它提供一个 读取器 和一个 可选的设置器 来间接得到和设置其它的 属性和值。类似于 OCset/get 方法去设值和取值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 计算属性
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()
// 计算属性:可以通过origin 和 size 计算出来
var center: Point {
get {
let centerX = origin.x + size.width/2.0
let centerY = origin.y + size.height/2.0
return Point(x:centerX, y:centerY)
}
set(newCenter) {
origin.x = newCenter.x - size.width/2.0
origin.y = newCenter.y - size.height/2.0
}
}
}

=============
Point(x: 50.0, y: 50.0)

简写 setter

  • 如果一个 计算属性设置器 没有为将要被设置的值定义一个名字,那么它将被默认命名为 newValue
1
2
3
4
set {
origin.x = newCenter.x - size.width/2.0
origin.y = newCenter.y - size.height/2.0
}

简写 getter

  • 如果整个 getter 的函数体是一个 单一的表达式,那么 getter 隐士返回这个表达式
1
2
3
4
// 简写 get
get {
Point(x:origin.x + size.width/2.0, y:origin.y + size.height/2.0)
}

只读计算属性

  • 一个 有读取器 但是 没有设置器计算属性 就是所谓的 只读计算属性。只读计算属性返回一个值,也可以通过 点语法 访问,但是不能被修改为另一个值

  • 你必须用 var关键字 定义计算属性(包括只读计算属性)为变量属性,因为它的 值不是固定的let关键字 只能用于常量属性,用于明确哪些值一旦作为实例初始化就不能更改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 只读计算属性
struct Rect {
// 存储属性
var origin = Point()
var size = Size()
// 计算属性:可以通过origin 和 size 计算出来
var center: Point {
get {
let centerX = origin.x + size.width/2.0
let centerY = origin.y + size.height/2.0
return Point(x:centerX, y:centerY)
}
}
}

属性观察者

  • willSet 会在 该值被存储之前被调用

  • didSet 会在 一个新值被存储之后被调用

  • 如果你实现了一个 willSet 观察者,新的属性值会以常量形式参数传递。你可以再你的 willSet 实现中为这个参数定义名字。如果你没有为它命名,那么它会使用默认值的名字 newValue

  • 如果你实现了一个 didSet 观察者,一个包含旧属性值的常量形式参数将会被传递。你可以为它命名,也可以使用默认值的形式参数名 oldValue。如果你在属性自己的 disSet 观察者里给自己赋值,你赋值的新值就会取代刚刚设置的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 属性观察器
struct StepCount {
var totalStep: Int = 0 {
// 值被存储前调用
willSet(newValue) {
print(newValue)
}
// 值被存储后调用
didSet(oldValue) {
// 如果 存储后的值 > 存储前的值
if totalStep > oldValue {
print("步数增加: \(totalStep - oldValue)")
}
}
}
}
var step = StepCount()
step.totalStep = 100

============
100
步数增加: 100

全局和局部变量

  • 观察属性的能力同样对全局变量和局部变量有效全局变量 是定义在 任何函数、方法、闭包或者类之外的变量局部变量 是定义在 函数、方法或闭包环境之中的变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 全局变量
struct Sum {
var count: Int = 0 {
willSet {
print("count : \(newValue)")
}
didSet {
print("did change count from \(oldValue) to \(count)")
}
}
}
var sum = Sum()
sum.count = 10 // 全局变量
print(sum.count)

==============
count : 10
did change count from 0 to 10

类型属性

  • struct、enum 中使用 static 关键字来定义 类型属性
1
2
3
4
5
6
7
8
9
// 类型属性
class SuperClass {
// 类型属性
static var name: String = "ZJ"
}
print(SuperClass.name)

=======
ZJ
  • struct、enum不允许 使用 class 关键字来定义 类型属性

定义实例方法

  • 实例方法 是属于 特定类实例、结构体实例或枚举实例的函数,提供 访问和修改实例属性的方法

  • 每一个类的实例都隐含一个叫做 self 的属性,它完全与实例本身相等。你可以用 self 属性在当前实例中调用自身的方法

  • 实际上,你不需要经常在代码中写 self。如果你没有显示地写出 selfswift 会在你方法中使用已知属性或方法的时候假定你调用了当前实例中的属性或者方法

  • 例外就是当一个实例方法的形式参数名与实例中某个属性拥有相同的名字的时候。在这种情况下,形式参数名具有优先权,并且调用属性的时候使用更加严谨的方式就很有必要了。你可以使用 self 属性来 区分形式参数名和属性名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 定义方法
struct PrintInfo {
var x = 0.0, y = 0.0
// 实例方法
func printInfo() {
print("x is \(x),y is \(y)")
}
}
var printInfo = PrintInfo()
printInfo.x = 10
printInfo.y = 20
printInfo.printInfo()

=======
x is 10.0,y is 20.0

实例方法 self

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// self
struct SomePoint {
var x = 0.0
// 实例方法 self
func isToTheRightOf(x: Double) -> Bool {
// self 访问 属性
return self.x > x
}
}
var somePoint = SomePoint()
somePoint.x = 4
if somePoint.isToTheRightOf(x: 1.0) {
print("the point is x \(somePoint.x)")
}

=======
the point is x 4.0

在实例方法中修改属性

  • struct、enum值类型的,默认情况下,值类型属性不能被修改

  • 你可以选择在 func 关键字前放一个 mutating(异变方法) 关键字来 指定可以修改属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 修改属性
struct ChangeName {
var name: String
// struct 中 使用 mutating 修改属性
mutating func change() {
name = "ZJ"
print(name)
}
}
var name = ChangeName(name: "")
name.change()

===========
ZJ

在 mutating 方法中赋值给 self

  • mutating 方法可以指定整个实例给隐含的 self 属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// self
struct SomePoint {
var x = 0.0
// 实例方法 self
func isToTheRightOf(x: Double) -> Bool {
// self 访问 属性
return self.x > x
}
}
var somePoint = SomePoint()
somePoint.x = 4
if somePoint.isToTheRightOf(x: 1.0) {
print("the point is x \(somePoint.x)")
}

=======
the point is x 4.0

类型方法

  • struct、enum 通过在 func 关键字前用 static 关键字来明确一个 类型方法
1
2
3
4
5
6
7
8
9
10
11
12
13
// 类型方法
struct PointClass {
var x = 0.0, y = 0.0
// 类型方法
static func pringInfo() {
print("pringInfo")
}
}
// 类型方法用 类 调用
PointClass.pringInfo()

=====
pringInfo
  • struct、enum 不允许既有 static 又有 mutating

定义初始化器

1
2
3
4
5
6
7
8
9
10
11
12
13
// 定义初始化器
struct Name {
var name: String
init(name: String) {
// 初始化值
self.name = "my name is \(name)"
}
}
let name = Name(name: "ZJ")
print(name.name)

=======
my name is ZJ

成员初始化器

如果结构体中没有定义任何初始化器,它会自动获得一个 成员初始化器

1
2
3
4
struct Size {
var width = 0.0, height = 0.0
}
let s = Size(width: 2.0,height: 2.0)

初始化器委托

初始化器可以调用其他初始化器,避免了多个初始化器里冗余代码

1
2
3
4
5
6
7
8
9
10
11
struct Name {
var name: String
init(name: String) {
// 初始化值
self.name = "my name is \(name)"
}
init(age: String,name: String) {
let newName = name + age
self.init(name:newName)
}
}

定义下标脚本

  • 类、结构体和枚举 可以定义 下标,它可以作为访问集合、列表或序列号成员元素的快捷方式。你可以使用下标通过索引值来设置或检索值而不需要为设置和检索分别使用实例方法

  • 你可以为一个类型 定义多个下标,并且下标会基于传入的索引值的类型选择合适的下标重载使用。下标没有限制单个维度,你可以使用多个输入形参来定义下标以满足自定义类型的需求

  • 下标脚本 允许你通过在实例名后面的方括号内写一个或多个值对该类的实例进行查询。它的语法类似于实例方法和计算属性。使用关键字 subscript 来定义下标,并且指定一个或多个输入形式参数和返回类型,与实例方法一样。与实例方法不同的是,下标可以是读写也可以是只读的

  • 下标脚本的访问用 []

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 定义下标脚本
struct Grade {
subscript(num: Int) ->String {
if num < 60 {
return "不及格"
}else {
return "及格"
}
}
}
let grade = Grade()
print(grade[99])

=======
及格
  • Post title:Swift学习39:结构体
  • Post author:张建
  • Create time:2023-03-05 17:04:39
  • Post link:https://redefine.ohevan.com/2023/03/05/Swift课程/Swift学习39:结构体/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.