加载中...
仓颉文档阅读-语言规约VII: 属性

仓颉文档阅读-语言规约VII: 属性

周六 10月 11 2025
2697 字 · 17 分钟

此样式内容, 表示文档原文内容

属性

属性是一种特殊的语法, 它不像字段一样会存储值, 相反它们提供了一个getter和一个可选的setter来间接检索和设置值

通过使用属性可以将数据操作封装成访问函数, 使用的时候与普通字段无异, 我们只需要对数据操作, 对内部的实现无感知, 可以更便利地实现访问控制、数据监控、跟踪调试、数据绑定等机制

属性在使用时语法与字段一致, 可以作为表达式或被赋值

以下是一个简单的例子, b是一个典型的属性, 封装了外部对a的访问:

CANGJIE
class Foo {
    private var a = 0
    mut prop b: Int64 {
        get() {
            print("get")
            a
        }
        set(value) {
            print("set")
            a = value
        }
    }
}

main() {
    var x = Foo()
    let y = x.b + 1   // get
    x.b = y           // set
}

属性, 是C++中没有的概念

从简单介绍来看, 属性可以用来操作成员变量, 且提供有gettersetter, 用于对属性取值以及赋值

属性的语法

属性的语法规则为:

PLAINTEXT
propertyDefinition
    : propertyModifier* 'prop' identifier ':' type propertyBody?
    ;

propertyBody
    : '{' propertyMemberDeclaration+ '}'
    ;

propertyMemberDeclaration
    : 'get' '(' ')' block end*
    | 'set' '(' identifier ')' block end*
    ;

propertyModifier
    : 'public'
    | 'private'
    | 'protected'
    | 'internal'
    | 'static'
    | 'open'
    | 'override'
    | 'redef'
    | 'mut'
    ;

一个属性基本的定义长这样:

CANGJIE
class PropTest {
    mut prop prop1: Int64 {
        get() {}
        set() {}
    }
}

还可以使用publicopenstatic等其他修饰符修饰

没有mut修饰符声明的属性需要定义getter实现

mut修饰符声明的属性需要单独的gettersetter实现

特别是, 对于数值类型、BoolUnitNothingRuneStringRangeFunctionEnumTuple类型, 在它们的扩展或定义体中不能定义mut修饰的属性, 也不能实现有mut属性的接口

如下面的示例所示, a是没有mut声明的属性, b是使用mut声明的属性

CANGJIE
class Foo {
    prop a: Int64 {
        get() {
            0
        }
    }
    mut prop b: Int64 {
        get() {
            0
        }
        set(v) {}
    }
}

没有mut声明的属性没有setter, 而且与用let声明的字段一样, 它们不能被赋值

CANGJIE
class A {
    prop i: Int64 {
        get() {
            0
        }
    }
}
main() {
    var x = A()
    x.i = 1           // error
}

特别是, 当使用let声明struct的实例时, 不能为struct中的属性赋值, 就像用let声明的字段一样

CANGJIE
struct A {
    var i1 = 0
    mut prop i2: Int64 {
        get() {
            i1
        }
        set(value) {
            i1 = value
        }
    }
}
main() {
    let x = A()
    x.i1 = 2          // error
    x.i2 = 2          // error
}

属性与字段不同, 属性不可以赋初始值, 必须要声明类型

属性不是字段, 即 不是变量, 它不能存储值, 不能给初始值, 所以必须要声明类型

虽然不能存储值, 但是要声明类型, 是为了getter返回目标类型数据, 以及setter的传参类型, 即 决定了访问属性时获取的数据类型, 以及赋值时参与计算的数据类型

属性是否用mut修饰, 决定此属性是否需要实现setter以及此属性是否允许被赋值

let实例化的strcut, 即使属性为mut, 也禁止修改属性

基础类型在扩展时, 不能定义mut属性, 也不能实现拥有mut属性的接口, 因为基础类型都是值类型

属性的定义

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

CANGJIE
class A {
    prop i: Int64 {
        get() {
            0
        }
    }
}

struct B {
    prop i: Int64 {
        get() {
            0
        }
    }
}

enum C {
    prop i: Int64 {
        get() {
            0
        }
    }
}

extend A {
    prop s: String {
        get() {
            ""
        }
    }
}

可以在interfaceabstract class中声明抽象属性, 它的定义体可以省略

在实现类型中实现抽象属性时, 它必须保持相同的名称、相同的类型和相同的mut修饰符

CANGJIE
interface I {
    prop a: Int64
}

class A <: I {
    public prop a: Int64 {
        get() {
            0
        }
    }
}

如同interface中的抽象函数可以拥有默认实现, interface中的抽象属性也同样可以拥有默认实现

拥有默认实现的抽象属性, 实现类型可以不提供自己的实现(必须符合默认实现的使用规则)

CANGJIE
interface I {
    prop a: Int64 {   // ok
        get() {
            0
        }
    }
}
class A <: I {}       // ok

接口和抽象类中可以声明抽象属性, 但在子类或子接口中实现时, 必须要与原抽象属性声明保持完全一致

属性分为实例成员属性和静态成员属性

其中, 实例成员属性只能由实例访问, 在gettersetter的实现中可以访问this、实例成员和其它静态成员

而静态成员属性只能访问静态成员

CANGJIE
class A {
    var x = 0
    mut prop X: Int64 {
        get() {
            x + y
        }
        set(v) {
            x = v + y
        }
    }
    static var y = 0
    static mut prop Y: Int64 {
        get() {
            y
        }
        set(v) {
            y = v
        }
    }
}

