仓颉编程入门:类和接口

面向对象编程的四大核心特性:封装、继承、多态、抽象,相信已经深入大多开发者的骨髓。仓颉中同样支持使用 class 来实现面向对象编程。

上篇学习了结构类型struct,class的成员变量、成员属性、静态初始化器、构造函数、成员函数和操作符函数定义和规则大部分和struct一样,这篇主要介绍class独有的一些特性。

class定义

class 的成员变量、静态初始化器、普通构造函数、主构造函数、成员函数、访问修饰符和struct的规则一样,可以参考struct的介绍。

抽象类

使用 abstract 修饰的类为抽象类,与普通类不同的是,在抽象类中除了可以定义普通的函数,还允许声明抽象函数(没有函数体)。抽象类定义时的 open 修饰符是可选的,也可以使用 sealed 修饰符修饰抽象类,表示该抽象类只能在本包被继承。

abstract sealed  class People{ //抽象类修饰符 open sealed 可选
    var name:String=''
    static let country:String
    static init(){    // 静态初始化器
        country = '中国'
    }
    People(age:Int64,var height:Int64){} //主构造函数
    func eat(){
        println('吃东西')
    }
    static func think():String{
        '思考中。。。'
    }
    public func study():Unit //抽象类中可以定义抽象函数,必须是public
}

注意

1.抽象类中禁止定义 private 的抽象函数;
2.不能为抽象类创建实例;
3.抽象类的非抽象子类必须实现父类中的所有抽象函数。

class成员函数

struct中的成员函数不能修改成员变量,需要使用mut修饰符才可以修改,在class中可以直接修改

class Student <: People{
    Student(age:Int64, height:Int64){
        super(age,height)
    }
   public func study(){
        println('仓颉')
    }
    public func setName(){ //直接可以修改变量,不需要mut修饰
        this.name='仓颉'
    }
}
main() {
    var p1 = Student(20,180)
    p1.setName()
    println(p1.name)  //仓颉
}

class 终结器

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

class C {
    var p: CString

    init(s: String) {
        p = unsafe { LibC.mallocCString(s) }
        println(s)
    }
    ~init() {
        unsafe { LibC.free(p) }
    }
}

注意

1.终结器没有参数,没有返回类型,没有泛型类型参数,没有任何修饰符,也不可以被显式调用。
2.一个类最多只能定义一个终结器。
3.更多注意事项可以官网了解

创建对象

不同于 struct,对象在赋值或传参时,不会将对象进行复制,多个变量指向的是同一个对象,通过一个变量去修改对象中成员的值,其他变量中对应的成员变量也会被修改。

struct SPoint{
    var x:Int64=0
    mut func set(){
        this.x=4
    }
}
class CPoint{
    var x:Int64=0
    public func set(){
        this.x=4
    }
}
main() {
    var s1 = SPoint()
    var s2 = s1  //struct类型赋值
    s2.set()    //修改s2的成员变量
    println(s1.x)  //0  s1没有变化
    var c1 = CPoint()
    var c2=c1     //class类型赋值
    c2.set()       //修改c2的成员变量
    println(c1.x) //4  c1也跟着变化
}

class的继承

子类将继承父类中除 private 成员和构造函数以外的所有成员。
class 仅支持单继承。
抽象类可以使用 sealed 修饰符,表示被修饰的类定义只能在本定义所在的包内被其他类继承。
sealed 已经蕴含了 public/open 的语义,因此定义 sealed abstract class 时若提供 public/open 修饰符,编译器将会告警。
sealed 的子类可以不是 sealed 类,仍可被 open/sealed 修饰,或不使用任何继承性修饰符。
若 sealed 类的子类被 open 修饰,则其子类可在包外被继承。
sealed 的子类可以不被 public 修饰。

父构造函数调用

子类的 init 构造函数可以使用 super(args) 的形式调用父类构造函数,或使用 this(args) 的形式调用本类其他构造函数,但两者之间只能调用一个。
子类的主构造函数中,可以使用 super(args) 的形式调用父类构造函数,但不能使用 this(args) 的形式调用本类其他构造函数。

