仓颉语言学习笔记(5)类class、接口interface、属性、子类型关系、类型转换、Collection类型

仓颉语言学习笔记(5)类class、接口interface、属性、子类型关系、类型转换、Collection类型

class定义

注意:

  1. class 只能定义在源文件的顶层作用域。
  2. 抽象类中禁止定义 private 的抽象函数;
  3. 不能为抽象类创建实例;
  4. 抽象类的非抽象子类必须实现父类中的所有抽象函数。
abstract class Shape{
    public func getArea(): Float64
}

class Rectangle <: Shape {
    private var width: Float64
    private var height: Float64

    init(width: Float64, height: Float64) {
        this.width = width
        this.height = height
    }

    public func getArea(): Float64 {
        return this.width * this.height
    }
}

main() {
    let a: Shape = Rectangle(5.0, 1.5)
    println("Area: " + a.getArea().toString())
}
// Area: 7.500000

class成员变量

class 成员变量分为实例成员变量静态成员变量,静态成员变量使用 static 修饰符修饰,没有静态初始化器时必须有初值,只能通过类型名访问。

实例成员变量定义时可以不设置初值(但必须标注类型),也可以设置初值,只能通过对象(即类的实例)访问。

class Calculator {
    static var x: Float64
    var y: Float64
    Calculator(y: Float64) {
        this.y = y
    }
    static init() {
        Calculator.x = 10.0
    }
    func calc(): Float64 {
        return Calculator.x * this.y
    }
}

main() {
    let a: Calculator = Calculator(5.0)
    println("Area: " + a.calc().toString())
}
// Area: 50.000000

class静态初始化器

静态初始化器以关键字组合 static init 开头,后跟无参参数列表和函数体,且不能被访问修饰符修饰。函数体中必须完成对所有未初始化的静态成员变量的初始化,否则编译报错。

一个 class 中最多允许定义一个静态初始化器,否则报重定义错误。

class Circle {
    let radius: Float64
    static let pi: Float64
    static init() {
        pi = 3.141592653589793
    }
    init(r: Float64) {
        radius = r
    }
    func area(): Float64 {
        return Circle.pi * radius * radius
    }
}

main() {
    let a: Circle = Circle(5.0)
    println("Area: " + a.area().toString())
}
// Area: 78.539816

class构造函数

普通构造函数以关键字 init 开头,后跟参数列表和函数体,函数体中必须完成所有未初始化实例成员变量的初始化,否则编译报错。一个类中可以定义多个普通构造函数,不同 构造函数之间必须构成重载。

class Rectangle {
    let width: Float64
    let height: Float64
    init(width: Float64, height: Float64) {
        this.width = width
        this.height = height
    }
}

main() {
    let rectangle = Rectangle(10.0, 10.0)
    let area = rectangle.width * rectangle.height
    print("Area: " + area.toString())
}
// Area: 78.539750

一个类内还可以定义(最多)一个主构造函数。主构造函数的名字和 class 类型名相同,它的参数列表中可以有两种形式的形参:普通形参成员变量形参(需要在参数名前加上 let 或 var),成员变量形参同时具有定义成员变量和构造函数参数的功能。

class Rectangle {
    Rectangle(let width: Float64, let height: Float64) {}
    func area() {
        return this.width * this.height
    }
}

main() {
    print("The area of width ${10.0} and height ${6.666666} is ")
    println(Rectangle(10.0, 6.666666).area())
}
// The area of width 10.000000 and height 6.666666 is 66.666660

创建类的实例时调用的构造函数,将根据以下顺序执行类中的表达式:

  1. 先初始化主构造函数之外定义的有缺省值的变量
  2. 如果构造函数体内未显式调用父类构造函数或本类其它构造函数,则调用父类的无参构造函数 super(),如果父类没有无参构造函数,则报错;
  3. 执行构造函数体内的代码

class终结器

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

class成员函数

