swift 笔记 (十四) —— 构造过程

构造过程

为了生成类、结构体、枚举等的实例,而做的准备过程,叫做构造过程。 为了这个过程,我们通常会定义一个方法来完成,这个方法叫做构造器。当然它的逆过程,叫做析构器,用于在实例被释放前做一些清理工作以及一此自定义化的处理。

为存储型属性设置初始值

类和结构体在生成实例那一刻,必须为所有的属性赋以特定的初始值。

要么在定义存储型属性的时候直接给个初始值,否则就必须在构造器里面指定一个初始值。

上面说的这两种情况,都不会触发存储型属性的监听者行为(property observer)。

struct MyClass {

var number: Int

init () {          //这个就是构造器,它没有返回值

number = 1     //为存储型属性指定一个初始值

}

}

或者

struct MyClass {

var number: Int = 1   //直接在存储型属性定义的时候,指定一个初始值

}

还可以自定义构造器,为它传入参数,然后对属性做相应的初始化:

struct MyClass {

var number: Int

init (num: Int) {          //这个就是构造器,它没有返回值

number = num     //为存储型属性指定一个初始值

}

init (num: Int, byTimes: Int) {     //可以定义多个构造器

number = num * byTimes

}

}

let myInstance1 = MyClass(num:1)          //使用第一种构造器生成一个实例

let myInstance2 = MyClass(num:10, byTimes:3)     //使用第二种构造器生成另一个实例

注意:这里与之前的类方法稍有不同,swift会为每个参数都提供一个外部使用名,而这个行为在类方法中,是从第二个参数开始才有的(笔记十一中有提到)。当然你可以指定一个外部使用名,来替代swift的默认行为(“_”依然可以禁止swift提供外部使用名)。

可选类型属性

如果我们在定义可选类型属性的时候没有赋值,并且,在构造器中也没有赋值的话,swift为我们将这个属性的值设置为nil。

常量属性

在构造过程结束之前,常量属性的值是可以随意修改的(当然要用同样类型的值)。

注意:子类是不可以修改父类的常量属性的!

默认构造器

swift会为那些没有构造器,并且所有属性都有初值的类,提供一个默认构造器(编译器做的事,在swift代码中看不见),上面已经出现过这样的一个类了:

struct MyClass {

var number: Int = 1

}

这个类中,我们并没有指定一个构造器,并且number属性也有初始值,那么swift就会提供一个默认的构造器,使得number属性确实等于1......(对于新手来说,这可能很难理解,就先这么记吧,因为这块涉及到内存分配的问题,暂且不展开记录,后继等把手册全都看完,我还想写个对swift中的各种类型变量做个内存模型的分析的系列笔记,到时候再来说这个问题。)

结构体的属性列表构造器(Memberwise Initializers for Structure Types)

在结构体中,除了上面说过的默认构造器外,swift会提自动接受一个”所有用于存储的属性带有初始值的列表“的构造器,前提是我们没有为struct写自定义构造器。

struct MyStruct {

var name = “1”

var number = 2

}

let myStructInstance = MyStruct(name: “Hello”, number:100)

这相当于提供了一个 init(name:String, number:Int) 的构造器,但这个行为是swift帮我们做的。

值类型的构造器代理

构造器还能调用其它的构造器作为构造过程的一部分。这个过程,叫做构造器代理。

值类型(结构体,枚举)并不支持继承,所以,他们的构造器代理与类的构造器代理的规则和形式有所区别。

值类型的构造器相对简单,因为类会继承很多父类的存储型属性,类就有责任在构造过程中保证它继承来的属性被正确的初始化(这个等会再说)。

先来看一个官方的例子:

这个例子中,有三个构造器,第一个是个空的{},当这个构造器被调用的时候:

let myRect = Rect()

origin会是 (0.0, 0.0), size会是 (0.0, 0.0),全部都是初始值。

第三个构造器中(第12行)调用了第二个构造器,这种方法,可以减少第三个构造器的代码,如果没有这种方式的话,那么在这个位置,我们就要把第二个构造器的内容复制一份,放在这里(这显然是不好的)。

类继承和构造过程

switf为类提供了两种构造器,以确保所有的属性都能被值始化:指定构造器 和 便利构造器

//吐槽:原本为了方便交流而定义了各种各样的名字,结果在上手学习的时候,却造成了极大的障碍,不知道这算不算是得不尝失。但既然已经有人给各种各样的调用方式都起了名字,并且又出现在官方文档里,那就逆来顺受吧,只要我们知道,其实这玩意就是那么回事。。。就行了

