仓颉语法-类、接口

362 阅读15分钟

类和接口

  1. class 只能定义在源文件顶层

  2. class 是引用类型,struct 是值类型。class 之间可以继承,但 struct 之间不能继承

  3. class默认不可被继承,需要使用open修饰符

  4. 所有的类都隐式的继承自顶级父类Object

  5. 仓颉语言只允许单继承,每个类只能有一个父类,但可以实现多个接口

  6. 所有的仓颉类型(包括Object)都隐式的实现Any接口

  7. class 有终结器,类的实例被垃圾回收的时候被调用。函数名固定为 ~init。一般用于释放系统资源

    • 终结器没有参数,没有返回类型,没有泛型类型参数,没有任何修饰符,也不可以被显式调用
    • 带有终结器的类不可被 open 修饰,只有非 open 的类可以拥有终结器
    • 一个类最多只能定义一个终结器
    • 终结器不可以定义在扩展中。
    • 终结器被触发的时机是不确定的。由GC决定
    • 终结器可能在任意一个线程上执行
    • 多个终结器的执行顺序是不确定的
    • 终结器向外抛出未捕获异常属于未定义行为。
    • 终结器中创建线程或者使用线程同步功能属于未定义行为。
    • 终结器执行结束之后,如果这个对象还可以被继续访问,则属于未定义行为。
  8. class的 成员变量分为 实例成员变量 和 静态成员变量(使用 static 修饰符修饰,且必须有初值)。

    • 实例成员变量只能通过 class 实例访问,静态成员变量只能通过 class 类型名访问
  9. class 成员函数分为实例成员函数 和 静态成员函数(使用 static 修饰符修饰)。

    • 实例成员函数只能通过 class 实例访问,静态成员函数只能通过 class 类型名访问
    • 静态成员函数中不能访问实例成员变量,也不能调用实例成员函数,但在实例成员函数中可以访问静态成员变量以及静态成员函数
    • 实例成员函数又分为抽象成员函数和非抽象成员函数
    • 抽象成员函数没有函数体,只能定义在抽象类或接口
  10. class 成员属性分为 实例成员属性 和 静态成员属性(使用 static 修饰符修饰)。

    • 实例成员属性只能通过 class 实例访问,静态成员属性只能通过 class 类型名访问
  11. 静态构造函数 以关键字组合 static init 开头,后跟无参参数列表和函数体,且不能被访问修饰符修饰。函数体中必须 完成对所有未初始化的静态成员变量的初始化,否则编译报错

    • 一个 class 中 最多定义一个静态构造器
  12. class 支持 普通构造函数 和 主构造函数主构造函数中 有普通形参和成员变量形参(需要在参数名前加上 let 或 var)

    • 成员变量形参同时具有定义成员变量和构造函数参数的功
    • 普通形参必须在成员变量形参前面
    • 一个 class 中 最多定义一个主构造函数
    • 主构造函数不能调用同类的普通构造函数,但是普通构造函数可以调用同类的普通构造函数
  13. 如果没有构造函数并且所有成员变量都有初始值,编译器会自动生成一个无参的构造器

  14. class 的成员有 4 种访问修饰符修饰:private、internal、protected 和 public

    • private 在 class 定义内可见
    • internal class 所在的包及子包(包括子包的子包)可见。默认修饰符
    • protected class 所属的模块及当前类的子类可见
    • public 模块内外均可见。
  15. This关键字,代指当前类的类型

    • 只能作为实例成员函数的返回类型
    • 当使用子类对象调用 在父类中定义的 返回This类型的函数 时,该函数调用的类型会被识别为子类类型,而非定义所在的父类类型。
    • 如果实例成员函数没有声明返回类型,并且只存在返回 This 类型表达式时,当前函数的返回类型会推断为 This
  16. class是单继承的,继承时子类继承父类中 除 private成员和构造函数以外 的所有成员

    • 抽象类总是可被继承的,它的 open 修饰符是可选的
    • sealed 修饰符修饰抽象类可以限制 抽象类只能在本包中被继承
    • 非抽象的类只有使用open修饰,才能被继承
    • 一个类的父类,要么是显式指定的父类,要么是隐式继承的Object
    • Object 是所有类的父类,所有的类最终都继承自Object
  17. 子类可以覆盖父类的非抽象实例成员函数、重定义父类的非抽象静态成员函数。调用时会根据运行时的实际类型进行调用

    • 覆盖时,父类中的成员函数使用 open 修饰,子类中的同名函数使用 override 修饰(override 可选)
    • 重定义时,要求子类中的同名静态函数使用 redef 修饰,其中 redef 是可选的
    • 如果抽象函数或 open 修饰的函数有命名形参,那么实现函数或 override 修饰的函数也需要保持同样的命名形参
    • 当实现或重定义的函数为泛型函数时,子类型函数的类型变元约束需要比父类型中对应函数更宽松或相同
  18. 接口只能约束行为,不能约束数据,默认具有 open 语义

    • 实现interface,需要实现它的所有方法
    • 接口中可以约束成员函数、操作符重载函数、成员属性
    • 上面的三类约束,可以使用open修饰,open是可选的。
    • 所有的成员默认具有public语义,所有的成员都可以默认实现。如果接口有默认实现,实现接口的类型可以选择性实现成员。如果实现接口的类型没有实现成员,通过实例调用时走接口默认实现,如果实现了成员,调用时走类型的实现
    • 如果一个类型实现了某个接口,这个类型自动成为接口的子类型
    • 使用sealed修饰interface,可以保证只能在 interface 定义所在的包内继承、实现或扩展该 interface。但不会影响继承 sealed 接口的子接口或实现 sealed 接口的类的修饰符
    • sealed 默认蕴含 public/open 的语义
    • 若 sealed 接口的子接口被 public 修饰,且不被 sealed 修饰,则其子接口可在包外被继承、实现或扩展。
    • 继承、实现 sealed 接口的类型可以不被 public 修饰
    • 接口的成员可以是实例的或者静态的
    • 接口的成员默认就被 public 修饰,不可以声明额外的访问控制修饰符,同时也要求实现类型必须使用 public 实现
  19. 接口支持继承,和类继承语法一样。如果要实现多个接口,可以通过&将多个接口连接起来

    • 接口可以继承一个或多个接口,但不能继承类。接口继承的时候可以添加新的接口成员

    • 子接口继承父接口的行为和子类继承父类类似。

      • 子接口覆盖父接口的实现,可以使用可选的 override(针对实例成员)或redef(针对静态成员)
      • 父接口的有默认实现的函数,子接口继承时要么不写,要么给一个新实现
  20. 仓颉所有的类型都可以实现接口,包括数值类型、Rune、String、struct、class、enum、Tuple、函数以及其它类型

    • 在定义类型时就声明实现接口
    • 通过扩展实现接口
    • 由语言内置实现
  21. 类型实现接口时有一定的限制

    • 需要实现接口中要求的所有成员
      • 对于成员函数和操作符重载函数,实现时要保证函数名,参数列表和返回值类型和接口中相同
      • 对于成员属性,实现时有没有mut以及类型要和接口中一致
      • 如果接口中的成员函数或操作符重载函数的返回值类型是 class 类型,那么允许实现函数的返回类型是其子类型
    • struct、enum 和 class 在实现接口时,函数或属性定义前的 override 修饰符(或 redef 修饰符)是可选的
    • 如果一个类型在实现多个接口时,多个接口中包含同一个成员的默认实现,需要实现类型提供自己的实现
  22. Any是一个内置接口,所有接口都默认继承它,所有非接口类型都默认实现它。所有类型都可以作为 Any 类型的子类型使用

  23. 属性可以在 interface、class、struct、enum、extend 中定义

    • 属性的 getter 和 setter 分别对应两个不同的函数
    • getter 函数类型是 () -> T,T 是该属性的类型,当使用该属性作为表达式时会执行 getter 函数。
    • setter 函数类型是 (T) -> Unit,T 是该属性的类型,形参名需要显式指定,当对该属性赋值时会执行 setter 函数
    • 在属性的 getter 和 setter 中访问属性自身属于递归调用,会死循环
  24. 如何判断子类型关系,可以查看这里

    • 继承 class 后,子类即为父类的子类型
    • 实现接口(含扩展实现)后,实现接口的类型即为接口的子类型
    • 一个元组 t1 的每个元素的类型都是另一个元组 t2 的对应位置元素类型的子类型,那么 t1是t2的子类型
    • 给定两个函数类型 (U1) -> S2 和 (U2) -> S1。当且仅当 U2 <: U1 且 S2 <: S1(注意顺序),(U1) -> S2 是 (U2) -> S1 的子类型。即子类型函数的参数类型更广,子类型函数的返回值类型更窄
    • 一个类型 T 永远是自身的子类型,即 T <: T。
    • Nothing 类型永远是其他任意类型 T 的子类型,即 Nothing <: T。
    • 任意类型 T 都是 Any 类型的子类型,即 T <: Any。
    • 任意 class 定义的类型都是 Object 的子类型,即如果有 class C {},则 C <: Object
  25. 类型转换

    • 仓颉不支持隐式类型转换,所有转换都是显式的
    • 对于数值类型(包括:Int8,Int16,Int32,Int64,IntNative,UInt8,UInt16,UInt32,UInt64,UIntNative,Float16,Float32,Float64),仓颉支持使用 T(e) 的方式得到一个值等于 e,类型为 T 的值。其中,表达式 e 的类型和 T 可以是上述任意数值类型
    • Rune 到 UInt32 的转换使用 UInt32(e) 的方式,其中 e 是一个 Rune 类型的表达式,UInt32(e) 的结果是 e 的 Unicode scalar value 对应的 UInt32 类型的整数值
    • 整数类型到 Rune 的转换使用 Rune(num) 的方式,其中 num 的类型可以是任意的整数类型,且仅当 num 的值落在 [0x0000, 0xD7FF] 或 [0xE000, 0x10FFFF] (即 Unicode scalar value)中时,返回对应的 Unicode scalar value 表示的字符,否则,编译报错(编译时可确定 num 的值)或运行时抛异常
    • is 操作符来判断某个表达式的类型是否是指定的类型(或其子类型)
    • as 操作符可以用于将某个表达式的类型转换为指定的类型,返回的是一个 Option 类型
