程序员iOS移动开发社区iOS Developer

08 Swift 继承与几大关键字\构造方法\继承与构造方法

2016-03-01  本文已影响454人  高俊

1. Swift继承\super关键字\override关键字\final关键字


继承语法

继承是面向对象最显著的特性,继承是从已经有的类中派生出新的类
新的类能够继承已有类的属性和方法,并能扩展新的能力
术语:基类(父类、超类), 派生类(子类、继承类)

格式:
    class 子类 : 父类{
    }
    继承优点 : 代码重用
    继承缺点 : 增加程序耦合性, 父类改变会影响子类
    注意 : Swift和OC一样没有多继承

class Man {

    var name: String = "gaojun"

    var age: Int = 10

    func sleep(){
        print("睡觉")
    }
}

class SuperMan: Man {

    var power: Int = 100

    func fly(){

        print("飞 \(name) \(age)")
    }
}

var m = Man()

m.sleep()

var sm = SuperMan()

sm.sleep()

sm.fly()

super关键字:

派生类中可以通过super关键字来引用父类的属性和方法

class Man1 {

    var name: String = "GAOJUN"

    var age: Int = 30

    func sleep(){
        print("睡觉")
    }

}

class SuperMan1: Man1 {

    var power: Int = 100

    func eat()
    {
        print("吃饭")
    }

    func fly(){

        // 子类可以继承父类的属性
        print("飞 \(super.name) \(super.age)")
    }

    func eatAndSleep()
    {
        eat()
    
        super.sleep()
    
        // 如果没有写super,那么先在当前类中查找,如果找不到再去父类中查找,如果写了super,会直接去父类中查找
    }
}

var sm1 = SuperMan1()

sm1.eatAndSleep()

方法重写: override

重写父类方法,必须加上override关键字

class Man2 {

    var name: String = "NICAI"

    var age: Int = 10

    func sleep(){
        print("父类睡觉")
    }
}

class SuperMan2: Man2 {

    var power: Int = 100

    /*
        override关键字主要是为了明确表示重写父类方法,
        所以如果要重写父类方法,必须加上override关键字
    */

    override func sleep() {
        // sleep() 不能这样写,会导致递归
        super.sleep()
        print("子类睡觉")
    }

    func eat()
    {
        print("吃饭")
    }

      func fly(){

        // 子类可以继承父类的属性
        print("飞 \(super.name) \(super.age)")
    }

    func eatAndSleep(){

        eat()
        sleep()
    }
}

var sm2 = SuperMan2()

sm2.sleep()

sm2.eatAndSleep()

重写属性

无论是存储属性还是计算属性,都只能重写为计算属性

class Man3 {

    var name: String = "2b"  // 存储属性

    var age: Int { // 计算属性
    
        get {
            return 30
        }
        set {
            print("man new age \(newValue)")
        }
    }

    func sleep(){

        print("睡觉")
    }

}

class SuperMan3: Man3 {

    var power: Int = 100

    // 可以将父类的存储属性重写为计算属性
    // 但是不可以将父类的存储属性又重写为存储属性,因为这 样没有意义

    override var name: String {

        get {
    
            return "gaojun"
        }
        set{
            print("SuperMan3 new name \(newValue)")
        }
    }

    // 可以将父类的计算属性重写为计算属性,同样不能重写为存储属性
    override var age: Int { // 计算属性
    
        get {
            return 30
        }
        set {
            print("superMan3 new age \(newValue)")
        }
    }
}

let sm3 = SuperMan3()

// 通过子类对象来调用重写的属性或者方法,肯定会调用子类中重写的版本

sm3.name = "xxxxx"

sm.age = 50

重写属性限制

1、读写计算属性/存储属性,是否可以重写为只读计算属性(权限变小)不可以
2、只读计算属性,是否可以在重写时变成读写计算属性?(权限变大)可以


class Man4 {