覆盖和重定义

继承的使用和Java基本一致,需要注意几个关键字。
覆盖时,要求父类中的成员函数使用 open 修饰,子类中的同名函数使用 override 修饰,其中 override 是可选的。

sealed abstract class People{ //抽象类修饰符 open sealed 可选
    var name:String=''
    static let country:String
    static init(){    // 静态初始化器
        country = '中国'
    }
    People(age:Int64,var height:Int64){} //主构造函数
    func eat(){
        println('吃东西')
    }
    static func think():String{
        '思考中。。。'
    }
    public func study():Unit //抽象类中可以定义抽象函数,必须是public

    public open func  run(){ //'open' function must be 'public' or 'protected'
        println('跑步')
    }
}
class Student <: People{
    Student(age:Int64, height:Int64){
        super(age,height)
    }
   public func study(){
        println('仓颉')
    }
    public func setName(){ //直接可以修改变量,不需要mut修饰
        this.name='仓颉'
    }
    // func eat(){} //the parent function is not modified by 'open'
    public func run(){
        println('奔跑')
    }
}

对于静态函数,子类中可以重定义父类中的同名非抽象静态函数,调用时将根据 class 的类型决定调用的版本。

接口

仓颉的接口和Java的定义和使用基本一样,接口的成员可以包含:
成员函数、操作符重载函数、成员属性
这些成员都是抽象的,要求实现类型必须拥有对应的成员实现。

接口定义

接口的成员可以是实例的或者静态的,都要求实现类型提供实现。
接口中的静态成员函数(或属性)可以没有默认实现,也可以拥有默认实现

 sealed interface 接口{
    func 实例方法():Unit
    static func 静态方法():Unit
    static func 静态方法有实现(){
        println('实现类可以不实现')
    }
}

接口的成员默认就被 public 修饰,不可以声明额外的访问控制修饰符,同时也要求实现类型必须使用 public 实现

接口继承

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

interface I1 {
   func f(a: Int64) {
        a
   }
   static func g(a: Int64) {
        a
   }
   func f1(a: Int64): Unit
   static func g1(a: Int64): Unit
}

interface I2 <: I1 {
    func f(a: Int64) { //父接口有默认实现,子接口也必须有实现方法
       a + 1
    }
    static func g(a: Int64) {
       a + 1
    }
    func f1(a: Int64): Unit //父接口没有默认实现,子接口可有实现也可没有
    static func g1(a: Int64): Unit 
}

接口实现

当想为一个类型实现多个接口,可以在声明处使用 & 分隔多个接口,实现的接口之间没有顺序要求。

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

interface Subtractable {
    func sub(other: Int64): Int64
}

class MyInt <: Addable & Subtractable {
    var value = 0
    public func add(other: Int64): Int64 {
        value + other
    }
    public func sub(other: Int64): Int64 {
        value - other
    }
}

接口实现的要求

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

Any类型

仓颉中所有接口都默认继承它,所有非接口类型都默认实现它,因此所有类型都可以作为 Any 类型的子类型使用。和Java的Object类型相似。

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

属性

属性(Properties)提供了一个 getter 和一个可选的 setter 来间接获取和设置值。
使用属性的时候与普通变量无异,只需要对数据操作,对内部的实现无感知,可以更便利地实现访问控制、数据监控、跟踪调试、数据绑定等机制。

class Foo {
    private var a = 0
    public mut prop b: Int64 {
        get() {
            println("get")
            a
        }
        set(value) {
            println("set")
            a = value
        }
    }
    public func setA(value:Int64){
        this.a =value
    }
    public func getA(){
        return a
    }
}
main() {
    var foo = Foo()
    foo.b = 1  //set
    println(foo.b) //get 1
    foo.setA(2)
    println(foo.getA())//2
}

属性定义