// 所有的类都隐式的继承自顶级父类Object
// 需要使用open修饰,MyClass才能被继承
public open class MyClass <: Object {
    // 只能在本class内可见
    private let valuePrivate: Int = 0
    // MyClass所在的包及子包(包括子包的子包)可见。默认修饰符
    let valueInternal: Int = 1
    // MyClass 所属的模块及当前类的子类可见
    protected let valueProtected: Int = 2
    // 所有模块都可见
    public let valuePublic: Int = 3

    // 静态成员变量 只能通过 class 类型名访问 eg. MyClass.NAME
    // 使用 static 修饰符修饰,且必须有初值
    // 不可变 静态成员变量
    static let NAME: String = 'NAME'
    // 可变静态成员变量
    static var AGE: Int = 18

    // 实例成员变量 只能通过 class 实例 eg. let s = MyClass(); printlnt(s.name)
    // 实例成员变量定义时可以不设置初值(但必须标注类型),也可以设置初值
    // 不可变 实例成员变量
    let name: String
    // 可变 实例成员变量
    var age: Int = 20

    // 静态成员函数 只能通过 class 类型名访问,不能访问实例成员变量和实例成员函数
    public static func nameAge(): String {
        "${NAME} ${AGE}"
    }

    // 实例成员函数 只能通过 class 实例访问。在实例成员函数中可以访问静态成员变量以及静态成员函数
    public open func nameAgeAnother(): String {
        // println("MyClass ${NAME} ${AGE}")
        "MyClass ${name} ${age} ${another}"
    }