class 成员函数同样分为实例成员函数静态成员函数(使用 static 修饰符修饰),实例成员函数只能通过对象访问,静态成员函数只能通过 class 类型名访问;静态成员函数中不能访问实例成员变量,也不能调用实例成员函数,但在实例成员函数中可以访问静态成员变量以及静态成员函数。

根据有没有函数体,实例成员函数又可以分为抽象成员函数非抽象成员函数。抽象成员函数没有函数体,只能定义在抽象类或接口中。需要注意的是,抽象实例成员函数默认具有 open 的语义,open 修饰符是可选的,且必须使用 public 或 protected 进行修饰

abstract class Animal {
    public func sound(): Unit
}

class Cat <: Animal {
    public func sound() {
        println("The cat meowed")
    }
}

main() {
    Cat().sound()
}

class成员的访问修饰符

对于 class 的成员(包括成员变量、成员属性、构造函数、成员函数),可以使用的访问修饰符有 4 种访问修饰符修饰:private、internal、protected 和 public,缺省的含义是 internal。4种访问修饰符的可见范围按从小到大的顺序排列。

  • private 表示在 class 定义内可见。
  • internal 表示仅当前包及子包内可见。
  • protected 表示当前模块及当前类的子类可见。
  • public 表示模块内外均可见。

This类型

在类内部,支持 This 类型占位符,代指当前类的类型。它只能被作为实例成员函数的返回类型来使用,当使用子类对象调用在父类中定义的返回 This 类型的函数时,该函数调用的类型会被识别为子类类型,而非定义所在的父类类型。

如果实例成员函数没有声明返回类型,并且只存在返回 This 类型表达式时,当前函数的返回类型会推断为 This。示例如下:

open class C1 {
    func f(): This {  // its type is `() -> C1`
        return this
    }

    func f2() { // its type is `() -> C1`
        return this
    }

    public open func f3(): C1 {
        return this
    }
}
class C2 <: C1 {
    // member function f is inherited from C1, and its type is `() -> C2` now
    public override func f3(): This { // Ok
        return this
    }
}

var obj1: C2 = C2()
var obj2: C1 = C2()

var x = obj1.f()    // During compilation, the type of x is C2
var y = obj2.f()    // During compilation, the type of y is C1

class的继承

子类将继承父类中除 private 成员和构造函数以外的所有成员。
抽象类总是可被继承的,故抽象类定义时的 open 修饰符是可选的,也可以使用 sealed 修饰符修饰抽象类,表示该抽象类只能在本包被继承。但非抽象的类可被继承是有条件的:非抽象类定义时必须使用修饰符 open 修饰。当带 open 修饰的实例成员被 class 继承时,该 open 的修饰符也会被继承。当非 open 修饰的类中存在 open 修饰的成员时,编译器会给出告警。

class 仅支持单继承。

open class Animal{
    let name: String
    init(name: String) {
        this.name = name
    }
    public open func sound() {
        println("${name} makes a sound")
    }
}

class Cat <: Animal {
    init(name: String) {
        super(name)
    }
    public override func sound() {
        println("${name} meows")
    }
}

对于定义时指定了父类的 class,它的直接父类就是定义时指定的类,对于定义时未指定父类的 class,它的直接父类是 Object 类型。Object 是所有类的父类。

父类构造函数调用

子类的 init 构造函数可以使用 super(args) 的形式调用父类构造函数,或使用 this(args) 的形式调用本类其他构造函数,但两者之间只能调用一个。如果调用,必须在构造函数体内的第一个表达式处,在此之前不能有任何表达式或声明。

open class Animal{
    let name: String
    init(name: String) {
        this.name = name
    }
}

class Cat <: Animal {
    let nickname: String
    init(name: String, nickname: String) {
        super(name)
        this.nickname = nickname
    }
}

子类的主构造函数中,可以使用 super(args) 的形式调用父类构造函数(不必是父类的主构造函数),但不能使用 this(args) 的形式调用本类其他构造函数。