    var name: String = "sb" // 存储属性

    var age: Int { // 计算属性
    
        get {
            return 30
        }
        set {
            print("man new age \(newValue)")
        }
    }

    func sleep(){
        print("睡觉")
    }
}

class SuperMan4: Man4 {

    var power: Int = 100

    override var name: String{

        get {
            return "size"
        }
        set {
            print("SuperMan new name \(newValue)")
        }

    }

    override var age: Int { // 计算属性
    
        get {
    
            return 30
        }
        set {
    
            print("SuperMan4 new age \(newValue)")
        }

    }

}

重写属性观察器

值能给lazy属性的变量存储属性设定属性观察器
不能给计算属性设置属性观察器,给计算属性设置属性观察器没有意义
属性观察器限制:
    1、不能在子类中重写父类只读的存储属性
    2、不能给lazy的属性设置属性观察器

class Man5 {

    var name: String = "wocao"

    var age: Int = 0 {// 存储属性
    
        willSet {
            print("super new \(newValue)")
        }
        didSet {
              print("super new \(oldValue)")
        }
    }

    var height: Double {

        get {
    
            print("super get")
        
            return 10.0
        }
        set {
    
            print("super set")
        }

    }
}

class superMan5: Man5 {

    override var name: String {

        willSet {
    
            print("new \(newValue)")
        }
        didSet {
    
            print("old \(oldValue)")
        }

    }

    // 可以在子类中重写父类的属性观察器
    override var age: Int {

        willSet {
    
            print("child new \(newValue)")
        }
        didSet {
    
            print("child old \(oldValue)")
        }

    }

    // 可以在子类重写父类的计算属性为属性观察器
    override var height: Double{

        willSet {
    
            print("child height")
        }
        didSet {
    
            print("child height")
        }

    }
}

var m5 = superMan5()

m5.age = 55

print(m5.age)

m5.height = 20.0

final关键字

利用final关键字防止重写
final关键字既可以修饰属性,也可以修饰方法,并且还可以修饰类
被final关键字修饰的属性和方法不能被重写
被final关键字修饰的类不能被继承

final class Man6 {

    final var name: String = "enen"

    final var age: Int = 0 { // 存储属性
    
        willSet {
    
            print("super new \(newValue)")
        }
        didSet {
    
            print("super new \(oldValue)")
        }
    }

    final var height: Double {

        get {
            print("super get")
            return 10.0
        }
        set {
    
          print("super set")
        }

    }

    final func eat(){

        print("吃饭")
    }

}

2. 构造方法

构造方法

作用:对实例对象的内容进行初始化
Swift要求或者结构体中的存储属性(非lazy的)对象构造完毕后要有初始化值
语法:
init (参数列表) {初始化代码}
注意:
1、在Swift中类/结构体/枚举都需要构造体
2、构造方法的作用仅仅是用于初始化属性,而不是分配内容,分配内存是系统帮我们做的
3、构造方法是隐式调用的,通过 类名称()形式创建一个对象就会隐式调用init()构造方法
4、如果所有的存储属性都是默认值,可以不提供构造方法,系统会提供一个隐式的构造方法
5、如果存储属性可以提供缺省,那么提倡大家使用设置缺省值的方式,
这样可以简化代码(不用自定义构造法方法,不用写存储类型)

  class Person {

    var name: String = "ninicai"

    var age: Int

    func desription() ->String {

        return "name = \(name) age = \(age)"
    }
    init(){

        print("init")
        age = 30
    }
}

/*
    1、分配内存
    2、初始化name和age
    3、构造方法是隐式调用的
*/

var p = Person()

p.desription() // 显示调用



// 带参数的构造方法

class Person1 {

    var name: String

    var age:Int

    func description() ->String {
    
        return "name = \(name) age = \(age)"
    }

