Swift难点-继承中的构造规则实例详解

一、两种构造器-指定构造器和便利构造器
指定构造器:类中必备的构造器,为所有的属性赋初值。(有些子类可能不需要显示声明,因为默认从基类继承了)
便利构造器:类中的辅助构造器,通过调用指定构造器为属性赋初值。(仅在必要的时候声明)
举例

[plain] view plaincopy

  1. class Food {
  2. var name: String
  3. init(name: String) {
  4. self.name = name
  5. }
  6. convenience init() {
  7. self.init(name: "[Unnamed]")
  8. }
  9. }

便利构造器通过convenience关键字声明,可以看到,便利构造器是通过调用指定构造器来进行构造的。这也就是一个关键的概念:横向代理。
何为代理:就是让别人帮你干活
二、构造过程中的规则
(一)构造器链就是调用构造器的顺序
规则如下:
1.1、指定构造器必须调用其父类的指定构造器
1.2、便利构造器必须调用同一类中的指定构造器
1.3、便利构造器必须最后以调用一个指定构造器而结束
总得来说一句话:便利构造器横向代理,指定构造器向上代理。
举个例子:

[plain] view plaincopy

  1. class Base{
  2. var baseVar:String
  3. init(baseInput:String){
  4. baseVar = baseInput
  5. }
  6. convenience init(){
  7. self.init(baseInput:"")
  8. }
  9. }
  10. class Sub:Base{
  11. var subVar:String;
  12. init(subInput:String,baseInput:String){
  13. subVar = subInput
  14. super.init(baseInput:baseInput)//这里是规则1.1
  15. }
  16. convenience init(conSubInput:String){
  17. self.init(subInput:conSubInput,baseInput:"")//这里是规则1.2
  18. }
  19. convenience init(){
  20. self.init(conSubInput:"")//这里是规则1.3,因为调用了另外一个便利构造器,而另外一个便利构造器以调用指定构造器结束
  21. }
  22. }

(二)关于构造器的继承与重载
swift中,子类不会默认继承父类的构造器。
构造器的重载遵循构造器链的规则(1.1-1.3)
构造器的继承规则如下:
2.1、如果子类中没有定义任何指定构造器,将会自动继承所有父类的指定构造器
2.2、如果子类中提供了所有父类指定构造器,不管是通过规则2.1继承来的,还是自定义实现的,它将继承所有父类的便利构造器。
注意:子类可以通过部分满足规则2.2的方式,使用子类便利构造器来实现父类的指定构造器。
例子一:

[plain] view plaincopy

  1. class Base{
  2. var baseVar:String
  3. init(baseInput:String){
  4. baseVar = baseInput
  5. }
  6. convenience init(){
  7. self.init(baseInput:"basevar")
  8. }
  9. }
  10. class Sub:Base{
  11. var subVar:String = "subvar";
  12. }

这里子类没有定义任何构造器,所以满足规则2.1,2.1,将继承所有父类的指定构造器和便利构造器
所以可以这么调用

[plain] view plaincopy

  1. var instance1 = Sub()
  2. var instance2 = Sub(baseInput:"newBaseVar")

例子二

[plain] view plaincopy

  1. class Base{
  2. var baseVar:String
  3. init(baseInput:String){
  4. baseVar = baseInput
  5. }
  6. init(firstPart:String,secondPart:String){
  7. baseVar = firstPart + secondPart
  8. }
  9. convenience init(){
  10. self.init(baseInput:"basevar")
  11. }
  12. }
  13. class Sub:Base{
  14. var subVar:String;
  15. init(subInput:String,baseInput:String){
  16. subVar = subInput
  17. super.init(baseInput)
  18. }
  19. }

这里,子类只是实现了父类的一个构造器,所以并未继承便利构造器,也没有继承另外一个指定构造器
只可以这么创造实例

[plain] view plaincopy

  1. var instance = Sub(subInput:"subvar",baseInput:"basevar")

(三)基于上述两个规则,构造过程分为两个部分
阶段一

  • 某个指定的构造器或者便利构造器被调用;
  • 完成新实例的内存分配(此时内存尚未初始化);
  • 指定构造器确保其引入的所有存储属性已经赋值(存储属性极其所属内存完成初始化);
  • 指定构造器调用父类构造器(父类构造器属性初始化);
  • 这个调用父类的构造器沿着构造器链一直向上,直到最顶部。(确保所有的继承的基类过程都已经初始化)。