这里的指定构造器和便利构造器在官方文档上说了一大堆的解释,各种名字概念天花乱坠,说得头都晕了,无非就是为了方便使用而封装了一层调用而已啊,绕晕有什么意思,还是暂时先放一下吧,一会儿再说。

构造器调用链

为了简化指定构造器和便利构造器的调用关系,swift指定了三条规则:

1. 构造器代理必须调用它的直接父类的指定构造器

2. 便利构造器必须调用它同一个类中定义的其它构造器

3. 便利构造器的结尾部分必须调用一个指定构造器

一个方便的记忆方法是(这是文档上的):

指定构造器必须总是向上代理

便利构造器必须总是横向代理

//吐槽:说得好听点是为了简化而搞出来的一堆并不简单的规则,说难听点不就是为了商业目的,降低门槛而搞出来的所谓自动推导而引出来的一系列的恶心问题嘛。。。。

向上代理的意思就是调用父类的指定构造器

横向代理的意思就是调用它自己类内的构造器

官方的图解:

每一个大的蓝色广块表示的是一个类,每一个小的蓝色广块(写着 Designated)代表指定构造器,每一个土黄色的方块(写着 Convenience)代表便利构造器。 这个图就是对上面三条规则的解释。

两段式的构造过程(Two-Phase Initialization)

swift中类,包含两个阶段。

第一阶段,每个存储型属性通过它们自己的类构造器来设置初始值。

第二阶段,每一个存储型属性都可以进一步的设置他们定制化的初始化。

我拿一小段代码来说明这个文档的SB之处(这段代码明显是错的):

int myNumA = 1

func myFunc() {

myNumA = myNumB          //在这里的时候 myNumB还没定义,也没初始化,明显是错的

}

int myNumB = 2          //myNumB定义+初始化

所谓的两段式构造过程,就是告诉我们,在使用myNumB之前,要确保它被定义并且被初始化了。。。。。于是,如果按着两段式构造过程,我们上面的代码正确写法应该是:

int myNumA = 1

int myNumB = 2

func myFunc() {

myNumA = myNumB

}

//看起来是不是很好笑。。。。用了一大堆的描述,就是想说明这样的问题。

构造器的继承和重写

swift中,子类不会默认的继承父类的构造器。但是我们偶尔会觉得父类的构造器的功能不够强大,这个时候就需要在子类中,重写构造器,以达到特定的目的。

如果重写的是一个指定构造器,写完我们的逻辑之后,记得要调用父类的那个构造器。

如果重写的是一个便例构造器,必须要调用这个类中的其它指定构造器。

构造器的重写,与属性、方法以及下标的重写不同,我们不需要在前面写 override关键字。

自动构造器的继承

在之前的笔记中以及文档中,一直在说,swift不会默认的继承父类的构造器,但实际上,很多例子已经说明可以自动继承了(为了避免误解与暴乱,扔白菜之类的事情发生,我一直都没说。。。:))

swift的构造器继承机制,要满足以下两点:

1. 如果子类没有定义任何指定构造器,它将会自动继承所有父类的指定构造器。

2. 如果子类中定义了所有父类的指定构造器的实现(通过规则1 或是 自己实现),它将会自动继承所有父类的例利构造器。

指定构造器及便利构造器的语法

指定构造器:

init (参数) {

函数体

}

便利构造器:

convenience init(参数) {          //这里有个convenience关键字

函数体

}

只要知道convenience型的构造器,只能调用它自己这个类中的非convenience构造器就行了。

用闭包或者函数设置属性的默认值

class MyClass {

var myNumbers:Int[] = {

var tmpNum = Int[]()     //生成一个临时数组,用来在闭包最后给myNumbers赋值

for i in 1…10 {

tmpNum.append(i)     //添加元素

}

return tmpNum     //闭包的返回值

}()          //这个()一定不可以省,不然的话,myNumbers就成了闭包这个函数,而不是闭包的返回值了

func getNumbers() -> Int[] {

return myNumbers

}

}

let myInstance = MyClass()

myInstance.getNumbers()      //这条取到了MyClass的myNumbers数组

swift 笔记 (十四) —— 构造过程

时间: 2024-10-23 08:51:40

swift 笔记 (十四) —— 构造过程的相关文章

Swift学习笔记十四:构造(Initialization)