    /*
        构造方法的内部参数,默认也是外部参数
        而函数的内部参数默认不会当做外部参数
        而方法的内部参数,从第二个开始才会当做外部参数
        init(name: String, age: Int)
        构造方法对属性的顺序没有要求
        只要保证对象构造完时所有存储属性被初始化即可
    */
    init (age: Int, name: String)
    {
        self.name = name
        self.age = age
    }

    func setName(name: String, age: Int)
    {

    }
}

var p1 = Person1(age: 30, name: "ninini")

p1.setName("wocao", age: 30)

func setName(name: String, age: Int)
{

}

p1.setName("ninicai", age: 30)

常量存储属性与构造方法

常量存储属性只能通过省值或在构造方法中被修改,其他任何地方都不能修改

class Person2 {

    var name: String = "gaojun"

    let age: Int

    init(age: Int, name: String)
    {
        self.age = age
        self.name = name
    }

    func description() ->String {

        return "name = \(name) age = \(age)"
    }

}

var p2 = Person2(age: 34, name: "enha")

print(p2.description())

// 常量存储属性初始化之后不允许被修改

可选属性与构造方法

class Car {

    let name: String
    init (name: String)
    {
        self.name = name
    }    

}

class Person3 {

    let name: String
    var age: Int
    var car: Car?

    /*
        可选值存储属性可以不再构造方法中初始化
        也就是说可选值在对象构造完毕后不用初始化
        其实如果不对可选存储属性进行初始化,默认就是nil
    */

    init (age: Int, name: String)
    {
        self.age = age
        self.name = name
    }
    func description() ->String{

        return "name = \(name) age = \(age) car = \(car)"
    }
}

var p3 = Person3(age: 10, name: "nimabi")

print(p3.description())

结构体构造方法

struct Rect {

    // 此时既没有提供缺省值,也没有提供构造方法,但是编译通过,因为默认情况下,结构体会给结构体提供一个默认的成员逐一构造器


    var width: Double = 0.0

    var height: Double = 0.0
    // 系统默认会提供一个类似的方法
}

/*
注意:
    1、在类中默认是没有逐一构造器的
    2、如果在给结构体中自定义了构造方法,那么系统不会    生成默认的逐一构造器
    3、如果给存储属性提供了缺省值,系统还是会提供默认的逐一构造器
    4、如果给存储属性提供了缺省值,可以使用不带参数的方法初始化结构体
*/
var r = Rect()

print("width = \(r.width), height = \(r.height)")

var r0 = Rect(width: 10, height: 20)

print("width = \(r.width), height = \(r.height)")

"值类型" 的构造器代理

构造器代理:构造方法之间的相互调用
构造方法可以调用其他构造方法来完成实例的构造,称之为构造器代理
好处:减少构造方法之间的重复代码

struct Rect1 {

    var width: Double

    var height: Double

    init(width: Double, height: Double)
    {
        self.height = height
    
        self.width = width
    
    }
    init ()
    {
          self.init(width: 0, height: 0)
    }

    func show()
    {
        print("width = \(width) height = \(height)")
    }
}

var r1 = Rect1()

r1.show()

var r11 = Rect1(width: 100, height: 100)

r11.show()

通过闭包或者全局函数/类方法 设置存储属性才能确定初始值时,就可以通过闭包或全局函数设置存储属性的缺省值

func getValue () ->Int
{
    print("getValue")
    return 55
}

class Person4 {

    var name: String
    /*
        系统在初始化的时候会隐式执行闭包
        将闭包的执行结构赋值给存储属性
        注意:闭包后面一定要有(), 代表执行闭包
    */

    var age: Int = {

        // 返回值可以省略,默认返回值IDE类型就是存储属性类型
        print("age闭包")
        return 30

    }()
    lazy var height: Double = {
    
        print("lazy闭包")
        return 175.0
    }()

    var age2: Int = getValue()
    var age3: Int = Person4.getValue2()