    // This指代当前类的类型,通过在方法中返回this实例,可用于完成链式调用
    // 
    public func setAge(age: Int): This {
        this.age = age
        return this
    }

    // 静态成员属性 getter。只能get,不能set
    public static prop GETNAME: String {
        get() {
            NAME
        }
    }
    // 静态成员属性 setter。既能get也能set
    public mut static prop SETAGE: Int {
        get() {
            AGE
        }
        set(v) {
            AGE = v
        }
    }

    // 实例成员属性 getter。只能get不能set
    public prop fullName: String {
        get() {
            NAME + name
        }
    }
    // 实例成员属性 setter。既能get也能set
    public mut prop fullAge: Int {
        get() {
            AGE + age
        }
        set(value) {
            age = value
        }
    }

    // 静态初始化器。一个 class 中最多允许定义一个静态初始化器
    // 静态初始化器以关键字组合 static init 开头,后跟无参参数列表和函数体,且不能被访问修饰符修饰
    // 在静态初始化器中通过赋值表达式来对静态成员变量进行初始化
    static init() {
        // 这里再次给静态成员变量赋值
        AGE = 20
        // 不能给 不可变静态成员变量 赋值
        // sName = 'aa'
    }
    // 定义(最多)一个主构造函数
    // 主构造函数的名字和 class 类型名相同,它的参数列表中可以有两种形式的形参:普通形参和成员变量形参(需要在参数名前加上 let 或 var)
    // 成员变量形参同时扮演定义成员变量和构造函数参数的功能
    // 普通形参必须在成员变量形参前面
    public MyClass(name: String, let another: String) {
        // 主构造器中不能调用同类的普通构造器
        // this(name)
        this.name = name
        this.age = 18
    }
    public init() {
        this('unravel')
    }
    // 普通构造函数以关键字 init 开头,后跟参数列表和函数体
    // 函数体中必须完成对所有未初始化的实例成员变量的初始化
    // 如果参数名和成员变量名无法区分,可以在成员变量前使用 this 加以区分,this 表示 class 的当前实例
    public init(name: String) {
        // 调用本类的其他 普通构造函数
        this(name, 21)
    }
    // 一个 class 中可以定义多个普通构造函数,但它们必须构成重载,否则报重定义错误
    public init(name: String, age: Int) {
        this.name = name
        this.age = age
        this.another = 'another'
    }