覆盖和重定义

子类中可以**覆盖(override)**父类中的同名非抽象实例成员函数(回顾重载:),即在子类中为父类中的某个实例成员函数定义新的实现。覆盖时,要求父类中的成员函数使用 open 修饰,子类中的同名函数使用 override 修饰,其中 override 是可选的。

对于被覆盖的函数,调用时将根据变量的运行时类型(由实际赋给该变量的对象决定)确定调用的版本(即所谓的动态派发)。

open class Cat {
    let name: String
    init(name: String) {
        this.name = name
    }
    public open func getName() {
        return name
    }
    func toString() {
        return this.getName()
    }
}

class LittleCat <: Cat {
    init(name: String) {
        super(name)
    }
    public override func getName() {
        return "Little " + super.getName()
    }
}

main() {
    let cats = ArrayList<Cat>()
    cats.add(LittleCat("CQX"))
    cats.add(Cat("HX"))
    for(cat in cats) {
        println(cat.toString())
    }
}

子类中可以重定义父类中的同名非抽象静态函数,即在子类中为父类中的某个静态函数定义新的实现。重定义时,要求子类中的同名静态函数使用 redef 修饰,其中 redef 是可选的。

open class MathTool {
    public static func add(a: Int64, b: Int64){
        return a + b
    }
}

class WeirdMathTool <: MathTool {
    public redef static func add(a: Int64, b: Int64) {
        return a * b
    }
}

main() {
    let x = 5
    let y = 6
    println("Weird ${x} + ${y} = ${WeirdMathTool.add(x, y)}")
}
// Weird 5 + 6 = 30

接口定义

接口使用关键字 interface 声明,其后是接口的标识符 I 和接口的成员。接口成员可被 open 修饰符修饰,并且 open 修饰符是可选的。

当接口 I 声明了一个成员函数 f 之后,要为一个类型实现 I 时,就必须在该类型中实现一个对应的 f 函数。

因为 interface 默认具有 open 语义,所以 interface 定义时的 open 修饰符是可选的。

interface Animal {
    func sound() {}
}

class Dog <: Animal {
    public func sound() {
        print("Woof!")
    }
}

main() {
    let dog = Dog()
    dog.sound()
}

接口实现的要求

仓颉除 Tuple、VArray 和函数外的其他类型都可以实现接口。

一个类型实现接口有三种途径:

  1. 定义类型时就声明实现接口。
  2. 通过扩展实现接口。
  3. 由语言内置实现。

实现类型声明实现接口时,需要实现接口中要求的所有成员,为此需要满足下面一些规则。

  1. 对于成员函数和操作符重载函数,要求实现类型提供的函数实现与接口对应的函数名称相同、参数列表相同、返回类型相同。
  2. 对于成员属性,要求是否被 mut 修饰保持一致,并且属性的类型相同。

如果接口中的成员函数或操作符重载函数的返回值类型是 class 类型,那么允许实现函数的返回类型是其子类型

open class Base {}
class Sub <: Base {}

interface I {
    func f(): Base
}

class C <: I {
    public func f(): Sub {
        Sub()
    }
}

接口的成员还可以提供默认实现

interface Animal {
    func sound() {
        print("???")
    }
}

class Dog <: Animal {
}

main() {
    let dog = Dog()
    dog.sound()
}

如果一个类型在实现多个接口时,多个接口中包含同一个成员的默认实现,这时会发生多重继承的冲突,语言无法选择最适合的实现,因此这时接口中的默认实现也会失效,需要实现类型提供自己的实现。

interface Run{
    func play() {
        return "Running"
    }
}

interface Fight {
    func play() {
        return "Fighting"
    }
}

class Man <: Run & Fight {
    public override func play() {
        return "Running ans Fighting"
    }
}

main() {
    let man = Man()
    println(man.play())
}

Any类型

Any 类型是一个内置的接口,仓颉中所有接口都默认继承它,所有非接口类型都默认实现它,因此所有类型都可以作为 Any 类型的子类型使用。