    // 不能这样写,因为调用方法时对象还没有初始化完毕,self只有当所有的存储属性都初始化完毕之后才可以用
    init (name: String)
    {
        print("init")
        self.name = name
    }
    class func getValue2() ->Int {
    
        print("class getValue2")
    
        return 100
    }
    func getValue3() ->Int
    {
        return 88
    }
}

var p4 = Person4(name: "wocaonicai")

print(p4.height)

3.继承与构造方法

指定构造与便利构造方法

class Person {

    var name: String
    var age: Int

    // 指定的构造方法都是以init开头的
    init(name: String, age: Int)
    {
        print("init")
        self.name = name
        self.age = age
    }

    /*
        如果是值类型没问题,称之为构造器代理
        但是如果是引用类型会报错,需要在前面加上convenience关键字
        被convenience关键字修饰的构造方法称之为便利构造器
        通过调用其他构造方法来初始化
        反而言之,便利构造器中一定是调用其他构造方法初始化
        一定要出现self.init
*/

    convenience init()
    {
        print("convenience init")
        self.init(name: "gaojun", age: 30)
    }

    // 类可以有多个构造方法
    init(name: String)
    {
        print("init name")
        self.name = name
        self.age = 0
    
        // 不能在指定构造方法中调用便利构造方法,也就是说指定构造方法中不能出现self.init
    }

    convenience init (age: Int)
    {
        print("convenience init age")
    
        /*
            可以在便利构造器中调用指定的构造器
            self.init(name: "gaojun", age: 30)
            已在便利构造器中调用便利构造器
        */
        self.init()
    }
}

var p = Person()

var p1 = Person(age: 10)

var p2 = Person(name: "gaojun")

var p3 = Person(name: "gaojun", age: 20)

派生类的构造方法

class Man {

    var name: String

    // 指定构造方法
    init(name: String){

        print("Man init")
        self.name = name
    }

    // 便利构造方法
    convenience init(){

        print("Man convenience init")
    
        self.init(name: "convenience-gaojun")
    }
}

class SuperMan: Man {

    var age: Int

    /*
    注意:
        1、默认情况下构造方法不会被继承
        2、积累的存储属性只能通过基类的构造方法初始化
        3、初始化存储属性时必须先初始化当前类再初始化父类
        4、不能通过便利构造方法初始化父类,只能通过调用指定构造方法初始化
    */

    // 指定构造器
    init(age: Int){

        self.age = age
        super.init(name: "nicai")
    
        // 不能通过便利构造方法初始化父类,只能通过调用指定构造方法初始化父类
        // 一下写法是错误的: super.init()
    }

    func show(){

        print("age = \(self.age) name = \(self.name)")
    }
}

var m = Man()

var SM = SuperMan(age: 10)

构造器间的调用规则

指定构造器必须调用其直接父类的"指定构造器"
便利构造器必须调用同类中的其它构造器(指定或便利)
便利构造器必须最终以调用一个指定构造器结束
(无论调用的是指定还是便利,最终肯定会调用一个指定构造)
指定构造器总是向上代理(父类)
便利构造器总是横向代理(当前类)

class Man1 {

    var name: String

    // 指定构造方法
    init(name: String){
    
        print("Man -> init")
    
        self.name = name
        }

    // 便利构造方法
    convenience init(){

        print("Man -> convenience init")
    
        self.init(name:"gaojun")

    }

}

class SuperMan1: Man1 {

    var age: Int

    // 指定构造器
    init(age: Int){

        print("SuperMan1 -> init")
    
        self.age = age
    
        super.init(name: "nicai")
    }

    convenience init(){

        print("SuperMan1 -> convenience init")
    
        self.init(age:30)
    }

    convenience init(name: String, age: Int)
    {
        print("SuperMan1 -> convenience init name age")
    
        /* 
            调用子类构造器一定能够初始化所有属性
            便利构造器中只能通过self.init来初始化,不能使用`super.init`
            因为调用父类构造器不一定能够完全初始化所有属性
        */
        self.init()

    }

