一: 结构体
在 Swift 标准库中,绝大多数的类型都是结构体类型,比如Bool,Int,Double,String,Array,Dictionary
等等都是结构体类型,比如Int
的定义如下:
public struct Int : FixedWidthInteger, SignedInteger {
...
}
编译器会根据情况为所有结构体类型自动生成初始化器(initializer)
,有可能会生成多个初始化器.生成初始化器的原则是:保证所有成员都有值
.
比如:
struct TestStruct{
var a: Int
var b: Int
}
// 编译器自动生成了传入a,b的初始化器
let s = TestStruct(a: 10, b: 20)
如果结构体的成员有初始值,那么编译器会给结构体生成多个初始化器.
比如只有一个成员有初始值:
struct TestStruct{
var a: Int = 0
var b: Int
}
// 生成了两个初始化器
let s = TestStruct(a: 10, b: 20)
let s2 = TestStruct(b: 20)
两个成员都有初始化器:
struct TestStruct{
var a: Int = 0
var b: Int = 0
}
//生成4个初始化器
let s = TestStruct(a: 10, b: 20)
let s2 = TestStruct(b: 20)
let s3 = TestStruct(a: 10)
let s4 = TestStruct()
我们也可以给结构体自定义初始化器,但是一旦自定义初始化器后,编译器就不会再自动生成初始化器:
自定义初始化器后,只有自定义的初始化器有效,系统不会再生成初始化器
其实我们自定义的初始化器和系统自动生成的初始化器,本质是一模一样的,他们的底层汇编都是一样的.
首先看一下编译器自动生成的初始化器的汇编:
编译器自动生成的初始化器汇编自定义初始化器汇编:
自定义初始化器汇编
可以看到上面两幅图的汇编语言完全是一模一样的.
结构体内存
结构体的内存和有关联值的枚举类型很相似:
结构体内存
结构体中还可以定义方法:
结构体中定义方法二:Class 类
类的定义
类的定义和结构体很相似,但是如果类的成员没有初始值,编译器不会为类生成初始化器:
编译器没有为类生成初始化器
如果类的成员都有初始值,编译器才会为类生成无参初始化器:
所以如果类的成员没有初始值的话,就需要我们自定义初始化器了:
自定义类的初始化器
类和结构体的区别
从上面可以看到结构体和类真的是非常的相似,那么结构体和类有什么区别呢?
一:类型不同,它们的本质区别是:结构体是值类型,类是引用类型
//类
func test(){
class Person{
var name:String
var age: Int
init(){
self.name = "Jack"
self.age = 18
}
}
let person = Person()
//结构体
struct Point {
let x: Int
let y: Int
}
let point = Point(x: 10, y: 20)
}
如上所示的代码,因为point 和 person
都是在test()
函数内部定义的.所以它们的内存布局如下:
解读:变量
point , person的内存空间都在栈空间.不同的是,
Struct的数据也存储在变量的内存中.而
Class类型的变量内存中存储的是一个地址,一个指向堆空间类对象的内存地址.
Class类型的数据都存储在堆空间中.
我们可以打个断点,看看point
变量和 person
变量的内存是不是如上图分析的那样,要搞清楚变量有没有分配堆空间内存,一个很重要的指标就是是否调用了malloc
方法,因为这个方法是向堆空间申请内存的.我们在let person = Person()
打上断点,然后一步步执行,会发现最终会调用malloc
方法:
而在let point = Point(x: 10, y: 20)
打断点,可以看到Point
的初始化代码的汇编简洁如下,根本就没有调用malloc
代码:
还可以直接打印出point
变量和person
变量的内容,更直观一些:
-
如果想查看一个类型所创建出来的变量占用多少内存,可以使用
MemoryLayout<类型>.stride
查看,比如:
查看String
类型对象占用多少内存:MemoryLayout<String>.stride
查看Int
类型对象占用多少内存:MemoryLayout<Int>.stride
-
如果想查看创建出来的指针变量在堆空间占用多少内存,可以使用
malloc_size(ptr: UnsafeRawPointer)
二:赋值操作不同,值类型是深拷贝,引用类型是浅拷贝(拷贝内存地址)
2.1 值类型的深度拷贝
看看struct
类型赋值操作的汇编语言:
//值类型的赋值操作
func testValueType(){
struct Point{
var x: Int
var y: Int
}
let a = Point(x: 10, y: 20)
var b = a
b.x = 11
b.y = 21
print("")
}
它的汇编如下:
第一步:进入Point 的 init() 函数 第二步:Point 的 init() 函数 函数内部 第三步:深拷贝
2.2 引用类型的浅拷贝
我们把上面的struct
改成class
:
func testReferenceType(){
class Point{
var x: Int
var y: Int
init(x: Int,y: Int){
self.x = x
self.y = y
}
}
let a = Point(x: 10, y: 20)
var b = a
b.x = 11
b.y = 21
print("")
}
testReferenceType()
它的汇编代码如下:
引用类型赋值汇编代码
上图中的rax
存放的是Point
初始化方法创建的对象地址,我们打印看一下:
2.3值类型和引用类型的 let
使用let
修饰值类型或者引用类型的常量,它们的区别也是很大的.
如图所示代码:
因为point 和 person
都是let
修饰的,也就是说他们的内存是不允许修改的.
它们的内存存储方式如下:
网友评论