    // class 有终结器,类的实例被垃圾回收的时候被调用。
    // 函数名固定为 ~init。一般用于释放系统资源
    // 只有非open修饰的class才能定义~init。所以可以将上面的open修饰符去掉
    // ~init() {
    //     println("~init 执行,用于释放一些资源")
    // }
}

// class 是引用类型,class 是值类型。class 之间可以继承,但 class 之间不能继承
// MyClass需要被open修饰,才能被继承
class MySubClass <: MyClass {
    // 子类将继承父类中除 private 成员和构造函数以外的所有成员
    // 通过super调用父类构造函数 init(name:)
    public init() {
        // 必须在第一个表达式处,之前不能有任何表达式或声明
        super('unravel')
    }

    // 如果子类的构造函数没有显式调用父类构造函数,也没有显式调用其他构造函数
    // 编译器会在该构造函数体的开始处插入直接父类的无参构造函数的调用
    // 需要保证父类有无参的普通构造函数 public init() {}
    public init(age: Int) {
        // 编译器会在这里插入一条super()调用
        this.age = age
    }

    // 覆盖(override)父类中的同名非抽象实例成员函数 nameAgeAnother
    // 覆盖时,要求父类中的成员函数使用 open 修饰,子类中的同名函数使用 override 修饰,其中 override 是可选的
    public override func nameAgeAnother(): String {
        println("MySubClass ${NAME} ${AGE}")
        // 调用父类实现
        super.nameAgeAnother()
    }

    // 重定义父类中的同名非抽象静态函数
    // 重定义时,要求子类中的同名静态函数使用 redef 修饰,其中 redef 是可选的
    public static redef func nameAge(): String {
        "MySubClass ${NAME} ${AGE}"
    }
}