阶段二

  • 从顶部一直向下,每个构造器链中类指定的构造器都有机会进一步定制实例,构造器此时可以访问self,修改它的属性并且调用实例方法等等。
  • 最终,任意构造器的便利构造器将有机会定制实例和使用self。

可能这个规则有点抽象,举个例子就明白了

[plain] view plaincopy

  1. class Base{
  2. var baseVar:String
  3. init(baseInput:String){
  4. baseVar = baseInput
  5. }
  6. }
  7. class Sub:Base{
  8. var subVar:String;
  9. func subPrint(){
  10. println("现在可以调用实例方法了")
  11. }
  12. init(subInput:String,baseInput:String){
  13. subVar = subInput
  14. super.init(baseInput:baseInput)
  15. //这里就完成了阶段一
  16. self.subVar = subInput + "123"//此时可以调用self
  17. subPrint()//此时也可以调用实例方法了
  18. }
  19. }

总得来说:当类的实例的内存被初始化完成,也就是调用super.init()之后,就完成了阶段一了。

三、编译器的安全检查
检查一

  指定构造器必须在它所在类的属性先初始化完成后才能把构造任务向上代理给父类中的构造器。简单来说,就是先初始化自己的存储属性,在调用父类的super.init向上初始化
检查二
  指定构造器必须先向上调用父类构造器,在为继承来的属性赋初值。这个很简答,假设继承来个x,你先为x赋值为1了,而在调用父类构造器,父类构造器会为x赋另外一个初值来保证初始化过程完成,那么你赋值的1就被覆盖了
检查三
  便利构造器先调用同类中其他构造器,再为任意属性赋初值。和检查二类似,也是防止被覆盖
检查四
  构造器在第一阶段完成之前,不能饮用self,不能调用任何实例属性,不能调用实例方法

四、总结一下
指定构造器的过程是这样的
1、为自己的属性赋初值
2、调用基类构造器(super.init)
3、然后就可以调用self,和实例方法,存储属性。定制新的值了。
然后,我们看下官方文档里给出的一个比较好的例子