main() {
    var any: Any = 1
    any = 2.0
    any = "hello, world!"
}

属性

**属性(Properties)**提供了一个 getter 和一个可选的 setter 来间接获取和设置值。

使用属性的时候与普通变量无异,只需要对数据操作,对内部的实现无感知,可以更便利地实现访问控制、数据监控、跟踪调试、数据绑定等机制。

class Cat {
    private var age = 10

    public mut prop propAge: Int64 {
        get() {
            println("get")
            return age
        }
        set(value) {
            println("set")
            age = value
        }
    }
}

main() {
    let myCat = Cat()
    println(myCat.propAge)
    myCat.propAge = 5
    println(myCat.propAge)
    return 0
}
// get
// 10
// set
// get
// 5

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

无 mut 修饰符的属性有且仅有定义 getter(对应取值)实现。使用 mut 修饰的属性必须分别定义 getter(对应取值)和 setter(对应赋值)的实现。

class Cat <: ToString{
    private var age: Int64
    private let name: String

    init(age: Int64, name: String) {
        this.age = age
        this.name = name
    }

    public mut prop propAge: Int64 {
        get() {
            println("get")
            return age
        }
        set(value) {
            println("set")
            age = value
        }
    }

    public prop propName: String {
        get() {
            return name
        }
    }

    public override func toString(): String {
        return "cat name: ${this.name}, age: ${this.age}"
    }
}

main() {
    let myCat = Cat(10, "baby")
    println(myCat)
    myCat.propAge = 123
    println(myCat)
    return 0
}

抽象属性

在 interface 和抽象类中也可以声明抽象属性,这些抽象属性没有实现。当实现类型实现 interface 或者非抽象子类继承抽象类时,必须要实现这些抽象属性。
与覆盖的规则一样,实现类型或子类在实现这些属性时,如果父类型属性带有 mut 修饰符,则子类型属性也需要带有 mut 修饰符,同时也必须保持一样的类型。

interface I {
    prop a: Int64
    mut prop b: Int64
}

class C <: I {
    private var value = 0

    public prop a: Int64 {
        get() {
            value
        }
    }

    public mut prop b: Int64 {
        get() {
            value
        }

        set(v) {
            value = v
        }
    }
}

属性使用

class A {
    private static var valueY: Int64 = 123
    public mut static prop y: Int64 {
        get() {
            valueY
        }
        set(v) {
            valueY = v
        }
    }
}

main() {
    println(A.y)
    A.y = 321
    println(A.y)
    return 0
}
// 123
// 321

子类型关系