属性可以在 interface、class、struct、enum、extend 中定义。
属性的 getter 和 setter 分别对应两个不同的函数。
getter 函数类型是 () -> T,T 是该属性的类型,当使用该属性作为表达式时会执行 getter 函数。
setter 函数类型是 (T) -> Unit,T 是该属性的类型,形参名需要显式指定,当对该属性赋值时会执行 setter 函数。

修饰符

和成员函数一样,成员属性也支持 open、override、redef 修饰,所以也可以在子类型中覆盖/重定义父类型属性的实现。

open class A {
    private var valueX = 0
    private static var valueY = 0

    public open prop x: Int64 {
        get() { valueX }
    }

    public static mut prop y: Int64 {
        get() { valueY }
        set(v) {
            valueY = v
        }
    }
}
class B <: A {
    private var valueX2 = 0
    private static var valueY2 = 0

    public override prop x: Int64 {
        get() { valueX2 }
    }

    public redef static mut prop y: Int64 {
        get() { valueY2 }
        set(v) {
            valueY2 = v
        }
    }
}

抽象属性

在 interface 和抽象类中也可以声明抽象属性,这些抽象属性没有实现。
当实现类型实现 interface 或者非抽象子类继承抽象类时,必须要实现这些抽象属性。

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
        }
    }
}

属性使用

属性分为实例成员属性和静态成员属性。成员属性的使用和成员变量的使用方式一样。无 mut 修饰符的属性类似 let 声明的变量,不能被赋值。

子类型关系

仓颉语言中,有些预设的子类型关系是永远成立的:
一个类型 T 永远是自身的子类型,即 T <: T。
Nothing 类型永远是其他任意类型 T 的子类型,即 Nothing <: T。
任意类型 T 都是 Any 类型的子类型,即 T <: Any。
任意 class 定义的类型都是 Object 的子类型,即如果有 class C {},则 C <: Object。

类型转换

类型转换必须显式地进行。

数值类型之间的转换

对于数值类型(包括:Int8,Int16,Int32,Int64,IntNative,UInt8,UInt16,UInt32,UInt64,UIntNative,Float16,Float32,Float64),仓颉支持使用 T(e) 的方式得到一个值等于 e,类型为 T 的值。其中,表达式 e 的类型和 T 可以是上述任意数值类型。

main() {
    let a: Int8 = 10
    let b: Int16 = 20
    let r1 = Int16(a)
    let r2 = Int8(b)

    let c: Float32 = 1.0
    let d: Float64 = 1.123456789
    let r3 = Float64(c)
    let r4 = Float32(d)

    let e: Int64 = 1024
    let f: Float64 = 1024.1024
    let r5 = Float64(e)
    let r6 = Int64(f)
}

Rune 到 UInt32 的转换和整数类型到 Rune 的转换

Rune 到 UInt32 的转换使用 UInt32(e) 的方式,其中 e 是一个 Rune 类型的表达式,UInt32(e) 的结果是 e 的 Unicode scalar value 对应的 UInt32 类型的整数值。
整数类型到 Rune 的转换使用 Rune(num) 的方式,其中 num 的类型可以是任意的整数类型,且仅当 num 的值落在Unicode scalar value中时。

main() {
    let x: Rune = 'a'
    let y: UInt32 = 65
    let r1 = UInt32(x)
    let r2 = Rune(y)
    println(r1) //97
    println(r2) //A
}

is和as操作符

仓颉支持使用 is 操作符来判断某个表达式的类型是否是指定的类型(或其子类型)。
对于表达式 e is T(e 可以是任意表达式,T 可以是任何类型),当 e 的运行时类型是 T 的子类型时,e is T 的值为 true,否则 e is T 的值为 false。
as 操作符可以用于将某个表达式的类型转换为指定的类型。
对于表达式 e as T(e 可以是任意表达式,T 可以是任何类型),当 e 的运行时类型是 T 的子类型时,e as T 的值为 Option<T>.Some(e),否则 e as T 的值为 Option<T>.None。

main() {
    let a = 1 is Int64  // true
    let b = 1 is String //false
    let c = (1==2) is Any //true
    let d = '12' as Int64 //None
    let e =  12 as Int64 //Some(12)
}
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容