public init(width: Int64, height: Int64) { // Ok: overloading with the first init function this.width = width this.height = height }
public init(height: Int64) { // Error, redefinition with the first init function this.width = height this.height = height } }
除了可以定义若干普通的以 init 为名字的构造函数外,class 内还可以定义(最多)一个主构造函数。主构造函数的名字和 class 类型名相同,它的参数列表中可以有两种形式的形参:普通形参和成员变量形参(需要在参数名前加上 let 或 var),成员变量形参同时具有定义成员变量和构造函数参数的功能。
使用主构造函数通常可以简化 class 的定义,例如,上述包含一个 init 构造函数的 Rectangle 可以简化为如下定义:
1 2 3
class Rectangle { public Rectangle(let width: Int64, let height: Int64) {} }
主构造函数的参数列表中也可以定义普通形参,例如:
1 2 3
class Rectangle { public Rectangle(name: String, let width: Int64, let height: Int64) {} }
class B <: A { var x = foo(0) init() { x = foo(1) println("init B finished") } }
main() { B() 0 }
上述例子中,调用 B 的构造函数时,首先初始化有缺省值的变量 x,此时 foo(0) 被调用;之后调用父类的无参构造函数,此时 A 的构造函数被调用;接下来执行构造函数体内的代码,此时 foo(1) 被调用,并打印字符串。因此上例的输出为:
1 2 3 4
I'm foo, got 0 I'm A I'm foo, got 1 init B finished
如果 class 定义中不存在自定义构造函数(包括主构造函数),并且所有实例成员变量都有初始值,则会自动为其生成一个无参构造函数(调用此无参构造函数会创建一个所有实例成员变量的值均等于其初值的对象);否则,不会自动生成此无参构造函数。例如,对于如下 class 定义,编译器会为其自动生成一个无参构造函数:
1 2 3 4 5 6 7 8 9 10 11 12 13
class Rectangle { let width = 10 let height = 20
/* Auto-generated parameterless constructor: public init() {
} */ }
// Invoke the auto-generated parameterless constructor let r = Rectangle() // r.width = 10,r.height = 20
class 终结器
class 支持定义终结器,这个函数在类的实例被垃圾回收的时候被调用。终结器的函数名固定为 ~init。终结器通常用于释放系统资源:
main() : Int64 { var i : Int64 = 0 while (i < 100) { if (foo() != 0) { print("fail: obj is freed before gc!") return 1 } gc(heavy: true) // blocking gc expected // wait ~init() to be executed while (Test.t0 != 0) { // error, this is undefined behavior continue } i++ } return 0 }
class 成员函数
class 成员函数同样分为实例成员函数和静态成员函数(使用 static 修饰符修饰),实例成员函数只能通过对象访问,静态成员函数只能通过 class 类型名访问;静态成员函数中不能访问实例成员变量,也不能调用实例成员函数,但在实例成员函数中可以访问静态成员变量以及静态成员函数。
下例中,area 是实例成员函数,typeName 是静态成员函数。
1 2 3 4 5 6 7 8 9 10 11 12
class Rectangle { let width: Int64 = 10 let height: Int64 = 20
public func area() { this.width * this.height }
public static func typeName(): String { "Rectangle" } }
根据是否有函数体,实例成员函数又可以分为抽象成员函数和非抽象成员函数。抽象成员函数没有函数体,只能定义在抽象类或接口(详见接口)中。需要注意的是,抽象实例成员函数默认具有 open 的语义,open 修饰符是可选的,且必须使用 public 或 protected 进行修饰。
非抽象函数必须有函数体,在函数体中可以通过 this 访问实例成员变量,例如:
1 2 3 4 5 6 7 8
class Rectangle { let width: Int64 = 10 let height: Int64 = 20
public func area() { this.width * this.height } }
class 成员的访问修饰符
对于 class 的成员(包括成员变量、成员属性、构造函数、成员函数),可以使用的访问修饰符有 4 种访问修饰符修饰:private、internal、protected 和 public,缺省的含义是 internal。
package a public open class Rectangle { public var width: Int64 protected var height: Int64 private var area: Int64 public init(width: Int64, height: Int64) { this.width = width this.height = height this.area = this.width * this.height } init(width: Int64, height: Int64, multiple: Int64) { this.width = width this.height = height this.area = width * height * multiple } }
func samePkgFunc() { var r = Rectangle(10, 20) // Ok: constructor 'Rectangle' can be accessed here r.width = 8 // Ok: public 'width' can be accessed here r.height = 24 // Ok: protected 'height' can be accessed here r.area = 30 // Error, private 'area' cannot be accessed here } package b import a.* public class Cuboid <: Rectangle { private var length: Int64 public init(width: Int64, height: Int64, length: Int64) { super(width, height) this.length = length } public func volume() { this.width * this.height * this.length // Ok: protected 'height' can be accessed here } }
main() { var r = Rectangle(10, 20, 2) // Error, Rectangle has no `public` constructor with three parameters var c = Cuboid(20, 20, 20) c.width = 8 // Ok: public 'width' can be accessed here c.height = 24 // Error, protected 'height' cannot be accessed here c.area = 30 // Error, private 'area' cannot be accessed here }
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 类型后,即可通过调用其构造函数来创建对象(通过 class 类型名调用构造函数)。例如,下例中通过 Rectangle(10, 20) 创建 Rectangle 类型的对象并赋值给变量 r。创建对象之后,可以通过对象访问(public 修饰的)实例成员变量和实例成员函数。例如,下例中通过 r.width 和 r.height 可分别访问 r 中 width 和 height 的值,通过 r.area() 可以调用成员函数 area。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
class Rectangle { let width: Int64 let height: Int64
像大多数支持 class 的编程语言一样,仓颉中的 class 同样支持继承。如果类 B 继承类 A,则称 A 为父类,B 为子类。子类将继承父类中除 private 成员和构造函数以外的所有成员。
抽象类总是可被继承的,故抽象类定义时的 open 修饰符是可选的,也可以使用 sealed 修饰符修饰抽象类,表示该抽象类只能在本包被继承。但非抽象的类可被继承是有条件的:定义时必须使用修饰符 open 修饰。当带 open 修饰的实例成员被 class 继承时,该 open 的修饰符也会被继承。当非 open 修饰的类中存在 open 修饰的成员时,编译器会给出告警。
可以在子类定义处通过 <: 指定其继承的父类,但要求父类必须是可继承的。例如,下面的例子中,class A 使用 open 修饰,是可以被类 B 继承的,但是因为类 B 是不可继承的,所以 C 在继承 B 的时候会报错。
1 2 3 4 5 6 7 8 9 10 11
open class A { let a: Int64 = 10 }
class B <: A { // Ok: 'B' Inheritance 'A' let b: Int64 = 20 }
class C <: B { // Error, 'B' is not inheritable let c: Int64 = 30 }
class 仅支持单继承,因此下面这样一个类继承两个类的代码是不合法的(& 是类实现多个接口时的语法,详见接口)。
1 2 3 4 5 6 7 8 9 10 11
open class A { let a: Int64 = 10 }
open class B { let b: Int64 = 20 }
class C <: A & B { // Error, 'C' can only inherit one class let c: Int64 = 30 }
因为子类是继承自父类的,所以子类的对象天然可以当做父类的对象使用,但是反之不然。例如,下例中 B 是 A 的子类,那么 B 类型的对象可以赋值给 A 类型的变量,但是 A 类型的对象不能赋值给 B 类型的变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
open class A { let a: Int64 = 10 }
class B <: A { let b: Int64 = 20 }
let a: A = B() // Ok: subclass objects can be assigned to superclass variables open class A { let a: Int64 = 10 }
class B <: A { let b: Int64 = 20 }
let b: B = A() // Error, superclass objects can not be assigned to subclass variables
class 定义的类型不允许继承类型本身。
1
class A <: A {} // Error, 'A' inherits itself.
抽象类可以使用 sealed 修饰符,表示被修饰的类定义只能在本定义所在的包内被其他类继承。sealed 已经蕴含了 public/open 的语义,因此定义 sealed abstract class 时若提供 public/open 修饰符,编译器将会告警。sealed 的子类可以不是 sealed 类,仍可被 open/sealed 修饰,或不使用任何继承性修饰符。若 sealed 类的子类被 open 修饰,则其子类可在包外被继承。sealed 的子类可以不被 public 修饰。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
package A public sealed abstract class C1 {} // Warning, redundant modifier, 'sealed' implies 'public' sealed open abstract class C2 {} // Warning, redundant modifier, 'sealed' implies 'open' sealed abstract class C3 {} // OK, 'public' is optional when 'sealed' is used
class S1 <: C1 {} // OK public open class S2 <: C1 {} // OK public sealed abstract class S3 <: C1 {} // OK open class S4 <: C1 {} // OK package B import A.*
class SS1 <: S2 {} // OK class SS2 <: S3 {} // Error, S3 is sealed class, cannot be inherited here. sealed class SS3 {} // Error, 'sealed' cannot be used on non-abstract class.
open class B <: A { let b: Int64 init(b: Int64) { // OK, `super()` added by compiler this.b = b } }
open class C <: B { let c: Int64 init(c: Int64) { // Error, there is no non-parameter constructor in super class this.c = c } }
覆盖和重定义
子类中可以覆盖(override)父类中的同名非抽象实例成员函数,即在子类中为父类中的某个实例成员函数定义新的实现。覆盖时,要求父类中的成员函数使用 open 修饰,子类中的同名函数使用 override 修饰,其中 override是可选的。例如,下面的例子中,子类 B 中的函数 f 覆盖了父类 A 中的函数 f。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
open class A { public open func f(): Unit { println("I am superclass") } }
class B <: A { public override func f(): Unit { println("I am subclass") } }
main() { let a: A = A() let b: A = B() a.f() b.f() }
对于被覆盖的函数,调用时将根据变量的运行时类型(由实际赋给该变量的对象决定)确定调用的版本(即所谓的动态派发)。例如,上例中 a 的运行时类型是 A,因此 a.f() 调用的是父类 A 中的函数 f;b 的运行时类型是 B(编译时类型是 A),因此 b.f() 调用的是子类 B 中的函数 f。所以程序会输出:
1 2
I am superclass I am subclass
对于静态函数,子类中可以重定义父类中的同名非抽象静态函数,即在子类中为父类中的某个静态函数定义新的实现。重定义时,要求子类中的同名静态函数使用 redef 修饰,其中 redef是可选的。例如,下面的例子中,子类 D 中的函数 foo 重定义了父类 C 中的函数 foo。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
open class C { public static func foo(): Unit { println("I am class C") } }
class D <: C { public redef static func foo(): Unit { println("I am class D") } }
main() { C.foo() D.foo() }
对于被重定义的函数,调用时将根据 class 的类型决定调用的版本。例如,上例中 C.foo() 调用的是父类 C 中的函数 foo,D.foo() 调用的是子类 D 中的函数 foo。
1 2
I am class C I am class D
如果抽象函数或 open 修饰的函数有命名形参,那么实现函数或 override 修饰的函数也需要保持同样的命名形参。
open class A {} open class B <: A {} open class C <: B {}
open class Base { public open func foo<T>(a: T): Unit where T <: B {} public open func bar<T>(a: T): Unit where T <: B {} public static func f<T>(a: T): Unit where T <: B {} public static func g<T>(): Unit where T <: B {} }
class D <: Base { public override func foo<T>(a: T): Unit where T <: C {} // Error, stricter constraint public override func bar<T>(a: T): Unit where T <: C {} // Error, stricter constraint public redef static func f<T>(a: T): Unit where T <: C {} // Error, stricter constraint public redef static func g<T>(): Unit where T <: C {} // Error, stricter constraint }
class E <: Base { public override func foo<T>(a: T): Unit where T <: A {} // OK: looser constraint public override func bar<V>(a: V): Unit where V <: A {} // OK: looser constraint, names of generic parameters do not matter public redef static func f<T>(a: T): Unit where T <: A {} // OK: looser constraint public redef static func g<T>(): Unit where T <: A {} // OK: looser constraint }
class F <: Base { public override func foo<T>(a: T): Unit where T <: B {} // OK: same constraint public override func bar<V>(a: V): Unit where V <: B {} // OK: same constraint public redef static func f<T>(a: T): Unit where T <: B {} // OK: same constraint public redef static func g<T>(): Unit where T <: B {} // OK: same constraint }
class A <: NamedType { public static func typename(): String { "A" } }
class B <: NamedType { public static func typename(): String { "B" } }
func printTypeName<T>() where T <: NamedType { println("the type is ${ T.typename() }") }
main() { printTypeName<A>() // Ok printTypeName<B>() // Ok printTypeName<I>() // Error, 'I' must implement all static function. Otherwise, an unimplemented 'f' is called, causing problems. }
接口中可以定义泛型实例成员函数或泛型静态成员函数,与非泛型函数一样具有 open 语义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
import std.collection.* interface M { func foo<T>(a: T): T static func toString<T>(b: ArrayList<T>): String where T <: ToString } class C <: M { public func foo<S>(a: S): S { // implements M::foo, names of generic parameters do not matter a } public static func toString<T>(b: ArrayList<T>) where T <: ToString { var res = "" for (s in b) { res += s.toString() } res } }
需要注意的是,接口的成员默认就被 public 修饰,不可以声明额外的访问控制修饰符,同时也要求实现类型必须使用 public 实现。
1 2 3 4 5 6 7
interface I { func f(): Unit }
open class C <: I { protected func f() {} // Compiler Error, f needs to be public semantics }
class MyInt <: Addable & Subtractable { var value = 0 public func add(other: Int64): Int64 { value + other } public func sub(other: Int64): Int64 { value - other } }
class MyInt <: Calculable { var value = 0 public func add(other: Int64): Int64 { value + other } public func sub(other: Int64): Int64 { value - other } public func mul(other: Int64): Int64 { value * other } public func div(other: Int64): Int64 { value / other } }
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 { /*'override' is optional*/ func f(a: Int64) { a + 1 } override func f(a: Int32) {} // Error, override function 'f' does not have an overridden function from its supertypes static /*'redef' is optional*/ func g(a: Int64) { a + 1 } /*'override' is optional*/ func f1(a: Int64): Unit {} static /*'redef' is optional*/ func g1(a: Int64): Unit {} }
public mut prop b: Int64 { get() { println("get") a } set(value) { println("set") a = value } } }
main() { var x = Foo() let y = x.b + 1 // get x.b = y // set }
此处 Foo 提供了一个名为 b 的属性,针对 getter/setter 这两个功能,仓颉提供了 get 和 set 两种语法来定义。当一个类型为 Foo 的变量 x 在访问 b 时,会调用 b 的 get 操作返回类型为 Int64 的值,因此可以用来与 1 相加;而当 x 在对 b 进行赋值时,会调用 b 的 set 操作,将 y 的值传给 set 的 value,最终将 value 的值赋值给 a。
通过属性 b,外部对 Foo 的成员变量 a 完全不感知,但却可以通过 b 做到同样地访问和修改操作,实现了有效的封装性。所以程序的输出如下:
1 2
get set
属性定义
属性可以在 interface、class、struct、enum、extend 中定义。
一个典型的属性语法结构如下:
1 2 3 4 5 6 7 8 9
class Foo { public prop a: Int64 { get() { 0 } } public mut prop b: Int64 { get() { 0 } set(v) {} } }
其中使用 prop 声明的 a 和 b 都是属性,a 和 b 的类型都是 Int64。a 是无 mut 修饰符的属性,这类属性有且仅有定义 getter(对应取值)实现。b 是使用 mut 修饰的属性,这类属性必须分别定义 getter(对应取值)和 setter(对应赋值)的实现。
仓颉不支持不同类型之间的隐式转换(子类型天然是父类型,所以子类型到父类型的转换不是隐式类型转换),类型转换必须显式地进行。下面将依次介绍数值类型之间的转换,Rune 到 UInt32 和整数类型到 Rune 的转换,以及 is 和 as 操作符。
数值类型之间的转换
对于数值类型(包括: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) println("The type of r1 is 'Int16', and r1 = ${r1}") let r2 = Int8(b) println("The type of r2 is 'Int8', and r2 = ${r2}")
let c: Float32 = 1.0 let d: Float64 = 1.123456789 let r3 = Float64(c) println("The type of r3 is 'Float64', and r3 = ${r3}") let r4 = Float32(d) println("The type of r4 is 'Float32', and r4 = ${r4}")
let e: Int64 = 1024 let f: Float64 = 1024.1024 let r5 = Float64(e) println("The type of r5 is 'Float64', and r5 = ${r5}") let r6 = Int64(f) println("The type of r6 is 'Int64', and r6 = ${r6}") }
上述代码的执行结果为:
1 2 3 4 5 6
The type of r1 is 'Int16', and r1 = 10 The type of r2 is 'Int8', and r2 = 20 The type of r3 is 'Float64', and r3 = 1.000000 The type of r4 is 'Float32', and r4 = 1.123457 The type of r5 is 'Float64', and r5 = 1024.000000 The type of r6 is 'Int64', and r6 = 1024
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 的值)或运行时抛异常。
下面的例子展示了 Rune 和 UInt32 之间的类型转换:
1 2 3 4 5 6 7 8
main() { let x: Rune = 'a' let y: UInt32 = 65 let r1 = UInt32(x) let r2 = Rune(y) println("The type of r1 is 'UInt32', and r1 = ${r1}") println("The type of r2 is 'Rune', and r2 = ${r2}") }
上述代码的执行结果为:
1 2
The type of r1 is 'UInt32', and r1 = 97 The type of r2 is 'Rune', and r2 = A
is 和 as 操作符
仓颉支持使用 is 操作符来判断某个表达式的类型是否是指定的类型(或其子类型)。具体而言,对于表达式 e is T(e 可以是任意表达式,T 可以是任何类型),当 e 的运行时类型是 T 的子类型时,e is T 的值为 true,否则 e is T 的值为 false。
open class Base { var name: String = "Alice" } class Derived <: Base { var age: UInt8 = 18 }
main() { let a = 1 is Int64 println("Is the type of 1 'Int64'? ${a}") let b = 1 is String println("Is the type of 1 'String'? ${b}")
let b1: Base = Base() let b2: Base = Derived() var x = b1 is Base println("Is the type of b1 'Base'? ${x}") x = b1 is Derived println("Is the type of b1 'Derived'? ${x}") x = b2 is Base println("Is the type of b2 'Base'? ${x}") x = b2 is Derived println("Is the type of b2 'Derived'? ${x}") }
上述代码的执行结果为:
1 2 3 4 5 6
Is the type of 1 'Int64'? true Is the type of 1 'String'? false Is the type of b1 'Base'? true Is the type of b1 'Derived'? false Is the type of b2 'Base'? true Is the type of b2 'Derived'? true
as 操作符可以用于将某个表达式的类型转换为指定的类型。因为类型转换有可能会失败,所以 as 操作返回的是一个 Option 类型。具体而言,对于表达式 e as T(e 可以是任意表达式,T 可以是任何类型),当 e 的运行时类型是 T 的子类型时,e as T 的值为 Option<T>.Some(e),否则 e as T 的值为 Option<T>.None。
下面的例子展示了 as 操作符的使用(注释中标明了 as 操作的结果):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
open class Base { var name: String = "Alice" } class Derived <: Base { var age: UInt8 = 18 }
let a = 1 as Int64 // a = Option<Int64>.Some(1) let b = 1 as String // b = Option<String>.None
let b1: Base = Base() let b2: Base = Derived() let d: Derived = Derived() let r1 = b1 as Base // r1 = Option<Base>.Some(b1) let r2 = b1 as Derived // r2 = Option<Derived>.None let r3 = b2 as Base // r3 = Option<Base>.Some(b2) let r4 = b2 as Derived // r4 = Option<Derived>.Some(b2) let r5 = d as Base // r5 = Option<Base>.Some(d) let r6 = d as Derived // r6 = Option<Derived>.Some(d)