类
class 与 struct 的主要区别在于:class 是引用类型,struct 是值类型,它们在赋值或传参时行为是不同的;class 之间可以继承,但 struct 之间不能继承。
class 定义
class 类型的定义以关键字 class 开头,后跟 class 的名字,接着是定义在一对花括号中的 class 定义体。
class Rectangle {
let width: Int64
let height: Int64
public init(width: Int64, height: Int64) {
this.width = width
this.height = height
}
public func area() {
width * height
}
}
class 成员变量
- class 成员变量分为实例成员变量和静态成员变量,静态成员变量使用 static 修饰符修饰,必须有初值,只能通过类型名访问
class Rectangle {
let width = 10
static let height = 20
}
let l = Rectangle.height // l = 20
- 实例成员变量定义时可以不设置初值(但必须标注类型),也可以设置初值,只能通过对象(即类的实例)访问
class Rectangle {
let width = 10
let height: Int64
init(h: Int64){
height = h
}
}
let rec = Rectangle(20)
let l = rec.height // l = 20
class 静态初始化器
class 支持定义静态初始化器,并在静态初始化器中通过赋值表达式来对静态成员变量进行初始化。
- 静态初始化器以关键字组合 static init 开头,后跟无参参数列表和函数体,且不能被访问修饰符修饰。函数体中必须完成对所有未初始化的静态成员变量的初始化,否则编译报错。
class Rectangle {
static let degree: Int64
static init() {
degree = 180
}
}
- 一个 class 中最多允许定义一个静态初始化器,否则报重定义错误。
class Rectangle {
static let degree: Int64
static init() {
degree = 180
}
static init() { // Error, redefinition with the previous static init function
degree = 180
}
}
class 构造函数
和 struct 一样,class 中也支持定义普通构造函数和主构造函数。
- 普通构造函数以关键字 init 开头,后跟参数列表和函数体,函数体中必须完成所有未初始化实例成员变量的初始化,否则编译报错。
class Rectangle {
let width: Int64
let height: Int64
public init(width: Int64, height: Int64) { // Error, 'height' is not initialized in the constructor
this.width = width
}
}
- 一个类中可以定义多个普通构造函数,但它们必须构成重载,否则报重定义错误。
class Rectangle {
let width: Int64
let height: Int64
public init(width: Int64) {
this.width = width
this.height = width
}
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 可以简化为如下定义
class Rectangle {
public Rectangle(let width: Int64, let height: Int64) {}
}
基本上会用这种方式,这和swift什么的是差不多的。减少变量定义,减少this,更简洁一点。
- 主构造函数的参数列表中也可以定义普通形参
class Rectangle {
public Rectangle(name: String, let width: Int64, let height: Int64) {} // name没有对应的成员变量,纯粹是供转化的原材料
}
- 如果 class 定义中不存在自定义构造函数(包括主构造函数),并且所有实例成员变量都有初值,则会自动为其生成一个无参构造函数(调用此无参构造函数会创建一个所有实例成员变量的值均等于其初值的对象);否则,不会自动生成此无参构造函数。
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。终结器一般被用于释放系统资源
class C {
var p: CString
init(s: String) {
p = unsafe { LibC.mallocCString(s) }
println(s)
}
~init() {
unsafe { LibC.free(p) }
}
}
类似析构函数,供系统调用,行为不可控。不常用,主要用来释放资源。
class 成员函数
class 成员函数同样分为实例成员函数和静态成员函数(使用 static 修饰符修饰),实例成员函数只能通过对象访问,静态成员函数只能通过 class 类型名访问;静态成员函数中不能访问实例成员变量,也不能调用实例成员函数,但在实例成员函数中可以访问静态成员变量以及静态成员函数。
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 进行修饰。
abstract class AbRectangle {
public func foo(): Unit
}
- 非抽象函数必须有函数体,在函数体中可以通过 this 访问实例成员变量
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 类型名调用构造函数)。
- 创建对象之后,可以通过对象访问(public 修饰的)实例成员变量和实例成员函数。
let r = Rectangle(10, 20) // r.width = 10, r.height = 20
let width = r.width // width = 10
let height = r.height // height = 20
let a = r.area() // a = 200
- 如果希望通过对象去修改成员变量的值(不鼓励这种方式,最好还是通过成员函数去修改),需要将 class 类型中的成员变量定义为可变成员变量(即使用 var 定义)。
class Rectangle {
public var width: Int64
public var height: Int64
...
}
main() {
let r = Rectangle(10, 20) // r.width = 10, r.height = 20
r.width = 8 // r.width = 8
r.height = 24 // r.height = 24
let a = r.area() // a = 192
}
- 不同于 struct,对象在赋值或传参时,不会将对象进行复制,多个变量指向的是同一个对象,通过一个变量去修改对象中成员的值,其他变量中对应的成员变量也会被修改。以赋值为例,下面的例子中,将 r1 赋值给 r2 之后,修改 r1 的 width 和 height 的值,r2 的 width 和 height 值也同样会被修改。
main() {
var r1 = Rectangle(10, 20) // r1.width = 10, r1.height = 20
var r2 = r1 // r2.width = 10, r2.height = 20
r1.width = 8 // r1.width = 8
r1.height = 24 // r1.height = 24
let a1 = r1.area() // a1 = 192
let a2 = r2.area() // a2 = 192
}
class 的继承
像大多数支持 class 的编程语言一样,仓颉中的 class 同样支持继承。如果类 B 继承类 A,则我们称 A 为父类,B 为子类。子类将继承父类中除 private 成员和构造函数以外的所有成员。
抽象类总是可被继承的,故抽象类定义时的 open 修饰符是可选的,也可以使用 sealed 修饰符修饰抽象类,表示该抽象类只能在本包被继承。
但非抽象的类可被继承是有条件的:定义时必须使用修饰符 open 修饰。当带 open 修饰的实例成员被 class 继承时,该 open 的修饰符也会被继承。当非 open 修饰的类中存在 open 修饰的成员时,编译器会给出告警。
可以在子类定义处通过 <: 指定其继承的父类,但要求父类必须是可继承的。
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 仅支持单继承,因此下面这样一个类继承两个类的代码是不合法的
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
}
因为类是单继承的,所以任何类都最多只能有一个直接父类。对于定义时指定了父类的 class,它的直接父类就是定义时指定的类,对于定义时未指定父类的 class,它的直接父类是 Object 类型。Object 是所有类的父类(注意,Object 没有直接父类,并且 Object 中不包含任何成员)。
因为子类是继承自父类的,所以子类的对象天然可以当做父类的对象使用,但是反之不然。
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 定义的类型不允许继承类型本身。
class A <: A {} // Error, 'A' inherits itself.
sealed 修饰符只能修饰抽象类,表示被修饰的类定义只能在本定义所在的包内被其他类继承。sealed 已经蕴含了 public/open 的语义,因此定义 sealed abstract class 时若提供 public/open 修饰符,编译器将会告警。
sealed 的子类可以不是 sealed 类,仍可被 open/sealed 修饰,或不使用任何继承性修饰符。若 sealed 类的子类被 open 修饰,则其子类可在包外被继承。sealed 的子类可以不被 public 修饰。
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.
父类构造函数调用
子类的 init 构造函数可以使用 super(args) 的形式调用父类构造函数,或使用 this(args) 的形式调用本类其它构造函数,但两者之间只能调用一个。如果调用,必须在构造函数体内的第一个表达式处,在此之前不能有任何表达式或声明。
open class A {
A(let a: Int64) {}
}
class B <: A {
let b: Int64
init(b: Int64) {
super(30)
this.b = b
}
}
- 如果子类的构造函数没有显式调用父类构造函数,也没有显式调用其他构造函数,编译器会在该构造函数体的开始处插入直接父类的无参构造函数的调用。如果此时父类没有无参构造函数,则会编译报错;
open class A {
let a: Int64
init() {
a = 100
}
}
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 是可选的。
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() // a 的运行时类型是 A,因此 a.f() 调用的是父类 A 中的函数 f
b.f() // b 的运行时类型是 B(编译时类型是 A),因此 b.f() 调用的是子类 B 中的函数 f
}
/* 程序结果输出:
I am superclass
I am subclass
*/
- 对于静态函数,子类中可以重定义父类中的同名非抽象静态函数,即在子类中为父类中的某个静态函数定义新的实现。重定义时,要求子类中的同名静态函数使用 redef 修饰,其中 redef 是可选的。
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()
}
/* 程序结果输出:
I am class C
I am class D
*/
- 如果抽象函数或 open 修饰的函数有命名形参,那么实现函数或 override 修饰的函数也需要保持同样的命名形参。
open class A {
public open func f(a!: Int32): Int32 {
a + 1
}
}
class B <: A {
public override func f(a!: Int32): Int32 { // Ok
a + 2
}
}
class C <: A {
public override func f(b!: Int32): Int32 { // Error
b + 3
}
}
main() {
B().f(a: 0)
C().f(b: 0)
}
- 当实现或重定义的函数为泛型函数时,子类型函数的类型变元约束需要比父类型中对应函数更宽松或相同。
open class A {}
open class B <: A {}
open class C <: B {}
open class Base {
static func f<T>(a: T): Unit where T <: B {}
static func g<T>(): Unit where T <: B {}
}
class D <: Base {
redef static func f<T>(a: T): Unit where T <: C {} // Error, stricter constraint
redef static func g<T>(): Unit where T <: C {} // Error, stricter constraint
}
class E <: Base {
redef static func f<T>(a: T): Unit where T <: A {} // OK: looser constraint
redef static func g<T>(): Unit where T <: A {} // OK: looser constraint
}
class F <: Base {
redef static func f<T>(a: T): Unit where T <: B {} // OK: same constraint
redef static func g<T>(): Unit where T <: B {} // OK: same constraint
}
接口
接口用来定义一个抽象类型,它不包含数据,但可以定义类型的行为。一个类型如果声明实现某接口,并且实现了该接口中所有的成员,就被称为实现了该接口。
接口定义
接口使用关键字 interface 声明,其后是接口的标识符和接口的成员。接口成员可被 open 修饰符修饰,并且 open 修饰符是可选的。
interface I { // 'open' modifier is optional.
func f(): Unit
}
class Foo <: I {
public func f(): Unit {
println("Foo")
}
}
main() {
let a = Foo()
let b: I = a // 当某个类型实现了某个接口之后,该类型就会成为该接口的子类型。Foo 是 I 的子类型,因此任何一个 Foo 类型的实例,都可以当作 I 类型的实例使用。
b.f() // "Foo"
}
/* 程序结果输出:
Foo
*/
interface 也可以使用 sealed 修饰符表示只能在 interface 定义所在的包内继承、实现或扩展该 interface。
sealed 已经蕴含了 public/open 的语义,因此定义 sealed interface 时若提供 public/open 修饰符,编译器将会告警。继承 sealed 接口的子接口或实现 sealed 接口的类仍可被 sealed 修饰或不使用 sealed 修饰。若 sealed 接口的子接口被 public 修饰,且不被 sealed 修饰,则其子接口可在包外被继承、实现或扩展。继承、实现 sealed 接口的类型可以不被 public 修饰。
package A
public interface I1 {}
sealed interface I2 {} // OK
public sealed interface I3 {} // Warning, redundant modifier, 'sealed' implies 'public'
sealed open interface I4 {} // Warning, redundant modifier, 'sealed' implies 'open'
class C1 <: I1 {}
public open class C2 <: I1 {}
sealed class C3 <: I2 {}
extend Int64 <: I2 {}
package B
import A.*
class S1 <: I1 {} // OK
class S2 <: I2 {} // Error, I2 is sealed interface, cannot be inherited here.
- 通过接口的这种约束能力,我们可以对一系列的类型约定共同的功能,达到对功能进行抽象的目的。
interface Flyable {
func fly(): Unit
}
class Bird <: Flyable {
public func fly(): Unit {
println("Bird flying")
}
}
class Bat <: Flyable {
public func fly(): Unit {
println("Bat flying")
}
}
class Airplane <: Flyable {
public func fly(): Unit {
println("Airplane flying")
}
}
func fly(item: Flyable): Unit {
item.fly()
}
main() {
let bird = Bird()
let bat = Bat()
let airplane = Airplane()
fly(bird)
fly(bat)
fly(airplane)
}
/* 程序结果输出:
Bird flying
Bat flying
Airplane flying
*/
- 静态成员函数和实例成员函数类似,都要求实现类型提供实现。
interface NamedType {
static func typename(): String
}
class A <: NamedType {
public static func typename(): String {
"A"
}
}
class B <: NamedType {
public static func typename(): String {
"B"
}
}
main() {
println("the type is ${ A.typename() }")
println("the type is ${ B.typename() }")
}
/* 程序结果输出:
the type is A
the type is B
*/
- 接口中的静态成员函数(或属性)也可以拥有默认实现,当另一个类型继承拥有默认静态函数(或属性)实现的接口时,该类型可以不再实现这个静态成员函数(或属性),该函数(或属性)可以通过接口名和该类型名直接访问。
interface NamedType {
static func typename(): String {
"interface NamedType"
}
}
class A <: NamedType {}
main() {
println(NamedType.typename())
println(A.typename())
0
}
/* 程序结果输出:
interface NamedType
interface NamedType
*/
- 通常我们会通过泛型约束,在泛型函数中使用这类静态成员。
interface NamedType {
static func typename(): String
}
interface I <: NamedType {
static func typename(): String {
f()
}
static func f(): String
}
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.
}
- 接口的成员默认就被 public 修饰,不可以声明额外的访问控制修饰符,同时也要求实现类型必须使用 public 实现。
interface I {
func f(): Unit
}
open class C <: I {
protected func f() {} // Compiler Error, f needs to be public semantics
}
接口继承
- 当我们想为一个类型实现多个接口,可以在声明处使用 & 分隔多个接口,实现的接口之间没有顺序要求。
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
}
}
- 接口可以继承一个或多个接口,但不能继承类。与此同时,接口继承的时候可以添加新的接口成员。
interface Addable {
func add(other: Int64): Int64
}
interface Subtractable {
func sub(other: Int64): Int64
}
interface Calculable <: Addable & Subtractable {
func mul(other: Int64): Int64
func div(other: Int64): Int64
}
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 的继承,子接口如果继承了父接口中有默认实现的函数或属性,则在子接口中不允许仅写此函数或属性的声明(即没有默认实现),而是必须要给出新的默认实现,并且函数定义前的 override 修饰符(或 redef 修饰符)是可选的;子接口如果继承了父接口中没有默认实现的函数或属性,则在子接口中允许仅写此函数或属性的声明(当然也允许定义默认实现),并且函数声明或定义前的 override 修饰符(或 redef 修饰符)是可选的。
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 {}
}
接口实现
仓颉所有的类型都可以实现接口,包括数值类型、Rune、String、struct、class、enum、Tuple、函数以及其它类型。
- 实现类型声明实现接口时,需要实现接口中要求的所有成员,为此需要满足下面一些规则。
- 对于成员函数和操作符重载函数,要求实现类型提供的函数实现与接口对应的函数名称相同、参数列表相同、返回类型相同。
- 对于成员属性,要求是否被 mut 修饰保持一致,并且属性的类型相同。
- 如果接口中的成员函数或操作符重载函数的返回值类型是 class 类型,那么允许实现函数的返回类型是其子类型。
open class Base {}
class Sub <: Base {}
interface I {
func f(): Base
}
class C <: I {
public func f(): Sub {
Sub() // f 返回类型可以是 Base 的子类型 Sub
}
}
- 接口的成员还可以为 class 类型提供默认实现。拥有默认实现的接口成员,当实现类型是 class 的时候,class 可以不提供自己的实现而继承接口的实现。
默认实现只对类型是 class 的实现类型有效,对其它类型无效。
interface SayHi {
func say() {
"hi"
}
}
class A <: SayHi {}
class B <: SayHi {
public func say() {
"hi, B"
}
}
- 如果一个类型在实现多个接口时,多个接口中包含同一个成员的默认实现,这时会发生多重继承的冲突,语言无法选择最适合的实现,因此这时接口中的默认实现也会失效,需要实现类型提供自己的实现。
interface SayHi {
func say() {
"hi"
}
}
interface SayHello {
func say() {
"hello"
}
}
class Foo <: SayHi & SayHello {
public func say() { // SayHi 和 SayHello 中都包含了 say 的实现,Foo 在实现这两个接口时就必须提供自己的实现,否则会出现编译错误。
"Foo"
}
}
- struct、enum 和 class 在实现接口时,函数或属性定义前的 override 修饰符(或 redef 修饰符)是可选的,无论接口中的函数或属性是否存在默认实现。
interface I {
func foo(): Int64 {
return 0
}
}
enum E <: I{
elem
public override func foo(): Int64 {
return 1
}
}
struct S <: I {
public override func foo(): Int64 {
return 1
}
}
Any 类型
Any 类型是一个内置的接口,它的定义如下面。
interface Any {}
- 仓颉中所有接口都默认继承它,所有非接口类型都默认实现它,因此所有类型都可以作为 Any 类型的子类型使用。
/// 我们可以将一系列不同类型的变量赋值给 Any 类型的变量。
main() {
var any: Any = 1
any = 2.0
any = "hello, world!"
}
这个其实就是动态语言,JS的强项。用起来是舒服了,毫无限制。但是危险性也增加了,类型检查就废了。所以,不到万不得已,慎用。
属性
属性(Properties)提供了一个 getter 和一个可选的 setter 来间接获取和设置值。
使用属性的时候与普通变量无异,我们只需要对数据操作,对内部的实现无感知,可以更便利地实现访问控制、数据监控、跟踪调试、数据绑定等机制。
属性在使用时可以作为表达式或被赋值。此处以类和接口为例进行说明,但属性不仅限于类和接口。
class Foo {
private var a = 0
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
}
/* 程序结果输出:
get
set
*/
属性定义
属性可以在 interface、class、struct、enum、extend 中定义。
getter 函数类型是 () -> T,T 是该属性的类型,当使用该属性作为表达式时会执行 getter 函数。
setter 函数类型是 (T) -> Unit,T 是该属性的类型,形参名需要显式指定,当对该属性赋值时会执行 setter 函数。
setter 中的参数对应的是赋值时传入的值。
class Foo {
private var j = 0
public mut prop i: Int64 {
get() {
j
}
set(v) {
j = v
}
}
}
修饰符
和成员函数一样,成员属性也支持 open、override、redef 修饰,所以我们也可以在子类型中覆盖/重定义父类型属性的实现。
- 子类型覆盖父类型的属性时,如果父类型属性带有 mut 修饰符,则子类型属性也需要带有 mut 修饰符,同时也必须保持一样的类型。
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 或者非抽象子类继承抽象类时,必须要实现这些抽象属性。
与覆盖的规则一样,实现类型或子类在实现这些属性时,如果父类型属性带有 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
}
}
}
- 通过抽象属性,我们可以让接口和抽象类对一些数据操作能以更加易用的方式进行约定,相比函数的方式要更加直观。
interface I1 {
mut prop size: Int64
}
interface I2 {
func getSize(): Int64
func setSize(value: Int64): Unit
}
class C <: I1 & I2 {
private var mySize = 0
public mut prop size: Int64 {
get() {
mySize
}
set(value) {
mySize = value
}
}
public func getSize() {
mySize
}
public func setSize(value: Int64) {
mySize = value
}
}
main() {
let a: I1 = C()
a.size = 5
println(a.size)
let b: I2 = C()
b.setSize(5)
println(b.getSize())
}
/* 程序结果输出:
5
5
*/
属性使用
- 属性分为实例成员属性和静态成员属性。成员属性的使用和成员变量的使用方式一样
class A {
public prop x: Int64 {
get() {
123
}
}
public static prop y: Int64 {
get() {
321
}
}
}
main() {
var a = A()
println(a.x) // 123
println(A.y) // 321
}
/* 程序结果输出:
123
321
*/
- 无 mut 修饰符的属性类似 let 声明的变量,不可以被赋值。
class A {
private let value = 0
public prop i: Int64 {
get() {
value
}
}
}
main() {
var x = A()
println(x.i) // OK
x.i = 1 // Error
}
- 带有 mut 修饰符的属性类似 var 声明的变量,可以取值也可以被赋值。
class A {
private var value: Int64 = 0
public mut prop i: Int64 {
get() {
value
}
set(v) {
value = v
}
}
}
main() {
var x = A()
println(x.i) // OK
x.i = 1 // OK
}
子类型关系
与其他面向对象语言一样,仓颉语言提供子类型关系和子类型多态。举例说明(不限于下述用例):
假设函数的形参是类型 T,则函数调用时传入的参数的实际类型既可以是 T 也可以是 T 的子类型(严格地说,T 的子类型已经包括 T 自身,下同)。
假设赋值表达式 = 左侧的变量的类型是 T,则 = 右侧的表达式的实际类型既可以是 T 也可以是 T 的子类型。
假设函数定义中用户标注的返回类型是 T,则函数体的类型(以及函数体内所有 return 表达式的类型)既可以是 T 也可以是 T 的子类型。
继承 class 带来的子类型关系
继承 class 后,子类即为父类的子类型。
open class Super { }
class Sub <: Super { }
实现接口带来的子类型关系
实现接口(含扩展实现)后,实现接口的类型即为接口的子类型。
interface I1 { }
interface I2 { }
interface I3 <: I1 & I2 { } // I3 是 I1 和 I2 的子类型
class C <: I1 { } // C 是 I1 的子类型
extend Int64 <: I2 { } // Int64 是 I2 的子类型
- 需要注意的是,部分跨扩展类型赋值后的类型向下转换场景(is 或 as)暂不支持,可能出现判断失败
// file1.cj
package p1
public class A{}
public func get(): Any {
return A()
}
// =====================
// file2.cj
import p1.*
interface I0 {}
extend A <: I0 {}
main() {
let v: Any = get()
println(v is I0) // 无法正确判断类型,打印内容不确定
}
元组类型的子类型关系
仓颉语言中的元组类型也有子类型关系。直观的,如果一个元组 t1 的每个元素的类型都是另一个元组 t2 的对应位置元素类型的子类型,那么元组 t1 的类型也是元组 t2 的类型的子类型。
open class C1 { }
class C2 <: C1 { }
open class C3 { }
class C4 <: C3 { }
let t1: (C1, C3) = (C2(), C4()) // OK
let t2: (C3, C1) = (C4(), C2()) // OK
函数类型的子类型关系
仓颉语言中,函数是一等公民,而函数类型亦有子类型关系:给定两个函数类型 (U1) -> S2 和 (U2) -> S1,(U1) -> S2 <: (U2) -> S1 当且仅当 U2 <: U1 且 S2 <: S1(注意顺序)
open class U1 { }
class U2 <: U1 { }
open class S1 { }
class S2 <: S1 { }
func f(a: U1): S2 { S2() }
func g(a: U2): S1 { S1() }
func call1() {
g(U2()) // Ok.
f(U2()) // Ok.
}
func h(lam: (U2) -> S1): S1 {
lam(U2())
}
func call2() {
h(g) // Ok.
h(f) // Ok.
}
永远成立的子类型关系
一个类型 T 永远是自身的子类型,即 T <: T。
Nothing 类型永远是其他任意类型 T 的子类型,即 Nothing <: T。
任意类型 T 都是 Any 类型的子类型,即 T <: Any。
任意 class 定义的类型都是 Object 的子类型,即如果有 class C {},则 C <: Object。
传递性带来的子类型关系
子类型关系具有传递性。如下代码中,虽然只描述了 I2 <: I1,C <: I2,以及 Bool <: I2,但根据子类型的传递性,也隐式存在 C <: I1 以及 Bool <: I1 这两个子类型关系。
interface I1 { }
interface I2 <: I1 { }
class C <: I2 { }
extend Bool <: I2 { }
类型转换
仓颉不支持不同类型之间的隐式转换(子类型天然是父类型,所以子类型到父类型的转换不是隐式类型转换),类型转换必须显式地进行。
数值类型之间的转换
对于数值类型(包括: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}")
}
/* 程序结果输出:
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 和整数类型到 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 的值)或运行时抛异常。
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}")
}
/* 程序结果输出:
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}")
}
/* 程序结果输出:
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。
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)