// interface只能约束行为,不能约束数据,默认具有 open 语义
// 实现interface,需要实现它的所有方法,不能选择性实现
interface MyInterface {
    // 成员函数 可被open修饰,open可选
    open func memberFunc(i: Int): Int
    // 成员属性 可以是实例成员属性,也可以是静态成员属性。上面的成员函数同理
    prop memberGet: String
    mut prop memberSet: String
    // 操作符重载函数
    // cjlint-ignore -start !G.OPR.01
    operator func [](index: Int, name: String): String
    operator func [](index: Int, name: String, value!: String): Unit
    // cjlint-ignore -end
}

class MyClassForInterface <: MyInterface {
    // 成员函数 可被open修饰,open可选
    public open func memberFunc(i: Int): Int {
        i
    }
    // 成员属性
    public prop memberGet: String {
        get() {
            'memberGet'
        }
    }
    public mut prop memberSet: String {
        get() {
            'memberSet'
        }
        set(v) {
            println(v)
        }
    }
    // 操作符重载函数
    // cjlint-ignore -start !G.OPR.01
    public operator func [](index: Int, name: String): String {
        "${index} ${name}"
    }
    public operator func [](index: Int, name: String, value!: String): Unit {
        println("${index} ${name} ${value}")
    }
    // cjlint-ignore -end
}

// interface只能约束行为,不能约束数据,默认具有 open 语义
// 实现interface,需要实现它的所有方法,不能选择性实现
interface MyInterface2 {
    // 成员函数 可被open修饰,open可选
    static func memberFunc(i: Int): Int
    // 成员属性 可以是实例成员属性,也可以是静态成员属性。上面的成员函数同理
    static prop MEMBERGET: String
    mut static prop MEMBERSET: String
    // 操作符重载函数
    // cjlint-ignore -start !G.OPR.01
    operator func [](index: Int, name: String): String
    operator func [](index: Int, name: String, value!: String): Unit
    // cjlint-ignore -end
}

class MyClassForInterface2 <: MyInterface2 {
    static var NAME = 'unravel'
    // 成员函数 可被open修饰,open可选
    public static func memberFunc(i: Int): Int {
        i
    }
    // 成员属性
    public static prop MEMBERGET: String {
        get() {
            'memberGet'
        }
    }
    public mut static prop MEMBERSET: String {
        get() {
            'memberSet'
        }
        set(v) {
            println(v)
            NAME = v
        }
    }
    // 操作符重载函数
    // cjlint-ignore -start !G.OPR.01
    public operator func [](index: Int, name: String): String {
        "${index} ${name}"
    }
    public operator func [](index: Int, name: String, value!: String): Unit {
        println("${index} ${name} ${value}")
    }
    // cjlint-ignore -end
}

interface DefaultImpInterface {
    static func defaultStaticImp(): String {
        "接口 静态函数默认实现"
    }

    func defaultInstanceImp(): String {
        "接口 实例函数默认实现"
    }

    static prop defaultStaticProp: String {
        get() {
            "接口 静态属性getter默认实现"
        }
    }

    mut static prop defaultStaticSetProp: String {
        get() {
            "接口 静态属性sette默认实现"
        }
        set(v) {
            "接口 静态属性setter默认实现" + v
        }
    }

    prop defaultGetterProp: String {
        get() {
            "接口 实例属性getter默认实现"
        }
    }

    mut prop defaultSetterProp: String {
        get() {
            "接口 实例属性sette默认实现"
        }
        set(v) {
            "接口 实例属性setter默认实现" + v
        }
    }

    // 操作符重载函数
    // cjlint-ignore -start !G.OPR.01
    operator func [](index: Int, name: String): String {
        "操作符函数默认实现 ${index} ${name}"
    }
    operator func [](index: Int, name: String, value!: String): Unit {
        println("操作符函数默认实现  ${index} ${name} ${value}")
    }
    // cjlint-ignore -end
}

// 实现类型为class,可以有默认实现
class DefaultClass <: DefaultImpInterface {}
// 实现类型为enum,可以有默认实现
enum DefaultEnum <: DefaultImpInterface {
    | One
}
// 实现类型为struct,可以有默认实现
struct DefaultStruct <: DefaultImpInterface {}