与其他面向对象语言一样,仓颉语言提供子类型关系子类型多态

  • 假设函数的形参是类型 T,则函数调用时传入的参数的实际类型既可以是 T 也可以是 T 的子类型(严格地说,T 的子类型已经包括 T 自身,下同)。
  • 假设赋值表达式 = 左侧的变量的类型是 T,则 = 右侧的表达式的实际类型既可以是 T 也可以是 T 的子类型
  • 假设函数定义中用户标注的返回类型是 T,则函数体的类型既可以是 T 也可以是 T 的子类型
  1. 继承clas带来的子类型关系

  2. 实现接口带来的子类型关系

    实现接口(含扩展实现)后,实现接口的类型即为接口的子类型。

  3. 元组类型的子类型关系

    如果一个元组 t1 的每个元素的类型都是另一个元组 t2 的对应位置元素类型的子类型,那么元组 t1 的类型也是元组 t2 的类型的子类型。

    open class C1 {}
    
    class C2 <: C1{}
    
    open class C3 {}
    
    class C4 <: C3 {}
    
    main() {
        let t: (C1, C3) = (C2(), C4())
        return 0
    }
    
  4. 函数类型的子类型关系

    给定两个函数类型 (U1) -> S2 和 (U2) -> S1,如果存在 (U1) -> S2 是 (U2) -> S1的子类型,当且仅当 U2 是 U1 的子类型,且 S2 是 S1 的子类型(S2 <: S1 且U2 <: U1 

  5. 永远成立的子类型关系

    • 一个类型 T 永远是自身的子类型,即 T <: T
    • Nothing 类型永远是其他任意类型 T 的子类型,即 Nothing <: T
    • 任意类型 T 都是 Any 类型的子类型,即 T <: Any
    • 任意 class 定义的类型都是 Object 的子类型,即如果有 class C {},则 C <: Object
  6. 传递性带来的子类型关系

类型转换

仓颉不支持不同类型之间的隐式转换(子类型天然是父类型,所以子类型到父类型的转换不是隐式类型转换),类型转换必须显式地进行。

open class Base {}
class Sub <: Base {}

main() {
    let b: Base = Sub() // 子类型天然是父类型
    let x: Float64 = 100 // 错误,不支持隐式转换
    return 0
}
  1. 数值类型之间的转换

    对于数值类型(包括:Int8Int16Int32Int64IntNativeUInt8UInt16UInt32UInt64UIntNativeFloat16Float32Float64),仓颉支持使用 T(e) 的方式得到一个值等于 e,类型为 T 的值。

    main() {
        let x: IntNative = 100
        let y = Float64(x)
        println(x)
        println(y)
    }
    
  2. Rune到UInt32和整数类型到Rune的转换

    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操作符

仓颉支持使用 is 操作符来判断某个表达式的类型是否是指定的类型(或其子类型)。具体而言,对于表达式 e is Te 可以是任意表达式,T 可以是任何类型),当 e 的运行时类型是 T 的子类型时,e is T 的值为 true,否则 e is T 的值为 false

open class A {}
class B <: A {}

main() {
    let aa: A = A()
    let ab: A = B()
    let bb: B = B()
    // let ba: B = A() // error
    // 子类就是父类
    println("aa is A = ${aa is A}")
    println("aa is B = ${aa is B}")
    println("ab is A = ${ab is A}")
    println("ab is B = ${ab is B}")
    println("bb is A = ${bb is A}")
    println("bb is B = ${bb is B}")
}
// aa is A = true
// aa is B = false
// ab is A = true
// ab is B = true
// bb is A = true
// bb is B = true

as 操作符可以用于将某个表达式的类型转换为指定的类型。因为类型转换有可能会失败,所以 as 操作返回的是一个 Option 类型。具体而言,对于表达式 e as Te 可以是任意表达式,T 可以是任何类型),当 e 的运行时类型是 T 的子类型时,e as T 的值为 Option<T>.Some(e),否则 e as T 的值为 Option<T>.None

Collention类型概述

  • Array:不需要增加和删除元素,但需要修改元素
  • ArrayList:需要频繁对元素增删查改
  • HashSet:希望每个元素都是唯一的
  • HashMap:希望存储一系列的映射关系

(学这个主要是用于替代C++中的vector、set、map等类)

类型名称元素可变增删元素元素唯一性有序序列
ArrayYNNY
ArrayListYYNY
HashSetNYYN
HashMap<K, V>K: N, V: YYK: Y, V: NN

ArrayList

仓颉使用 ArrayList 表示 ArrayList 类型,T 表示 ArrayList 的元素类型,T 可以是任意类型。

    let a = ArrayList<String>()
    let b = ArrayList<String>(100)
    let c = ArrayList<Int64>([1, 2, 3, 4, 5])
    let d = ArrayList<Int64>(c)
    let e = ArrayList<String>(2, {x: Int64 => x.toString()})

访问ArrayList成员

使用for-in循环遍历。