属性不支持重载, 也不支持遮盖, 不能和其它同级别成员重名

CANGJIE
open class A {
    var i = 0
    prop i: Int64 {       // error
        get() {
            0
        }
    }
}
class B <: A {
    prop i: Int64 {       // error
        get() {
            0
        }
    }
}

静态属性的gettersetter中 只能访问静态成员

普通属性的gettersetter中 也可以访问静态成员, 同时可以访问this

属性的名字不能与其他成员存在任何冲突, 也不支持重载, 也不支持遮盖, 但是支持覆盖

属性的实现

属性的gettersetter分别对应两个不同的函数

  • getter函数类型是()->T, T是该属性的类型, 当使用该属性作为表达式时会执行getter函数

  • setter函数类型是(T)->Unit, T是该属性的类型, 形参名需要显式指定, 当对该属性赋值时会执行setter函数

属性的实现同函数的实现规则一样, 其中可以包含声明和表达式, 可以省略return, 其返回值必须符合返回类型

CANGJIE
class Foo {
    mut prop a: Int64 {
        get() {           // () -> Int64
            "123"         // error
        }
        set(v) {          // (Int64) -> Unit
            123
        }
    }
}

无论在属性内部还是外部, 访问属性的行为都是一致的, 因此属性递归访问时与函数一样可能会造成死循环

CANGJIE
class Foo {
    prop i: Int64 {
        get() {
            i             // dead loop
        }
    }
}

需要注意的是, structsettermut函数, 因此也可以在setter内部修改其它字段的值, 并且this会受到mut函数的限制

属性声明的类型, 决定了settergetter函数的类型

禁止在getter函数内访问属性本身, 禁止在setter函数内给属性本身赋值, 会出现无限递归

属性的修饰符

属性跟函数一样可以使用修饰符修饰, 但只允许对整个属性修饰, 不能对gettersetter独立修饰

CANGJIE
class Foo {
    public mut prop a: Int64 {        // ok
        get() {
            0
        }
        set(v) {}
    }
    mut prop b: Int64 {
        public get() {                // error
            0
        }
        public set(v) {}              // error
    }
}

属性可以使用访问控制修饰符有private, protected, public

CANGJIE
class Foo {
    private prop a: Int64 {           // ok
        get() { 0 }
    }
    protected prop b: Int64 {         // ok
        get() { 0 }
    }
    public static prop c: Int64 {     // ok
        get() { 0 }
    }
}

实例属性像实例函数一样, 可以使用openoverride修饰

使用open修饰的属性, 子类型可以使用override覆盖父类型的实现(override是可选的)

CANGJIE
open class A {
    public open mut prop i: Int64 {
        get() { 0 }
        set(v) {}
    }
}
class B <: A {
    override mut prop i: Int64 {
        get() { 1 }
        set(v) {}
    }
}

静态属性像静态函数一样, 可以使用redef修饰(redef是可选的), 子类型可以重新实现父类型的静态属性

CANGJIE
open class A {
    static mut prop i: Int64 {
        get() { 0 }
        set(v) {}
    }
}
class B <: A {
    redef static mut prop i: Int64 {
        get() { 1 }
        set(v) {}
    }
}

子类型override/redef使用let声明的实例属性必须要重新实现getter

子类型override/redef父类型中使用mut修饰符声明的的属性时, 允许只重新实现gettersetter, 但不能均不重新实现

CANGJIE
open class A {
    public open mut prop i1: Int64 {
        get() { 0 }
        set(v) {}
    }
    static mut prop i2: Int64 {
        get() { 0 }
        set(v) {}
    }
}

// case 1
class B <: A {
    public override mut prop i1: Int64 {
        get() { 1 }                       // ok
    }
    redef static mut prop i2: Int64 {
        get() { 1 }                       // ok
    }
}

// case 2
class B <: A {
    public override mut prop i1: Int64 {
        set(v) {}                         // ok
    }
    redef static mut prop i2: Int64 {
        set(v) {}                         // ok
    }
}

// case 3
class B <: A {
    override mut prop i1: Int64 {}        // error
    redef static mut prop i2: Int64 {}    // error
}

子类型的属性override/redef必须与父类保持相同的mut修饰符, 并且还必须保持相同的类型

CANGJIE
class P {}
class S {}

open class A {
    open prop i1: P {
        get() { P() }
    }
    static prop i2: P {
        get() { P() }
    }
}

// case 1
class B <: A {
    override mut prop i1: P {         // error
        set(v) {}
    }
    redef static mut prop i2: P {     // error
        set(v) {}
    }
}
// case 2
class B <: A {
    override prop i1: S {             // error
        get() { S() }
    }
    redef static prop i2: S {         // error
        get() { S() }
    }
}

我不理解这一句话: 子类型override/redef使用let声明的实例属性必须要重新实现getter

  1. 属性并不能被letvar修饰

  2. 经测试, 就算父类型属性基于let成员变量, 如果不显式覆盖, 也可以直接不重新实现属性

  3. 假设要显式覆盖属性, 就必须要重新实现getter, 那跟letvar也没有任何关系

所以我不是很理解, 这一句话是什么意思?

子类override父类的属性时, 可以使用super调用父类的实例属性

CANGJIE
open class A {
    open prop v: Int64 {
        get() { 1 }
    }
}
class B <: A {
    override prop v: Int64 {
        get() { super.v + 1 }
    }
}

其实不仅是通过super访问父类的实例属性, 还能访问其他成员


Thanks for reading!

仓颉文档阅读-语言规约VII: 属性

周六 10月 11 2025
2697 字 · 17 分钟