interface Addable {
    func anyAdd(other: Int64): Int64
    func add(other: Int64): Int64 {
        other
    }

    static func staticAdd(other: Int64): Int64 {
        other + 10
    }
}

interface Subtractable {
    func sub(other: Int64): Int64
}
// 接口继承的同时,额外添加函数约束
interface Calculable <: Addable & Subtractable {
    // Addable中add有默认实现,这里必须给一套新的实现
    override func add(other: Int64): Int64 {
        other + 1
    }
    // Addable中add有默认实现,这里必须给一套新的实现
    static redef func staticAdd(other: Int64): Int64 {
        other + 20
    }
    func mul(other: Int64): Int64
    func div(other: Int64): Int64
}
// 实现多个接口,要同时实现Addable 和 Subtractable的所有方法以及Calculable的方法
class MyInt <: Calculable {
    let value = 0
    public func anyAdd(other: Int64): Int64 {
        value + other
    }
    // 这里add又重新实现了。实际调用的时候会调用这个实现
    public func add(other: Int64): Int64 {
        value + other
    }
    // staticAdd 没有重新实现,实际调用时会调用Calculable中重定义的默认实现
    public func sub(other: Int64): Int64 {
        value - other
    }
    public func mul(other: Int64): Int64 {
        value * other
    }
    public func div(other: Int64): Int64 {
        value / other
    }
}

public func classDemo() {
    // 使用 init(name) 普通构造函数 创建一个不可变的MyClass
    let instance = MyClass('unravel')
    // 访问 实例成员变量
    println("访问 实例成员变量" + instance.name)
    // 因为s是一个let变量,同时MyClass是一个值类型。所以即使age变量被var修饰。这里也不能修改它
    // s.age = 10
    // 访问 静态成员变量
    println("访问 静态成员变量" + MyClass.NAME)

    // 访问 实例成员函数
    println("访问 实例成员函数" + instance.nameAgeAnother())
    // 访问 静态成员函数
    println("访问 静态成员函数" + MyClass.nameAge())

    // 访问 实例成员属性
    println("访问 实例成员属性" + instance.fullName)
    // 访问 静态成员属性
    println("访问 静态成员属性" + MyClass.GETNAME)

    // 使用 init(name,age) 创建一个可变的MyClass
    let varInstance = MyClass('unravel', 22)
    // 这里可以修改 实例成员变量。因为age是var
    varInstance.age = 22
    // 设置 实例成员属性
    // 不鼓励这种方式,最好还是通过成员函数去修改
    varInstance.fullAge = 28
    // 设置 静态成员属性
    MyClass.SETAGE = 20

    // 编译期是MyClass类型,运行期是MyClass类型,最终调用MyClass中的方法
    let parent: MyClass = MyClass("parent")
    // 编译期是MyClass类型,运行期是MySubClass类型,最终调用MySubClass中的方法
    let child: MyClass = MySubClass()
    println("调用MyClass中的方法" + parent.nameAgeAnother())
    println("调用MySubClass中的方法" + child.nameAgeAnother())
    // 执行MyClass中的nameAge方法
    println("执行MyClass中的nameAge方法" + MyClass.nameAge())
    // 执行MySubClass的nameAge方法
    println("执行MySubClass的nameAge方法" + MySubClass.nameAge())

    // Any是一个内置接口,所有接口都默认继承它,所有非接口类型都默认实现它
    let a: Any = 1

    let defaultClass = DefaultClass()
    defaultClass.defaultInstanceImp()
    let defaultEnum = DefaultEnum.One
    defaultEnum.defaultInstanceImp()
    let defaultStruct = DefaultStruct()
    defaultStruct.defaultInstanceImp()
}

参考资料

  1. 仓颉编程语言开发指南 developer.huawei.com/consumer/cn…
  2. 仓颉编程语言白皮书 developer.huawei.com/consumer/cn…
  3. 仓颉编程语言语言规约developer.huawei.com/consumer/cn…