main(){
    let list = ArrayList<String>(5, {x: Int64 => x.toString()})
    println("Size is ${list.size})
    for(i in list) {
        println("The item is: ${i}")
    }
}
// The item is: 0
// The item is: 1
// The item is: 2
// The item is: 3
// The item is: 4

修改ArrayList

可以使用下标语法对某个位置的元素进行修改。ArrayList 是引用类型,ArrayList 在作为表达式使用时不会拷贝副本,同一个 ArrayList 实例的所有引用都会共享同样的数据。

main(){
    let list = ArrayList<Int64>([1, 2, 3, 4, 5])
    for(i in 0..5) {
        list[i] += 1
    }
    println(list)
}
// [2, 3, 4, 5, 6]

使用add函数在末尾添加元素,使用add(all!: Collection)同时添加多个元素。

main(): Int64 {
    let list = ArrayList<Int64>([0, 1, 2, 3, 4])
    list.add(all: [6, 6, 6])
    println(list)
    return 0
}
// [0, 1, 2, 3, 4, 6, 6, 6]

可以通过 add(T, at!: Int64) 和 add(all!: Collection<T>, at!: Int64) 函数将指定的单个元素或相同元素类型的 Collection 值插入到指定索引的位置。该索引处的元素和后面的元素会被挪后以腾出空间。

从 ArrayList 中删除元素,可以使用 remove 函数,需要指定删除的索引。该索引处后面的元素会前移以填充空间。

main(): Int64 {
    let list = ArrayList<Int64>([0, 1, 2, 3, 4])
    list.add(all: [6, 6, 6], at: 2)
    println(list)
    list.remove(at: 2)
    println(list)
    return 0
}
// [0, 1, 6, 6, 6, 2, 3, 4]
// [0, 1, 6, 6, 2, 3, 4]

HashSet

main(): Int64 {
    let hs = HashSet<Int64>(10, {x: Int64 => (x * x)})
    println(hs)
    return 0
}
// [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

修改HashSet

HashSet 是一种可变的引用类型,HashSet 类型提供了添加元素、删除元素的功能。

使用 add 函数添加元素,使用remove函数删除指定的元素。

main(): Int64 {
    let hs = HashSet<Int64>(10, {x: Int64 => x})
    for(i in 1..5) {
        hs.add(i + 10)
        hs.remove(i)
    }
    println(hs)
    return 0
}

HashMap

HashMap 是一种哈希表,提供对其包含的元素的快速访问。表中的每个元素都使用其键作为标识,可以使用键来访问相应的值。

仓颉使用 HashMap<K, V> 表示 HashMap 类型,K 表示 HashMap 的键类型,K 必须是实现了 Hashable 和 Equatable<K> 接口的类型,例如数值或 String。V 表示 HashMap 的值类型,V 可以是任意类型。

main(): Int64 {
    let hm = HashMap<String, Int64>()
    hm["one"] = 1
    hm["two"] = 2
    hm["three"] = 3
    println(hm)
    return 0
}
// [(one, 1), (two, 2), (three, 3)]

访问HashMap成员

用for-in循环访问HashMap成员

main(): Int64 {
    let hm = HashMap<String, Int64>()
    hm["one"] = 1
    hm["two"] = 2
    hm["three"] = 3
    for((k, v) in hm) {
        println("key is ${k}, value is ${v}")
    }
    for(kv in hm) {
        println("key is ${kv[0]}, value is ${kv[1]}")
    }
    return 0
}

修改HashMap

使用下标语法对某个键对应的值进行修改。

如果希望同时添加多个键值对,可以使用 add(all!: Collection<(K, V)>) 函数。当键不存在时,add 函数会执行添加的操作,当键存在时,add 函数会将新的值覆盖旧的值。也可以使用赋值的方式直接将新的键值对添加到 HashMap。使用remove函数指定删除的键。

main(): Int64 {
    let hm = HashMap<String, Int64>()
    hm["one"] = 1
    hm["two"] = 2
    hm["three"] = 3
    hm.add("two", 666)
    println(hm)
    hm.remove("three")
    println(hm)
    return 0
}
// [(one, 1), (two, 666), (three, 3)]
// [(one, 1), (two, 666)]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值