結(jié)構(gòu)體
先看一下簡單的語法書寫
栗子1
struct Point {
var x: Int = 0
var y: Int
}
var p1 = Point(x: 10, y:20)// 正確
var p2 = Point(y:20)//錯誤
var p3 = Point(x: 10)//錯誤
var p4 = Point()//錯誤
栗子2
struct Point {
var x: Int
var y: Int
}
var p1 = Point(x: 10, y:20)// 正確
var p2 = Point(x: 10)//錯誤
var p3 = Point( y:20)//錯誤
var p4 = Point()//錯誤
struct Point {
var x: Int = 0
var y: Int = 0
}
var p1 = Point(x: 10, y:20)// 正確
var p2 = Point(x: 10)//正確
var p3 = Point(y:20)//正確
var p4 = Point()//正確
編譯器干了啥?
編譯器會根據(jù)情況,可能會為結(jié)構(gòu)體生成生成多個初始化器,宗旨是:保證所有成員都有初始值.
??下面的代碼能編譯通過嗎?
struct Point {
var x: Int?
var y: Int?
}
var p1 = Point(x: 10, y:20)
var p2 = Point(x: 10)
var p3 = Point( y:20)
var p4 = Point()
自定義初始化器
struct Point {
var x: Int = 0
var y: Int = 0
init(x: Int, y: Int) {
self.x = x
self.y = y
}
}
var p1 = Point(x: 10, y:20)//正確
var p2 = Point(x: 10)//錯誤
var p3 = Point( y:20)//錯誤
var p4 = Point()//錯誤
窺看初始化器本質(zhì)
栗子1
struct Point {
var x: Int = 0
var y: Int = 0
}
var p = Point()
栗子2
struct Point {
var x: Int
var y: Int
init() {
self.x = 0
self.y = 0
}
}
var p = Point()
栗子1和栗子2完全等價
結(jié)構(gòu)體的本質(zhì)
func testStruct() {
struct Point {
var x: Int = 10
var y: Int = 20
var b: Bool = false
}
var p = Point.init(x: 10, y: 10, b: true)
print(Mems.memStr(ofVal: &p))
print(MemoryLayout<Point>.size)
print(MemoryLayout<Point>.stride)
print(MemoryLayout<Point>.alignment)
}
testStruct()
打印結(jié)果:
0x000000000000000a 0x000000000000000a 0x0000000000000001
17
24
8
Program ended with exit code: 0
類
類的定義和結(jié)構(gòu)體是相似的,但是編譯器并沒有為類自動生成可以傳入成員值的初始化器.
class Point {
var x: Int = 0
var y: Int = 0
func test() { }
}
var p1 = Point()//正確
var p2 = Point(x: 10, y: 20)//錯誤
var p3 = Point(x: 10)//錯誤
var p4 = Point(y: 20)//錯誤
對比結(jié)構(gòu)體
struct Point {
var x: Int = 0
var y: Int = 0
func test() { }
}
var p1 = Point()//正確
var p2 = Point(x: 10, y: 20)//正確
var p3 = Point(x: 10)//正確
var p4 = Point(y: 20)//正確
結(jié)構(gòu)體和類的本質(zhì)區(qū)別
結(jié)構(gòu)體和枚舉都是值類型
類是引用類型(指針類型)
class Size {
var width = 1
var height = 2
}
struct Point {
var x = 3
var y = 4
}
func test() {
var size = Size()
var point = Point()
}
問題:函數(shù)調(diào)用時,內(nèi)存在哪里?
0x10000就是point結(jié)構(gòu)體變量的內(nèi)存地址
0x10008就是point結(jié)構(gòu)體中y的內(nèi)存地址
size是指針變量,占用8個字節(jié),放在zhan里.
注:上圖指針針對的是64bit環(huán)境
對象的堆空間申請過程
??怎么看有木有在堆空間呢?
在swift里面就看有木有調(diào)用alloc叮盘、malloc這些函數(shù).
func testClassAndStruct() {
class Size {
var w = 1
var h = 2
}
struct Point {
var x = 3
var y = 4
}
var s = Size()
var p = Point()
}
在swift中,創(chuàng)建類的實(shí)例對象,要向堆空間申請內(nèi)存,大概流程如下:
- Class.__allocating_init()
- libswiftCore.dyld:swift_allocObject
- libswiftCore.dyld:swift_slowAlloc
- libsystem_malloc.dylib:malloc
class Point {
var x = 3
var y = 4
var b = true
}
var p = Point()
class_getInstanceSize(type(of:p))// 40
class_getInstanceSize(Point.self)// 40
func testClassAndStruct() {
class Size {
var w = 1
var h = 2
}
struct Point {
var x = 3
var y = 4
}
print("MemoryLayout<Size>.stride",MemoryLayout<Size>.stride)
print("MemoryLayout<Point>.stride",MemoryLayout<Point>.stride)
print("-------------------------")
var s = Size()
print("s變量的內(nèi)存地址:",Mems.ptr(ofVal: &s))
print("s變量的內(nèi)存的內(nèi)容:",Mems.memStr(ofVal: &s))
print("-------------------------")
print("s所指向的內(nèi)存地址:",Mems.ptr(ofRef: s))
print("s所指向的內(nèi)存的內(nèi)容:",Mems.memStr(ofRef: s))
print("-------------------------")
var p = Point()
print("p變量的內(nèi)存地址:",Mems.ptr(ofVal: &p))
print("p變量的內(nèi)存的內(nèi)容:",Mems.memStr(ofVal: &p))
}
打印結(jié)果:
MemoryLayout<Size>.stride 8
MemoryLayout<Point>.stride 16
-------------------------
s變量的內(nèi)存地址: 0x00007ffeefbff4a0
s變量的內(nèi)存的內(nèi)容: 0x00000001006060e0
-------------------------
s所指向的內(nèi)存地址: 0x00000001006060e0
s所指向的內(nèi)存的內(nèi)容: 0x0000000100008298 0x0000000200000002 0x0000000000000001 0x0000000000000002
-------------------------
p變量的內(nèi)存地址: 0x00007ffeefbff480
p變量的內(nèi)存的內(nèi)容: 0x0000000000000003 0x0000000000000004
Program ended with exit code: 0
由此可見:
1秧廉、s變量的內(nèi)存地址0x00007ffeefbff4a0
怒竿、p變量的內(nèi)存地址0x00007ffeefbff480
相鄰,相差16個字節(jié);
2、0x00000001006060e0
這個就是堆空間對象的地址;
3湃望、MemoryLayout<Size>.stride
為8,s所指向的內(nèi)存的內(nèi)容為32?
4、如何知道一個對象創(chuàng)建出來占用多少堆空間地址?
var ptr = malloc(16)
print(malloc_size(ptr))// 16
小常識:在Mac痰驱、iOS中的malloc函數(shù)分配的內(nèi)存大小總是16的倍數(shù)
通過class_getInstanceSize可以得知類的對象真正使用的內(nèi)存大小
來一個問題??: 結(jié)構(gòu)體內(nèi)存一定在棧里嗎?
不一定,要看你的結(jié)構(gòu)體變量在哪里定義的.
- 如果結(jié)構(gòu)體變量是在函數(shù)里定義,它的內(nèi)存在椫ぐ牛空間.
如:
func test() {
var p = Ponit()//結(jié)構(gòu)體
}
- 如果結(jié)構(gòu)體變量是在外邊定義的,那它的內(nèi)存就在數(shù)據(jù)段(全局區(qū))是一個全局變量.
如:
var p = Ponit()//結(jié)構(gòu)體
class Size {
var w = 1
var h = 2
}
- 如果結(jié)構(gòu)體變量在一個類里面創(chuàng)建,那它的內(nèi)存就在堆空間.
如:
class Size {
var w = 1
var h = 2
var p = Ponit()//結(jié)構(gòu)體
}
思考??:
class Size {
var w = 1
var h = 2
func test() {
var p = Ponit()//結(jié)構(gòu)體
}
}
上面這個結(jié)構(gòu)體變量p內(nèi)存地址在哪里?
聯(lián)想一下: 類的內(nèi)存在哪里呢?
無論在哪里創(chuàng)建對象,這個對象的內(nèi)存一定在堆空間.只不過指針變量的內(nèi)存在的位置不一定.函數(shù)里面定義就在棧空間,函數(shù)外邊定義就在(數(shù)據(jù)段)全局區(qū).
值類型
值類型賦值給var萄唇、let或者給函數(shù)傳參,是直接將所有內(nèi)容拷貝一份(深copy).
func testValueType() {
struct Point {
var x: Int
var y: Int
}
var p1 = Point(x: 10, y: 20)
var p2 = p1
p2.x = 11
p2.y = 21
print("1111")
}
匯編觀察上面的值類型
mov qword ptr [rbp - 0x10], rax // rbp - 0x10 0x1000 p1的內(nèi)存地址
mov qword ptr [rbp - 0x8], rdx // rbp - 0x8 0x1008
mov qword ptr [rbp - 0x20], rax // rbp - 0x20 p2的內(nèi)存地址
mov qword ptr [rbp - 0x18], rdx // rbp - 0x18
mov qword ptr [rbp - 0x20], 0xb // 11 給了p2
mov qword ptr [rbp - 0x18], 0x15 // 22
全局定義
struct Point {
var x: Int
var y: Int
}
var p1 = Point(x: 10, y: 20)
var p2 = p1
p2.x = 11
p2.y = 21
print("1111")
匯編觀察上面的值類型
mov qword ptr [rip + 0x57e9], rax 0x1000019e7 + 0x57e9 0x1000071D0 p1的內(nèi)存地址
mov qword ptr [rip + 0x57ea], rdx 0x1000019ee + 0x57ea 0x1000071D8
0x1000019ff <+79>: mov rax, qword ptr [rip + 0x57ca] // 10
0x100001a06 <+86>: mov qword ptr [rip + 0x57d3], rax // 0x1000071E0 p2的內(nèi)存地址
0x100001a0d <+93>: mov rax, qword ptr [rip + 0x57c4] // 20
0x100001a14 <+100>: mov qword ptr [rip + 0x57cd], rax // 0x1000071E8
0x100001a1b <+107>: lea rdi, [rbp - 0x18]
規(guī)律:局部變量的內(nèi)存格式 [rbp - 0x10],全局變量的內(nèi)存格式 [rip + 0x57e9]
全局變量:整個程序運(yùn)行過程中,只存在一份內(nèi)存;
值類型的賦值操作
栗子1
var s1 = "Jack"
var s2 = s1
s2.append("_rose")
print(s1)//Jack
print(s2)//Jack_rose
栗子2
var a1 = [1,2,3]
var a2= a1
a2.append(4)
a1[0] = 2
print(a1)// [2,2,3]
print(a2)// [1,2,3,4]
栗子3
var d1 = ["max":10, "min":21]
var d2= d1
d1["other"] = 7
d1["max"] = 12
print(d1)// ["other:12, ""max":10, "min":21]
print(d2)// ["max":12,"min": 21]
在swift標(biāo)準(zhǔn)庫中,為了提升性能,String檩帐、Array、Dictionary另萤、Set采用了Copy On Write.
比如僅當(dāng)有“寫”操作時,才會真正執(zhí)行拷貝操作
對于標(biāo)準(zhǔn)庫值類型的賦值操作,Swift能確保最佳性能,所以沒必要為了保證性能來避免賦值.
struct Point {
var x: Int
var y: Int
}
var p1 = Point(x: 10,y: 20)
p1 = Point(x: 20,y: 30)
留意: 這個操作修改的還是p1原來的內(nèi)存.
引用類型
引用賦值給var 湃密、let 或者給函數(shù)傳參,是將內(nèi)存地址拷貝一份(有點(diǎn)向快捷方式的概念),屬于淺拷貝.
class Point {
var x: Int
var y: Int
init(x: Int, y: Int) {
self.x = x
self.y = y
}
}
func test() {
var p1 = Point(x: 10,y: 20)
p1 = p2
}