    func show(){

        print("name = \(self.name) age = \(self.age)")
    }

}

var p11 = SuperMan1()

p11.show()

var p12 = SuperMan1(age: 10)

p12.show()

var p13 = SuperMan1(name: "gaojun", age: 20)

p13.show()

两段式构造

构造过程可以划分为两个阶段
    1、确保当前类和父类所有存储属性都被初始化
    2、做一些其他初始化操作
好处:
    1、可以防止属性在被初始化之前访问
    2、可以防止属性被另外一个构造器意外赋值
注意:构造器的初始化永远是在所有类中的第一阶段初始化完毕之后才会开始第二阶段

编译器安全检查:
    1、必须先初始化子类特有属性,再向上代理父类指定构造方法初始化父类属性
    2、只能在调用完父类指定构造器才能访问父类属性
    3、在便利构造器中,必须先调用同类其他构造方法后才能访问属性
    4、第一阶段完成前不能访问父类睡醒/也不能引用self和调用任何实例方法

class Man2 {

    var name: String

    // 指定构造方法
    init(name: String){

        self.name = name
    }
    // 便利构造方法
    convenience init(){

        self.init(name: "goajun")
    }
}

class SuperMan2: Man2
{
    var age: Int

    // 指定构造器
    init(age: Int){

        print("SuperMan2第一阶段开始")
        // 对于子类引入的属性初始化
        self.age = age
    
        // 代码会报错,因为调用self.name之前还没有对父类的name进行初始化。即便在这个地方修改,也会被后面的初始化语句覆盖
    
        // 对父类引入的属性进行初始化
        super.init(name: "xmg")
    
        print("SuperMan2第二阶段开始")
    
        if age > 30 {
        
            self.name = "nicai"
        }
    }
}


class MonkeyMan: SuperMan2 {

    var height: Double
    init(height: Double){
    
        print("MonkeyMan第一阶段开始")
    
        // 对子类引入的属性初始化
        self.height = 99.0
    
        // 对父类引入的属性进行初始化
        super.init(age: 30)
    
    
        print("MonkeyMan第二阶段开始")
    
        if height < 100.0 {
    
        self.age = 50
        }   
    }
}

var mm = MonkeyMan(height: 20)

重写指定构造方法

// 子类的构造方法和父类的一模一样

class Man3 {

    var name: String

    // 指定构造方法
    init(name: String){

    self.name = name
    }
}

class SuperMan3: Man3 {

    var age: Int

    init(){

        self.age = 30
        super.init(name: "nicai")
    
    }

    /*
        如果子类中的构造器和父类一模一样
        必须加上override关键字,表示重写父类方法
        在老版本中Swift语言是不需要override关键字的,新版本才推出的
        override init(name: String){
    
            self.age = 30
            super.init(name: name)

        }


        将父类的指定构造器重写一个便利构造器
        也必须加上override关键字,表示重写父类方法
    */

    convenience override init (name: String){

        self.init(name: name)
    
        self.age = 30
    }
}

var Sp3 = SuperMan3()

便利构造方法不存在重写

class Man4 {

    var name: String

    // 指定构造方法
    init(name: String){

        print("Man4 -> init")
        self.name = name
    
    }

    convenience init(){

        print("Man -> convenience init")
    
        self.init(name:"wocao")
    }
}

class SuperMan4: Man4 {

    var age: Int
    init(age: Int){
        print("SuperMan4 -> init")
    
        self.age = age
    
        super.init(name: "wocaocao")

    }

    /*
        Swift中便利构造器方法不存在重写,如果加上override关键字
        系统会去查找父类中有没有和便利构造方法一样的指定构造方法
        有就不会报错,没有就报错

    
        为什么便利构造器不能重写呢?
        因为便利构造器只能是横向代理
        只能调用当前类的其他构造方法或指定方法,不可能调用super,所以不存在重写
        也就是说子类的便利构造方法不能直接访问父类的便利构造器方法,所以不存在重写的概念
    */

