结构体与类是构建应用APP的数据支柱,它们提供了重要的机制来为代码中要表达的事物建模,结构体的功能与类差不多,但也有很多不同的地方,可以根据自己的使用场景选择。
结构体(struct)是把相关数据块组合在一起放在内存中的一种数据类型,当需要把数据组合为一种通用类型时就可以用结构体。结构体可以把数据封装成为一种类型。
1、声明结构体与使用
//定义一个村庄的结构体
struct Twon{
let region = "south"//方位(使用let定义常量只读)
var population = 5_422//定义人口
var numberOfStopligts = 4//红绿灯数
//使用枚举嵌套在结构体里
enum Size {
case small
case medium
case large
}
//使用惰性存储(lazy)属性根据人口数定义村庄的规模
lazy var townSize: Size = {
//虽然直接使用population也可以访问值,但这里还是使用了self,因为self很重要
switch self.population {
case 0...1_000:
return Size.small
case 1_001...10_000:
return Size.medium
default:
return Size.large
}
}()//不要忘记最了的()
//打印村庄信息
func printDescription() {
print ("人口:(population)n",
"红绿灯:(numberOfStopligts)n")
}
//mutating 可修改属性值
mutating func changePopulation(by amount:Int){
population += amount
}
}
var myTwon = Twon()//初始化一个村庄
myTwon.printDescription()//打印村庄信息
print("村庄规模:" , myTwon.townSize)//村庄规模: medium
myTwon.changePopulation(by: 900000)//修改人口
//虽然改了人口但还是会输出medium(因为lazyd会在第一次读取的时候计算,后面调用将不会再次计算)
print("村庄规模:" , myTwon.townSize)//村庄规模: medium
myTwon.printDescription()//再次打印
mutating在上一章的枚举里有讲过,可以修改成员属性的值,结构的实例化与类一样的。可以调用结构体里的方法。只针对枚举和结构体的方法适合,类(class)里不能使用。因为结构体是一个值拷贝而类是值引用,mutating方法第一个参数是self并以inout的形式传入。
为什么self.population 里的selft很重要:这个闭包必须引用self才能在闭包里访问到这个population属性,为了让闭包能安全访问self,编译器必须知道self已经初始化完成了。把townSize设置为lazy是告诉编译器这个属性不是创建self必须的,如果它不存在就应该在它第一次被访问的时候创建,目的就是为了告诉编译器,如果要使用townSize就必须确保self可用。
被标记为lazy的属性只会在访问的时候计算一次,就算改变了计算值也不会重新再计算。如果要动态计算属性可使用下面的代码实现:
2、结构体与类的构造函数
很多程序语言里只支持写1个init的构造函数,而swift语言可以根据实例化时带入的参数不一样而调用不同的构造函数,可以我们可以在类与结构体里定义多个构造函数。
struct flag{
var x :Int
var y :Int
//不带参数的构造函数
init() {
self.x = 0
self.y = 0
print("你没有使用参数~所以坐标初始都为0")
}
//带参数实例化时使用的构造函数
iit(x:Int,y:Int) {
self.x = x
self.y = y
print("你使用了标准参数~")
}
//实际化时不写tag的构造函数
init(_ x:Int,_ y:Int) {
self.x = x
self.y = y
print("你使用了简洁参数~")
}
}
var f1 = flag()//打印:你没有使用参数~所以坐标初始都为0
var f2 = flag(x:123,y:321)//打印:你使用了标准参数~
var f3 = flag(11,22)//打印:你使用了简洁参数~
构造函数是在对象被实例化时自动调用的函数,结构体和类都可以使用多个构造函数。
类可用于抽象成一个通用类型的相关据据建模,与结构体不同之处,类有继承、重写等特性。
1、定义和实例化类
//接上面结构体代码
//定义一个动物类
class Animals{
var twon:Twon? //属于哪个村
var name = "Dog" //动物名字
var voice = "旺 、旺、旺"
func action() {
if twon != nil{
print(name + "每天保护村庄,并发出:" + voice)
}else{
print(name + "在世界流浪,暗自:" + voice)
}
}
}
var myTwon = Twon()//初始化结构体
let myDog = Animals()//实例化类
myDog.twon = myTwon//给类的成员属性赋值
myDog.action()//调用类的方法
2、类的继承
上面的Animals类本来是想做成动物的属性,却不小心变成了DOG,不方便我们未来扩展使用,假设我还要写一个关于“鸡”的类就可以从Animals类里去继承相关的属性与方法。
//Cock继承Animals所有的属性与方法
class Cock: Animals {
// 重写父级方法使用override关键字,final关键字禁止继承后再次重写,它们可以分开使用
final override func action() {
//使用let hasTwon = twon 检测twon是否被定义
if let hasTwon = twon{
twon?.changePopulation(by: 10)//调用结构体里的方法
super.action()//使用super访问父级的方法
}else{
print("公鸡打鸣:喔、喔、喔~")
}
}
func changeInfo(){
name = "鸡"
voice = "喔、喔、喔~"
}
}
let myCock = Cock()
myCock.changeInfo()//修改属性
myCock.twon = Twon()
myCock.action()
super是基于继承思想的产物,在枚举、结构体等值类型上不可用,调用super可以从父类借用功能或重写父类的功能。
Cock里的town继承于Animals,如果我们要检测town是否定义可以if let hasTwon = twon代码。
3、类的析构函数(deinit)
而析构函数是对像被销毁时才会调用的函数,结构体只有构造函数,而没有析构函数。析构函数常常用于关闭文件,断开链接等操作。
上面用到了惰性属性lazy,还有基本的属性。属性分为存储(stored)属性和计算(computed)属性,前面大部都使用的存储属性也就是变量、常量的赋值操作,在swift里我们还可以对变量、常量定义的时候进行计算操作,比如上面的lazy,通过计算属性我们可以对变量值进行逻辑处理,比如校验数据等。
1、计算属性
计算属性可以声明拥有读取方法get、和写入方法set。读取方法能从属性读取数据里处理逻辑。写入方法可以在赋值的时候处理逻辑。同时拥有读取和写入的被称为读写属性。
//在上面的类里增加一个属性
var victimPool:Int{
get{
return twon?.population ?? 0
}
set(newPop){
twon?.population = newPop
}
}
可以根据更复杂的条件给变量赋值。
var townSize:Size{
get{
switch self.population {
case 0...1_000:
return Size.small
case 1_001...10_000:
return Size.medium
default:
return Size.large
}
}
}
2、属性的观察者
属性观察者会观察并响应给定属性的变化,属性观察对于任何自定义的存储属性和任何继承的属性都可以。自定义的计算属性不能用属性观察者(但是我们对这类计算属性的读取写入有完全的控制权,所以用它来响应变化)
用法与上面的get、set类似,请看下面的代码:
var pop = 123{
didSet(oldPop){
print("pop的原始值为:" , oldPop )
}
willSet(newPop){
print("pop的新值为:" , newPop )
}
}
pop = 1233
//pop的新值为: 1233
//pop的原始值为: 123
我们可以通过属性观察者做很多逻辑上的处理。比如动画过度处理,数据库记录等。
3、类型方法
a.static 静态类型方法:值类型(struct)声明类型方法需要此类型方法,用于修饰类或者结构体里的属性与方法,使用static修饰只表示这个结构或者类只属于当前结构体或者类,调用时候需要使用结构类或者类的名字.方法(属性)名。如果是class使用此修饰子类不能覆盖父类的类型属性。
b.class 类的类属性:适合于引用类型(class)的类型方法,使用此修饰后子类是可以覆盖父类的类型属性,这也是与static的最大区别。
4、访问控制
访问级别 | 定义 |
---|---|
open | 实体对模块内的所有文件以及引入了此模块的文件都可见,并可以继承。 |
public | 可以访问自己模块中源文件里的任何实体,别人也可以通过引入该模块来访问源文件里的所有实体。 |
internal | 可以访问自己模块中源文件里的任何实体,但是别人不能访问该模块中源文件里的实体。 |
fileprivate | 文件内私有,只能在当前源文件中使用。 |
private | 只能在类中访问,离开了这个类或者结构体的作用域外面就无法访问。 |
注:open 为最高级访问级别,private 为最低级访问级别。
a.如果属性既有读取方法也有写入方法,我们想分别控制的时候可以使用 private(set) 指定为读取方法。这是表示写入是私有访问层级。其它如public、internal、或private只要写入方法的可见度不比读取方法更高就可以了。例如:如果 读取方法是internal那么就不能写public(set)。
结构体与类的存储属性在初始化完成的时候需要有初始值。如果没有使用初始化方法(init)的时候在实例化时编译器要求填入初始值。初始化方法(init)通常有1个或者多个。根据使用参数的不同而调用不同的初始化方法。代码参考上面 2、结构体与类的构造函数
1、结构体初始化:如果没有init构造函数编译器提供空初始化方法。默认初始化方法还有一种就是在定义的时间就已赋值如 var a = 1这样在实例化的时候也不能更改a的值了。
2、类初始化:因为类可以继承,类提供了designated指定初始化方法和convenience便捷初始化方法增加了其使用的复杂度,与结构体不同的是类没有默认的成员初始化方法,一般来说类不会继承父类的初始化方法。不过在有些情况下子类会自动继承父类的初始化方法:
a.如果子类没有定义任何指定初始化方法面会继承父类指定初始化方法。
b.如果子类实现了父类的所有指定初始化方法(不管显式还是隐式)就会继承父类所有的便捷初始方法。
必须初始化方法不需要使用override关键字标记,因为编译已知道required标记隐含了覆盖的意思。
一但我们写了自己的初始化方法swift就不再提供默认的初始化方法了。如果使用init?则是一个可失败的初始化方法。
结构体与类之间的重要区别在于一个是值类型(struct)一个是引用类型(class),根据场景的不同我们可以使用不的类型,特别是swiftUI的MVVM的时候我们更知道什么时候使用什么类型。
1、如果类型需要传值、哪就用结构体。这么做会确保赋值传递到函数参数中时类型被复制。
2、如果类型不支持子类继承,哪就用结构体。结构体不支持继承,所以不能子类。
3、如果类型表达的行为相对比较直观,而且包含一些简单的值,优化考虑使用结构体实现。有必须的话可以把类改为结构体。
4、其它情况都用类:
结构体在swiftUI中常常用于数据建模也就是MVVM中的第一个model(数据模型),比如图型的长、宽、高等,区别(起点、终点)、坐标(xyz)等适合定义数据结构,swift标准库中的String、Array和Dictionary类型都是使用结构体。
还有一个情况如果要把引用到处传值但是又不想有子类我们这里就可以使用final class{...}修饰。
除非注明,网络人的文章均为原创,转载请以链接形式标明本文地址:https://www.55mx.com/post/104
《【基础篇】6 结构体(struct)与类(class)的详解》的网友评论(0)