认识Swift系列6之结构体和类

2019-07-11  本文已影响0人  Niuszeng
    // 1.结构体
    func test_struct() {
        // 在swift标准库中,绝大数公开的类都是结构体,枚举和类只占一小部分
        // Bool、Int、Double、String、Array、Dictionary等常见类型都是结构体
        
        struct Date {
            var year: Int
            var month: Int
            var day: Int
            
            var stringDesc: String {
                get {
                    return String(format: "%04d-%02d-%02d", year, month, day)
                }
            }
        }
        // 编译器会默认为结构体生成一个初始化器,参数包含所有的存储属性
        let date = Date(year: 2019, month: 7, day: 11)
        print("Date is \(date)")
        print("Datedesc is \(date.stringDesc)")
        
        // 编译器根据情况自动为结构体生成不同的的初始化器
        // 具体情况视成员属性而定,宗旨:所有存储属性创建出来都能被初始化
        struct Point1 {
            var x: Int
            var y: Int
            // 自动生成(保证x和y能被初始化)
            // Point1(x: <#T##Int#>, y: <#T##Int#>)
        }
        let p1 = Point1(x: 1, y: 1)
        
        struct Point2 {
            var x: Int = 0
            var y: Int
            // 自动生成(保证y能被初始化)
            // Point1(x: <#T##Int#>, y: <#T##Int#>)
            // Point2(x: <#T##Int#>)
        }
        let p21 = Point2(x: 2, y: 2)
        let p22 = Point2(y: 2)
        
        struct Point3 {
            var x: Int = 0
            var y: Int = 0
            // 自动生成(x和y都已经初始化了,不许要在保证,因此可以生成一下几个构造方法)
            // Point3()
            // Point3(x: <#T##Int#>, y: <#T##Int#>)
            // Point3(x: <#T##Int#>)
            // Point3(y: <#T##Int#>)
        }
        let p31 = Point3()
        let p32 = Point3(x: 2, y: 2)
        let p33 = Point3(x: 2)
        let p34 = Point3(y: 3)
        
        // 当自己定义了初始化函数后,编译器不会再自动生成其它初始化函数
        struct Point4 {
            var x: Int = 0
            var y: Int = 0
            
            // Point4(x: <#T##Int#>, y: <#T##Int#>)
            init(x: Int, y: Int) {
                self.x = x
                self.y = y
            }
            
            // 由于自定义了初始化器,以下三个构造器编译器不会生成
            // Point4()
            // Point4(x: <#T##Int#>)
            // Point4(y: <#T##Int#>)
        }
        let p41 = Point4(x: 2, y: 2)
        
        // 初始化成员值和构造器的本质
        /**
         事实上,虽然Test1中在定义成员的时候给了初始值,但a和b发生初始化的时机在init方法中
         即Test1中会自动生成类似Test2中的init方法,Test1和Test2完全等价
         */
        struct Test1 {
            var a: Int = 0
            var b: Int = 0
        }
        let t1 = Test1()
        struct Test2 {
            var a: Int
            var b: Int
            init() {
                self.a = 0
                self.b = 0
            }
        }
        let t2 = Test2()
    }
    test_struct()
    
    // 2.类
    func test_class() {
        // 类在初始化成员值时的原理和结构体相同,都是在无参init方法中
        // 默认情况下,编译器不会帮类生成初始化构造器
        /**
         class Size1 {
            var w: Int
            var h: Int
            // 注意:该类没有任何构造器
         }
         */
        
        /**
         class Size2 {
            var w = 10
            var h: Int
         }
         */
        
        // 当且仅当类的所有成员都有初始值的时候,编译器才会为类生成无参构造方法
        class Size3 {
            var w = 10
            var h = 20
        }
        let s3 = Size3()
        
        // 同结构体,一下两段代码完全等效[Size4 和 Size5 等效]
        class Size4 {
            var a: Int = 0
            var b: Int = 0
        }
        let s4 = Size4()
        class Size5 {
            var a: Int
            var b: Int
            init() {
                self.a = 0
                self.b = 0
            }
        }
        let s5 = Size5()
    }
    test_class()
    
    // 3.结构体和类的本质区别
    func test_struct_class() {
        // (1)结构和枚举:值类型
        // 创建对象,在哪里创建对象,就在哪里开辟对象需要的内存空间
        
        // (2)类:引用类型(指针类型)
        // 创建对象,对象需要的内存通常开辟堆空间,而在创建的地方使用一个指针指向该堆空间
        
        // 值类型
        struct sPoint1 {
            var x = 10
            var y = 30
        }
        let sp1 = sPoint1()
        /**
         sp1 的结构
         ┏━━━━━━━┓
         ┃ x(10) ┃
         ┃ y(30) ┃
         ┗━━━━━━━┛
         如果在函数内部创建sp1,则在栈空间开辟 16个字节
         如果在全局区创建sp1,则在数据段开辟 16个字节
         如果sp1是类的成员变量,则在堆空间开辟 16个字节
         */
        print("sPoint1 sp1 size:\(MemoryLayout.size(ofValue: sp1))")
        
        class cPoint1 {
            var x = 10
            var y = 30
        }
        let cp1 = cPoint1()
        /**
         cp1 的结构
         ┏━━━━━━━┓
         ┃ cp1   ┃     
         ┃(指针)  ------> 堆空间
         ┗━━━━━━━┛     ┏━━━━━━━━━━━━━┓
                       ┃ classInfo   ┃
                       ┃ retainCount ┃
                       ┃    x(10)    ┃
                       ┃    y(30)    ┃
                       ┗━━━━━━━━━━━━━┛
         这里,cp1是一个指针,同样,cp1可能在栈空间、堆空间、全局区
         但是 cp1指向的对象,一定在堆空间
         因此cp1占用8个字节,cp1指向的对象占用32个字节
         */
        print("cPoint1 cp1 size:\(MemoryLayout.size(ofValue: cp1))")
        print("cPoint1 cp1 obj size:\(class_getInstanceSize(type(of: cp1)))")
        
        // (3)值类型的赋值(实际上是深拷贝:Copy On Write)
        let sp2 = sp1 // 这里实际上会将sp1的内存拷贝一份到新的sp2空间
        var sp3 = sp1 // 同上
        // 不过在swift标准库中,为了提升性能,String、Array、Dictionary、Set等采取了Copy On Write技术
        // 该技术意思是,只有被拷贝的副本真正需要执行写(修改)操作的时候,才会进行深拷贝操作
        // 不过该操作只针对swift标准库类型
        // 所以在我们不需要修改类型时,尽量使用let定义,这样编译器直接不会进行深拷贝操作
        
        // 注意:
        var sp4 = sPoint1(x: 1, y: 2)
        sp4 = sPoint1(x: 3, y: 5)
        /**
         sp4 的结构
         ┏━━━━━━━━━━━━━━━━━┓         ┏━━━━━━━━━━━━━━━━━┓
         ┃0x1000000 x(1)   ┃ ------> ┃0x1000000 x(3)   ┃
         ┃0x1000008 y(2)   ┃         ┃0x1000008 y(5)   ┃
         ┗━━━━━━━━━━━━━━━━━┛         ┗━━━━━━━━━━━━━━━━━┛
         //该操作实际上类似于深拷贝,sp4不会开辟新空间,而是值覆盖
         */
        
        // (4)引用类型的赋值(浅拷贝,即拷贝内存地址:指针地址)
        let cp2 = cp1
        // 该操作只会让cp2拥有和cp1同样的指针地址
        // 即两个指针指向同一个对象,虽然cp2拥有cp1相关信息,但并不拥有独立空间
    }
    test_struct_class()
    
    
    // 4.let 对值类型和引用类型的区别
    func test_let_for_struct_class() {
        struct Point {
            var x = 1
            var y = 1
        }
        class Size {
            var x = 1
            var y = 1
            
            init(x: Int, y: Int) {
                self.x = x
                self.y = y
            }
        }
        
        let p = Point(x: 5, y: 4)
        // p.x = 8 // 报错
        
        let s = Size(x: 5, y: 4)
        s.x = 8
        /** 对比可知
         对于let修饰的:
           如果是值类型,那么该对象本身和内部成员都不可修改
           如果是引用类型,那么指针指向不可修改,但是指向的对象可以修改
         
         可以粗暴的这样理解
            对于值类型 p 被 let 修饰,由于p的成员都在let之后,则不可修改
            对于引用类型 s 被 let 修饰,s的在let之后,但s指向的对象却在另一个空间,不归let管
         */
    }
    test_let_for_struct_class()
    
    
    // 5.嵌套类型和方法
    func test_nest_func() {
        // 嵌套
        struct A {
            enum A1 {
                case a11, a12, a13, a14
            }
            enum A2: Int {
                case a21 = 2, a22, a23, a24
            }
        }
        var a1 = A.A1.a13
        a1 = .a14
        
        // 类、结构体、枚举 都可以定义方法
        class Size {
            var w = 10
            var h = 10
            
            func show(){
                print("Size w = \(w), h = \(h)")
            }
        }
        struct Point {
            var x = 10
            var y = 10
            
            func show(){
                print("Point w = \(x), h = \(y)")
            }
        }
        enum TestEnum {
            case a1, a2, a3
            func show(){
                print("A is \(self)")
            }
        }
        
        let s = Size()
        s.show()
        
        let p = Point()
        p.show()
        
        let e = TestEnum.a1
        e.show()
    }
    test_nest_func()
上一篇 下一篇

猜你喜欢

热点阅读