[plain] view plaincopy

  1. class Food {
  2. var name: String
  3. init(name: String) {
  4. self.name = name
  5. }
  6. convenience init() {
  7. self.init(name: "[Unnamed]")
  8. }
  9. }
  10. class RecipeIngredient: Food {
  11. var quantity: Int
  12. init(name: String, quantity: Int) {
  13. self.quantity = quantity
  14. super.init(name: name)
  15. }
  16. override convenience init(name: String) {
  17. self.init(name: name, quantity: 1)
  18. }
  19. }
  20. class ShoppingListItem: RecipeIngredient {
  21. var purchased = false
  22. var description: String {
  23. var output = "\(quantity) x \(name.lowercaseString)" {
  24. output += purchased ? " YES" : " NO"
  25. return output
  26. }
  27. }

这个构造器链的关系如图

解释

  • 基类Food定义了一个指定构造函数和一个便利构造器
  • 子类RecipeIngredient实现了基类Food所有的指定构造器,所以它继承了基类的便利构造器
  • 子类ShoppingListItem没有定义构造器,所以继承了基类RecipeIngredient的所有构造器。Swift入门系列15-继承中的构造规则(难点)
时间: 2024-10-11 17:00:25

Swift难点-继承中的构造规则实例详解的相关文章

Swift难点-继承中的构造规则实例具体解释

关于继承中的构造规则是一个难点. 假设有问题,请留言问我. 我的Swift新手教程专栏 http://blog.csdn.net/column/details/swfitexperience.html 为什么要有构造器:为类中自身和继承来的存储属性赋初值. 一.两种构造器-指定构造器和便利构造器 指定构造器:类中必备的构造器.为全部的属性赋初值.(有些子类可能不须要显示声明,由于默认从基类继承了) 便利构造器:类中的辅助构造器,通过调用指定构造器为属性赋初值.(仅在必要的时候声明) 举例 cla

C++--继承中的构造与析构、父子间的冲突

一.继承中的构造与析构 Q:如何初始化父类成员?父类构造函数与子类构造函数由什么关系?A.子类对象的构造1.子类在可以定义构造函数2.子类构造函数--必须对继承而来的成员进程初始化(直接通过初始化列表或者赋值的方式进行初始化,调用父类构造函数进行初始化)B.父类构造函数在子类中的调用方式1.默认调用--适用于无参构造函数和使用默认参数的构造函数2.显示调用--通过初始化列表进行调用,适用于所有父类构造函数代码示例 #include <iostream> #include <string&

C++ 类的继承三(继承中的构造与析构)

//继承中的构造与析构 #include<iostream> using namespace std; /* 继承中的构造析构调用原则 1.子类对象在创建时会首先调用父类的构造函数 2.父类构造函数执行结束后,执行子类的构造函数 3.当父类的构造函数有参数时,需要在子类的初始化列表中显示调用 4.析构函数调用的先后顺序与构造函数相反 继承与其他类做成员变量混搭的情况下,构造和析构嗲用原则 1.先构造父类,在构造成员变量,最后构造自己 2.先析构自己,再析构成员变量,最后析构父类 */ clas

C++--第16课 - 继承中的构造与析构

第16课 - 继承中的构造与析构 1. 赋值兼容性原则 (1)      子类对象可以当作父类对象使用. (2)      子类对象可以直接赋值给父类对象. (3)      子类对象可以直接初始化父类对象. (4)      父类指针可以直接指向子类对象. (5)      父类引用可以直接引用子类对象. 子类就是特殊的父类 #include <cstdlib> #include <iostream> using namespace std; class Parent { pro

C#中Serializable序列化实例详解

本文实例讲述了C#中Serializable序列化.分享给大家供大家参考.具体分析如下: 概述: 序列化就是是将对象转换为容易传输的格式的过程,一般情况下转化打流文件,放入内存或者IO文件 中.例如,可以序列化一个对象,然后使用 HTTP 通过 Internet 在客户端和服务器之间传输该对象,或者和其它应用程序共享使用.反之,反序列化根据流重新构造对象. 一.几种序列化技术 1)二进制序列化保持类型保真度,这对于在应用程序的不同调用之间保留对象的状态很有用.例如,通过将对象序列化到剪贴板,可在

RabbitMQ实例详解+Spring中的MQ使用

RabbitMQ实例详解 消息队列中间件是分布式系统中重要的组件,主要解决应用解耦,异步消息,流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构. Queue Queue(队列)是RabbitMQ的内部对象,用于存储消息,用下图表示. RabbitMQ中的消息都只能存储在Queue中,生产者(下图中的P)生产消息并最终投递到Queue中,消费者(下图中的C)可以从Queue中获取消息并消费. 多个消费者可以订阅同一个Queue,这时Queue中的消息会被平均分摊给多个消费者进行处理,而不

C#中string用法实例详解

在进行C#程序设计时,用的最多的莫过于string了,但有些时候由于不仔细或者基础的不牢固等因素容易出错,今天本文就来较为详细的总结一下C#中string的用法.具体如下: 1.string是一个引用类型,平时我们比较string对象,比较的是对象的值而不是对象本身 如下面代码所示: string strA="abcde"; string strB="abc"; string strC="de"; Console.WriteLine(strA =

实例详解 EJB 中的六大事务传播属性--转

前言 事务 (Transaction) 是访问并可能更新数据库中各种数据项的一个程序执行单元 (unit).在关系数据库中,一个事务可以是一条或一组 SQL 语句,甚至整个程序.它有通常被称为 ACID 的原子性(Atomicity).一致性(Consistency).隔离性(Isolation).持续性(Durability)四大特性: 原子性(Atomicity):一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做. 一致性(Consistency):事务必须是使数据库

PHP中__get()和__set()的用法实例详解

PHP中__get()和__set()的用法实例详解 在PHP5中,预定义了两个函数“__get()”和“__set()”来获取和赋值其属性,对每个字段进行set和get的操作.只需要加上两个魔术方法即可 php面向对象_get(),_set()的用法 一般来说,总是把类的属性定义为private,这更符合 现实的逻辑.但是,对属性的读取和赋值操作是非常频繁的,因此在PHP5中,预定义了两个函数“__get()”和“__set()”来获取和赋值其属 性.类似于java中的javabean的操作,