    convenience init(){

        print("SuperMan4 -> convenience init")
    
        self.init(age:30)
    }

}
var sm4 = SuperMan4()

/*
### 构造方法的自动继承

1、如果子类没有定义任何构造器,且子类中多用的存储属性都有缺省值,
会继承父类中所有的构造方法(包括便利构造器)
2、如果子类中只是重写父类中的某些指定的构造器
不管子类中的存储属性是否有缺省值,都不会继承父类中的其他构造方法
3、如果子类重写了父类中所有的指定构造器
不管子类中的存储属性是否有缺省值,都会同时继承父类中的所有便利方法
*/

class Person1 {

    var name: String

    var age: Int

    init(name: String, age: Int){

        print("Person init name age")
    
        self.name = name
        self.age = age
    }

    init(name: String){

        print("Person init name")
    
        self.name = name
    
        self.age = 0

    }

    convenience init(){

        print("Person convenience init")
    
        self.init(name:"MADe")

    }
}

class SuperPerson: Person1 {

    var height: Double = 175.0
    
    /*
        如果子类没有定义任何构造器,
        且子类所有的存储属性都有缺省值,会继承父类中所有的构造方法(包括便利构造器)
        父类中的存储属性是有父类的构造器初始化,子类的存储属性是由缺省值初始化的
    */
}



var sp = SuperPerson()

var sp1 = SuperPerson(name: "nicai", age: 12)


/*
    如果子类中只是重写了父类中的某些指定构造器
    不管子类中的存储属性是否有缺省值,都不会继承父类中的其他构造方法
*/

class SuperMan5: Person1 {


    var height: Double = 175.0

    init(height: Double){
    
        self.height = height
        super.init(name: "wocoa", age: 30)
    }
}


//var sm5 = SuperMan5()

var sm5 = SuperMan5(height: 90)

/*
    如果子类重写了父类中所有的指定构造器
    不管子类中的存储属性是否有缺省值,都会继承父类中的所有便利方法
*/

class SuperMan6: Person1 {

    var height: Double

    init(height: Double){

        self.height = height
    
        super.init(name: "wang", age: 21)
    }

    override init(name: String, age: Int) {
    
        self.height = 175.0
    
        super.init(name: name, age: age)
    }

    override init(name: String) {
    
        self.height = 175.0
    
         super.init(name: name)
    }

}

var sm6 = SuperMan6(height: <#T##Double#>)

var sm61 = SuperMan6(name: <#T##String#>)

var sm62 = SuperMan6(name: <#T##String#>, age:     <#T##Int#>)

必须构造器

/*
    必须构造方法的前面加上一个required关键字
    那么所有子类(后续子类)只要定义了构造方法都必须实现 该构造方法
*/

class Person2 {

    var name: String

    //早期的Swift版本中没有这个语法
    required init(name: String){

        print("Person2 init")
    
        self.name = name
    
    }

}

class SuperPerson2: Person2 {

    var age: Int = 30

    // 如果子类没有定义构造器, 可以不用重写
    init(){

        print("SuperPerson2 init")
    
        self.age = 30
    
        super.init(name: "gao")
    }
    
    /*
        1、因为子类自定义了构造器,就必须重写“必须构造器”
            因为如果子类没有自定义任何构造器,默认会继承父类构造器,所以不用重写
        2、重写必须构造器不用加上override关键字
           因为系统看到required关键字就自动查看父类
            为什么重写还需要加上required关键字,因为所有后续子类都必须重写
    */

    required init(name: String) {
    
        print("SuperPerson2 init name")
    
        self.age = 30
    
        super.init(name: name)
    }
}

var sp2 = SuperPerson2(name: "made")
上一篇下一篇

猜你喜欢

热点阅读