结构体
Swift 的结构体,让我们可以创建d自定义复杂的数据类型,并拥有自己的变量和函数。
关键字struct
struct Album {
let title: String
let artist: String
let year: Int
var number: Int
func printSummary() {
print("\(title) (\(year)) by \(artist)")
}
// 能改变自身的 变量的函数,需用mutating
mutating func takeVacation(days: Int) {
if 4 > days {
number = days
}
}
}
//创建 结构体实例。使用 初始化器
let redx = Album(title: "Red", artist: "Taylor Swift", year: 2012,number: 0)
此时,Album
就像String
或Int
————我们可以创建它们、赋值、复制它们等
- mutating关键字
任何 更改属于结构体的可变属性的方法,都必须用特殊的mutating
关键字标记
未标记为 mutating 的方法,无法调用 mutating 函数 - 您必须将它们都标记为 mutating
在 Swift 标准库中,比如
Bool、Int、Double、 String、Array、Dictionary
等常见类型都是结构体。
相关概念:属性 方法 实例 初始化器
属于结构体的变量和常量称为“属性”。
属于结构体的函数称为“方法”。
当我们从结构体中创建常量或变量时,我们将其称为“实例”
当我们创建结构体实例时,我们使用“初始化器”
- 函数和方法有什么区别
Swift 的函数和方法,两者都可以接受任意数量的参数,包括可变参数,并且都可以返回值。 那么有什么区别呢?
老实说,唯一真正的区别是:方法是属于类型的,例如结构、枚举和类,而函数则不然。这是唯一的区别。方法与特定类型(如结构体)相关联,意味着方法获得了一项重要的能力:它们可以引用该类型内的其他属性和方法。 方法还有一个优点 :方法可以避免名称空间污染。
初始化器
Swift 在名为 的结构体中默默地创建一个特殊函数init(),使用该结构体的所有属性作为其参数。然后它会自动将这两段代码视为相同:
let x = Album(title: "Red", artist: "Taylor Swift", year: 2012,number: 0)
let x = Album.init(title: "Red", artist: "Taylor Swift", year: 2012,number: 0)
如果属性有默认值,初始化器会自动调整
struct Dog {
let name: String
var year = 3
}
Dog(name: 'Tom')
Dog(name: 'Tom', year: 12)
如果给属性设置 可选变量,则会自动生成多个初始化器。
struct Point {
var x: Int?
var y: Int?
}
// 创建。
var p1 = Point(x: 10, y: 10)
var p2 = Point(y: 10)
var p3 = Point(x: 10)
var p4 = Point()
黄金法则:所有属性在初始化程序结束时都必须有一个值
- 自定义 初始化器
struct Player {
let name: String
let number: Int
init(name: String, number: Int) {
self.name = name
self.number = number
// 其他
}
init(name: String) {
self.name = name
number = Int.random(in: 1...9)
}
}
self
表示实例本身
初始化器 没有func关键字 ,也不会显式地具有返回类型
可以在初始化程序中调用结构体的其他方法,但在为所有属性赋值之前不能这样做
如果您创建自己的初始化程序,则删除成员初始化程序。
struct Employee {
var name: String
var yearsActive = 0
init() {
self.name = "Anonymous"
}
}
这时候,将不再被允许:
let roslin = Employee(name: "Laura Roslin")
如果您希望保留它,请将自定义初始值设定项移至扩展
extension Employee {
init() {
self.name = "Anonymous"
}
}
属性
结构体可以有两种属性:
- 存储属性:是在结构实例内保存一段数据的变量或常量。
- 计算属性:在每次访问时 动态计算属性的值。这意味着,计算属性是存储属性和函数的混合:它们像存储属性一样访问,但像函数一样工作。
struct Employee {
// 存储属性
var vacationAllocated = 14
var vacationTaken = 0
// 计算属性
var vacationRemaining: Int {
vacationAllocated - vacationTaken
}
}
或
// 计算属性(可读可写)
var vacationRemaining: Int {
get {
vacationAllocated - vacationTaken
}
set {
vacationAllocated = vacationTaken + newValue
}
}
newValue
是由 Swift 自动提供给我们的,并存储用户尝试分配给属性的任何值
什么时候应该使用计算属性或存储属性 ???
属性观察器
是在属性发生更改时 运行的特殊代码片段。它们采用两种形式:didSet
观察者在属性刚刚更改时运行;willSet
观察者在属性更改之前运行。
struct Game {
var score = 0 {
willSet {
print("当前值= \(score)")
print("新值 将= \(newValue)")
}
didSet {
print("分数= \(score)")
print("旧值= \(oldValue)")
}
}
}
在实践中,willSet的使用量比 didSet少得多。性能考虑,请尽量避免让观察器 投入太多工作
访问控制
关键字修饰 属性或方法
private
“不要让结构体之外的任何东西使用它”。
fileprivate
“不要让当前文件之外的任何内容使用它”。
public
“让任何人、任何地方都可以使用它”。
静态属性、静态方法
有时,我们希望将属性或方法添加到结构体本身,而不是其实例。使用关键字static
修饰,这样,它们就属于结构体本身,而不是结构体的各个实例。 我们不需要创建实例,就可以使用。这意味着它们在结构体的实例上不是唯一存在的。
struct School {
static var studentCount = 0
static func add(student: String) {
print("\(student) joined the school.")
studentCount += 1
}
}
// 调用
School.add(student: "Taylor")
静态属性和方法,不能引用非静态属性和方法
self 和 Self的含义不同:
self
指的是结构体的当前值,Self
指的是当前类型
- 静态属性的适用场景
1、使用静态属性来组织 应用程序中的通用数据 (全局数据);通用方法
如:
struct AppData {
static let version = "1.3 beta 2"
static let name = "xxxxx"
static let homeURL = "https://www.xxxxxxx.com"
static func getRandomInt() -> Int {
return Int.random(in: 1...1000) + 1
}
}
2、创建结构示例,方便预览
struct Employee {
let username: String
let password: String
static let example = Employee(username: "xxx", password: "cccc")
}
只有少数情况下静态属性或方法才有用途,但它们仍然是一个有用的工具
结构体 适用场景
结构体的主要目的是用来 封装少量相关简单数据值。
有理由预计一个结构体实例在赋值或传递时,封装的数据将会被拷贝而不是被引用。
任何在结构体中储存的值类型属性,也将会被拷贝,而不是被引用。
结构体不需要去继承另一个已存在类型的属性或者行为。
元组VS结构体
- 当您想要从函数返回两个或多个任意值时,请使用元组。但当您想要多次发送或接收某些固定数据时,更喜欢使用结构体。
类
类与结构体有许多共同点,但在关键地方有所不同。
- 共同点
您可以创建并命名它们。 您可以添加属性和方法,包括属性观察器和访问控制。 您可以创建自定义初始值设定项 来根据需要配置新实例。
- 不同点
- 您可以使一个类继承另一个类中的功能,获取其所有属性和方法。如果您想重写某些方法,您也可以这样做。
- 由于第一点,Swift 不会自动为类生成成员初始化程序!!所以您要么需编写自己的初始值设定项,要么为所有属性分配默认值。
- (重要)当您复制类的实例时,两个副本共享相同的数据 - 如果您更改一个副本,另一个副本也会更改。即 浅拷贝。结构体的每个副本都是独立的,唯一的,并保存自己的数据。
- 当类实例的最终副本被销毁时,Swift 可以选择运行
deinitializer
函数。结构体没有此函数。 - 即使您将类设为常量,您仍然可以更改其var属性。 但常量结构体中的变量属性则不能。
编译器不会为类自动生成成员初始化程序。但希望您能理解,因为一个类可以基于另一个类 - 如果类 C 添加一个额外的属性,它将破坏 C、B 和 A 的所有初始化器。
类的作者必须手动编写自己的初始化程序
由于类的一个实例可以在多个位置引用,因此了解最终副本何时被销毁变得很重要。这就是去初始化器的用武之地:它允许我们清理在最后一个副本消失时分配的任何特殊资源。
如果类的所有成员 都在定义时初始化了,编译器会为类生成无参的初始化器
关键字class
class Point {
var x: Int = 0
var y: Int = 0
}
// 创建
var point = Point()
继承
Swift 允许我们基于现有类来创建类,这个过程称为继承。当一个类从另一个类(其“父类”或“超级”类)继承功能时,Swift 将授予新类(“子类”或“子类”)对该父类的属性和方法的访问权限,从而允许我们进行小的添加或更改以自定义新类的行为方式。
请在子类名称后面写一个冒号,然后添加父类名称。以下是例子:
// 父类
class Employee {
let hours: Int
// 必须 初始化
init(hours: Int) {
self.hours = hours
}
func printSummary() {
print("工作了 \(hours) 小时")
}
}
// 子类1
class Developer: Employee {
func work() {
print("我写代码有 \(hours) 小时.")
}
}
// 子类2
class Manager: Employee {
func work() {
print("我开会了 \(hours) 小时.")
}
}
-
子类可直接引用父类的属性,直接调用父类的公共方法
-
子类想要更改父类的方法,则必须用
override
修饰该方法以重写
class Manager: Employee {
override func printSummary() {
print("工作中 开会了 \(hours) 小时")
}
}
使用了override,但你的方法实际上并没有覆盖父类中的某些内容,Swift 将拒绝构建你的代码
父类的A方法不带参,子类想更改为带参,则不需要override。
final修饰的类,不可被继承
类的初始化器
如果子类有任何自定义初始化器,它必须始终在完成自己的初始化器后,调用父类的初始化器,如果有的话。
class Vehicle {
let isElectric: Bool // 是否 电动
init(isElectric: Bool) {
self.isElectric = isElectric
}
}
class Car: Vehicle {
let isConvertible: Bool
init(isElectric: Bool, isConvertible: Bool) {
self.isConvertible = isConvertible
super.init(isElectric: isElectric) // 必须
}
}
super是 Swift 自动为我们提供的另一个值,类似于self:它允许我们调用属于父类的方法
如果子类没有任何自己的初始值设定项,它将自动继承其父类的初始值设定项。
引用类型 和 值类型
类实例的所有副本共享相同的数据,这意味着您对一个副本所做的任何更改,都会自动更改其他副本。因为类在 Swift 中是引用类型 。结构体是值类型。
因此,在类中,不需要对更改其属性的方法使用关键字 mutating
反初始化器
Swift 的类可以选择赋予一个deinitializer
,这有点像初始化器的反面,因为它在对象被销毁时而不是在创建时被调用.
如果你看一下大局,你会发现每个都使用大括号来创建它们的作用域:条件、循环和函数都创建局部作用域。
当一个值退出作用域时,我们意味着创建它的上下文将消失。对于结构来说,这意味着数据正在被破坏,但对于类来说,这意味着只有底层数据的一个副本会消失——其他地方可能仍然有其他副本。但是,当最终副本消失时(当指向类实例的最后一个常量或变量被销毁时),底层数据也会被销毁,并且它所使用的内存将返回给系统。
deinit {
print("User \(id): I'm dead!")
}
- 自动引用计数
Swift 在幕后执行称为自动引用计数(ARC)的操作。ARC 跟踪每个类实例存在多少个副本:每次获取类实例的副本时,Swift 都会为其引用计数添加 1,每次销毁副本时,Swift 都会从其引用计数中减去 1。当计数达到 0 时,意味着没有人再引用该类,Swift 将调用其析构函数并销毁该对象。
类的使用
类不像结构体那样常用,但在共享数据方面,具有非常宝贵的用途
结构体与类对比
结构体是值类型,类是引用类型(指针类型)
-
值类型 Swift 中所有的结构体、枚举类型都是值类型。当它被赋值给一个变量、常量或者被传递给一个函数的时候,其内容会被拷贝。相当于“深拷贝”,两者相互独立。
-
引用类型 在被赋予到一个变量、常量或者被传递到一个函数时,其值不会被拷贝。因此,使用的是已存在实例的引用,而不是其拷贝。相当于“浅拷贝”。
因为类是引用类型,所以多个常量和变量可能在幕后同时引用同一个类实例。判定两个常量或者变量,是否引用同一个类实例,Swift 提供了两个恒等运算符:相同
===
、不相同!==
由于结构体属于值类型。当值类型的实例被声明为常量的时候,它的所有属性也就成了常量。属于引用类型的类则不一样。把一个引用类型的实例赋给一个常量后,依然可以修改该实例的可变属性。