类和结构体在实例创建时,必须为所有存储型属性设置合适的初始值.存储型属性的值不能处于一个未知的状态. 你可以在构造器中为存储型属性赋初值,也可以在定义属性时为其设置默认值.以下章节将详细介绍这两种方法. 注意: 当你为存储型属性设置默认值或者在构造器中为其赋值时,它们的值是被直接设置的,不会触发任何属性观测器(property observers). 一.基本语法 class Human{ var name :String init(){ name = "human" } init(n

swift 笔记 (十六) —— 可选链

可选链(Optional Chaining) 我们都知道"可选型"是什么,那么可选链又是什么,举个例子解释一下: struct MyName{ var name } struct MyInfo { var myName:MyName? = MyName() } class MyClass { var structInstance: MyInfo? = MyInfo() } 这里有两个结构体和一个类,当,这个类实例化的时候: var myInstance = MyClass() 所有的可

swift 笔记 (十九) —— 协议

协议(Protocols) 协议仅是用定义某些任务或者是功能必须的方法和属性.类似于java里的interface的作用.但协议并不会实现具体的功能. 我猜这个名字源于OO中提到的"契约",但我并不觉得这名字很好,反而是interface这名字更容被接受,因为我觉得协议这个名字很容易和网络编程的时候的网络协议搞混,网络协议也通常简称为协议. 语法: protocol MyProtocol { //协议定义 } struct MyStruct: MyProtocol1, MyProtoc

swift 笔记 (十二) —— 下标

下标 swift允许我们为 类,结构体,枚举 定义下标,以更便捷的方式访问一大堆属性.比如Array和Dictionary都是结构体,swift的工程师已经为这两个类型提供好了下标操作的代码,所以,我们才可以通过 myArray[2]这种方式,读取和改写这个struct中保存的数据.而且,一个类型中可以定义多种下标访问方式(重载,关于重载,在后面的笔记中会提到,这里先不用太在意) 下标可以定义为"读写"型的,也可以定义为"只读"型,这种行为,是通过定义一组操作完成的

laravel3学习笔记(十四)

原作者博客:ieqi.net ==================================================================================================== 运行时配置 在 Laravel3 中很多地方我们都可以看到“约定大于配置”的影子,我本人也很喜欢这种工程哲学尤其是在框架领域,当然这并不能代替所有的配置.我们知道 Laravel3 中,主要配置都写在 application/config 文件夹下,在应用逻辑中,往往

Oracle基础笔记十四

第十四章 高级子查询 1.子查询 子查询 (内查询) 在主查询执行之前执行 主查询(外查询)使用子查询的结果 SELECT select_list FROM table WHERE expr operator (SELECT select_list FROM  table); 问题:查询工资大于149号员工工资的员工的信息 SELECT last_name FROM   employees WHERE  salary > (SELECT salary FROM   employees WHERE

SICP学习笔记及题解—构造过程抽象(三)

主要内容 高阶过程:以过程为参数和/或返回值的过程 lambda 表达式 let 表达式 用过程作为解决问题的通用方法 求函数的 0 点 求函数的不动点 返回过程值 过程是语言里的一等公民 (first-class object) 1.3.1高阶过程 过程是抽象,一个过程描述了一种对数据的复合操作,如求立方过程:(define (cube x) (* x x x)) 换个方式,也可以总直接写组合式:(* 3 3 3), (* x x x), 不定义过程,总基于系统操作描述,不能提高描述的层次,

Swift学习笔记十四

Deinitialization 当类的实例对象即将要被释放时,会立即调用deinitializer,通过deinit关键字来定义deinitializer,和initializer一样,它也只存在于类类型上. 当实例对象不再有用时,Swift会自动释放该对象.Swift通过自动引用计数(ARC)来管理实例内存.通常情况下,对象被释放时,你并不需要做特别的清理,但是,如果你操作了你自己的资源,可能需要做一些额外的清理工作.比如,你在某个类中打开了一个文件,并往里写入了数据,那么你需要在这个类的实

SICP学习笔记及题解---构造过程抽象(一)

有段时间没看这本书了. 而且在做笔记的时候产生了一些疑问,觉得这样照着书做笔记没什么意义.于是乎,改变了一下做法.改成先提出疑问,记下重点,然后结合实际案例学习相关东西,最后附上题解, ok,下面就是第一次的笔记.(依旧是旧套路的) 本节内容 l  讨论基本的Scheme语法规则 l  过程的定义 l  代换模型 l  条件表达式和谓词 l  过程抽象 l  与C语言比较 程序设计的基本元素 所有的高级的语言都会在把简单的认知组合起来形成复杂认识的方法上有独到之处.而且每个强有力的语言都为此提供