swift学习笔记 ⑤ —— 结构体和类

Swift学习笔记 - 文集

一、结构体

在 Swift 中,绝大多数公开的类型都是结构体类型。我们在swift学习笔记 ① —— 基础语法中讲到,Swift 提供了值类型引用类型两种数据类型,结构体就属于值类型。而Int、Bool、Double、Array、Dictionary等常见类型都属于结构体类型。

和 OC 一样,我们通过关键字 struct 来定义结构体,也可以为结构体定义属性(常量、变量)和添加方法,从而扩展结构体的功能。:

struct Point {
    var x: Int
    var y: Int
}

所有的结构体都有一个编译器自动生成可以传入成员值的初始化器,我们在使用结构体时,传入所有存储属性值,用以初始化所有存储属性。例如:

var point = Point(x: 10, y:20)

编译器为了保证所有成员都有初始值,也会根据实际情况生成不同的初始化器。例如在声明结构体时,我们设置了其中一个成员的初始值,在使用结构体时就不用再设置初始值:

struct Point {
    var x: Int = 0
    var y: Int = 0
}
// x y 已经设置了初始值,可以不传入初始值
var point = Point()
// x y 已经设置了初始值,也可以继续传入新的值
var point1 = Point(x: 10, y:20)
//可以单独传入x 或 y 的初始值
var point2 = Point(x: 10)
var point3 = Point(y: 20)

如果我们在定义结构体时,自定义了初始化器,那么编译器就不会再为我们生成初始化器:

struct Point {
    var x: Int = 0
    var y: Int = 0
    init(x: Int, y: Int) {
        self.x = x
        self.y = y
    }
}

例子中我们自定义了初始化器,所以在使用时需要传入初始值:

var point = Point(x: 10, y:20)

如果没有传入所有成员的初始值那么编译就会报错:

var point1 = Point()
var point2 = Point(x: 10)
var point3 = Point(y: 20)

结构体的内存结构

我们可以通过 MemoryLayout来分析一下结构体的内存结构:

struct Point {
    var x: Int = 0
    var y: Int = 0
    var origin: Bool = true
}
print(MemoryLayout.size) // 17
print(MemoryLayout.stride) // 24
print(MemoryLayout.alignment) // 8

可以看出,结构体Point实际占用内存为 17 个字节,在内存中共占用 24 个字节,内存对齐是 8 个字节。

Swift中,类和结构体很相似。我们可以为类定义属性(常量、变量)和方法。

class Size {
    var width: Int = 0
    var height: Int = 0
}

上面的例子中我们就定义了 Point类,里面包括两个属性。

需要注意的是,编译器并没有为类自动生成可以传入成员值的初始化器,但是因为xy有初始值,所以编译器为类生成了一个无参的初始化器。所以我们可以通过无参的方式调用:

var size1 = Size()

由于编译器并没有为类自动生成可以传入成员值的初始化器,所以下面的调用会报错:

var size2 = Size(width: 10)
var size3 = Size(height: 20)
var size4 = Size(width: 10, height:20)

结构体和类的区别

两者的区别就是:结构体是值类型,类是引用类型(指针类型)。

例如下面的代码:

struct Point {
    var x: Int = 10
    var y: Int = 20
}

class Size {
    var width: Int = 30
    var height: Int = 40
}

var point = Point()
var size = Size()

由于结构体是值类型,那么例子中的变量 point 在栈空间中占据的 16 个字节的内存空间保存的就是 xy的值;而类是引用类型,变量 size 是一个指针变量,在栈空间中占据 8 个字节内存,保存的是指针变量 size 指向的内存地址,也就是 Size() 类对象的内存地址。

Size() 类对象保存在堆空间中,占据了 24 个字节的内存空间。其保存的数据除了widthheight两个成员的值外,还保存着指向类型的相关信息引用计数

下面我们通过 MemoryLayout来打印一下size对象的内存:

var size = Size()

print(MemoryLayout.stride(ofValue: size))  // 8
print(MemoryLayout.size(ofValue: size))  // 8
print(MemoryLayout.alignment(ofValue: size)) // 8

可以看出,size对象实际占用内存为 8 个字节,在内存中共占用 8 个字节,内存对齐是 8 个字节。

我们通过第三方工具Mems再来看一下变量pointsize分别保存了什么:

print("size变量的内存内容:", Mems.memStr(ofVal: &size))
print("point变量的内存内容:", Mems.memStr(ofVal: &point))

打印输出:

size变量的内存内容: 0x0000000100706d40
point变量的内存内容: 0x000000000000000a 0x0000000000000014

可以看出,size变量保存的是一个内存地址,而point变量保存的则是初始值 10 和 20。
我们再来看一下size变量保存的内存地址指向的内存保存的内容:

print("size所指向内存的内容", Mems.memStr(ofRef: size))

打印输出:

size所指向内存的内容 0x00000001000086e8 0x0000000200000002 0x000000000000001e 0x0000000000000028

可以看出,32 个字节中一共存储了 4 中内容。保存的具体内容是:最开始的 8 个字节保存了 指向类型的相关信息,其次 8 个字节保存的是引用计数,最后两个 8 个字节内存分别存储了widthheight两个成员的值 30 和 40 。

值类型

值类型赋值给变量或常量,或者给作为参数传给函数时,是直接将内容拷贝一份,类似作copy操作,产生一个全新的副本,属于深拷贝。

在 Swift 中,为了提升性能,StringArrayDictionarySet都采取了 Copy On Write技术,只有存在“写”操作时,才会真正执行拷贝操作。

例如:

var name1 = "Mars"
var name2 = name1
name2.append("_iOS")
print(name1) // Mars
print(name2) // Mars_iOS
var a1 = [1, 2, 3]
var a2 = a1
a2.append(4)
a1[0] = 5
print(a1) // [5, 2, 3]
print(a2) // [1, 2, 3, 4]

引用类型

不同于值类型,引用类型赋值给变量或常量,或者给作为参数传给函数时,是直接将内存地址拷贝一份,指向同一块内存,属于浅拷贝。
例如:

class Size {
    var width: Int
    var height: Int
    init(width: Int, height: Int) {
        self.width = width
        self.height = height
    }
}
var s1 = Size(width: 10, height: 20)
var s2 = s1
s2.width = 11
s2.height = 22
print(s1.width) // 11
print(s1.height) // 22
print(s2.width) // 11
print(s2.height) // 22

可以看出,我们在修改变量s2的值时,变量s1的值也发生了改变。这就是因为指针变量s1s2指向的是同一块内存,当修改s2时,变量s1s2指向的内存空间所保存的数据发生改变,自认打印s1的值也会发生改变。

更多技术知识请扫码关注微信公众号

iOS进阶

swift学习笔记 ⑤ —— 结构体和类_第1张图片
iOS进阶.jpg

你可能感兴趣的:(swift学习笔记 ⑤ —— 结构体和类)