news 2026/10/14 6:40:28

仓颉语言属性(prop)完全指南:getter/setter 定义、抽象属性与继承覆盖实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
仓颉语言属性(prop)完全指南:getter/setter 定义、抽象属性与继承覆盖实战
  • 文档
  • 编程语言

【免费下载链接】cangjie_docs

仓颉编程语言开发者文档。

项目地址:https://gitcode.com/Cangjie/cangjie_docs
点击查看免费下载

属性(Properties)是仓颉语言中用于间接获取和设置值的语言机制:它通过一个 getter 和可选的 setter 对外暴露数据访问接口,而调用方使用属性时与使用普通变量无异。本文以仓颉开发者文档 prop.md 为骨架,系统讲解属性的定义语法、getter/setter 函数类型语义、mut修饰规则、修饰符体系、抽象属性与覆盖/重定义规则,并结合class、struct、enum、扩展与反射等仓库文档,给出可直接编译运行的完整示例。读完本文,你将能够熟练地在类、接口、结构体、枚举与扩展中定义属性,设计出兼具封装性与可读性的数据访问接口。

属性是什么:用 getter/setter 封装数据访问

属性提供了一个 getter 和一个可选的 setter,用来间接获取和设置值。使用属性的时候与普通变量无异——你只需要对数据操作,对内部的实现无感知。这种间接性带来了四个典型收益:访问控制、数据监控、跟踪调试、数据绑定。例如,可以在 getter/setter 中插入日志、校验、缓存或通知逻辑,而调用方无需感知这些内部细节。

属性在使用时可以作为表达式(触发 getter)或被赋值(触发 setter)。文档以类和接口为例进行说明,但属性不仅限于类和接口,还可在struct、enum、extend中定义。

下面是一个最典型的属性示例:b封装了外部对私有成员a的访问。

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 }

这里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做到同样的访问和修改操作,实现了有效的封装性。程序输出如下:

get set

属性的定义与语法结构

属性可以在interface、class、struct、enum、extend中定义。一个典型的属性语法结构如下:

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

其中使用prop声明的a和b都是属性,类型都是Int64。这里可以提炼出两条强制规则:

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

注意:对于数值、元组、函数、Bool、Unit、Nothing、String、Range和enum类型,在它们的扩展和声明中不能声明mut修饰的属性,也不能实现带mut属性的接口。也就是说,上述这些类型只能承载只读属性。

从关键字表(见 keyword.md)可以看到,prop是仓颉的保留关键字,不能作为标识符使用,属性的声明统一由它引出。

getter 与 setter 的函数类型语义

属性的 getter 和 setter 分别对应两个不同的函数,二者的函数类型是固定的:

  1. getter 函数类型是() -> T,其中T是该属性的类型。当使用该属性作为表达式时会执行 getter 函数;
  2. setter 函数类型是(T) -> Unit,其中T是该属性的类型,形参名需要显式指定。当对该属性赋值时会执行 setter 函数,setter 中的形参对应赋值时传入的值。

getter 和 setter 的实现中,可以像函数体一样包含声明和表达式,与函数体的规则一致(函数体规则详见 define_functions.md#函数体:函数体通常包含变量定义和表达式,其类型为最后一项的类型,若最后一项为表达式,函数体类型即该表达式的类型;若最后一项为变量定义或函数声明,或函数体为空,则类型为Unit)。因此,getter 的末尾直接写表达式即为其返回值。

下面这个示例展示了 setter 形参v如何承接赋值传入的值:

class Foo { private var j = 0 public mut prop i: Int64 { get() { j } set(v) { j = v } } }

需要注意一个陷阱:在属性的 getter 和 setter 中访问属性自身属于递归调用,与函数调用一样可能会出现死循环。例如在b的 getter 中写b(而不是a),会不断触发 getter 自身,导致无限递归。正确的做法是在 getter/setter 内部访问真正承载数据的成员变量。

属性修饰符:访问控制、静态属性与继承覆盖

可以在prop前面声明需要的修饰符。属性支持访问修饰符(如public、private),也支持static、open、override、redef等。

访问修饰符

和成员函数一样,属性可以用public、private等访问修饰符控制可见性:

class Foo { public prop a: Int64 { get() { 0 } } private prop b: Int64 { get() { 0 } } }

open / override / redef:在子类型中覆盖与重定义

和成员函数一样,成员属性也支持open、override、redef修饰,因此可以在子类型中覆盖/重定义父类型属性的实现。覆盖规则有一条硬性约束:

子类型覆盖父类型的属性时,如果父类型属性带有mut修饰符,则子类型属性也需要带有mut修饰符,同时也必须保持一样的类型。

如下代码所示,A中定义了x和y两个属性,B中可以分别对x和y进行override/redef(static属性使用redef,实例属性使用override):

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 static redef mut prop y: Int64 { get() { valueY2 } set(v) { valueY2 = v } } }

注意父类A需要open修饰才可被继承,父类中需要被覆盖的属性用open标记;子类中实例属性用override,静态属性用redef,且mut一致性、类型一致性都必须保持。

静态属性

属性还分为实例成员属性和静态成员属性。静态属性使用static修饰,通过类型名访问,其定义、使用规则与class的静态成员变量一致(成员变量规则详见 class.md#class-成员变量)。

抽象属性:接口与抽象类的数据操作约定

类似于抽象函数,在interface和抽象类中也可以声明抽象属性——这些抽象属性没有实现,只保留签名:

interface I { prop a: Int64 } abstract class C { public prop a: Int64 }

当实现类型实现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 } } }

抽象属性让接口和抽象类对一些数据操作能以更加易用的方式进行约定,相比函数的方式更直观。下面是对同一个size值的获取和设置进行约定时,属性方式(I1)与函数方式(I2)的对比——属性方式代码更少,也更符合对数据操作的意图:

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

同样的语义,属性方式(a.size = 5/println(a.size))比函数方式(b.setSize(5)/println(b.getSize()))更贴近"对数据进行操作"的意图,同时代码量更少。

属性的使用:实例属性、静态属性与 let/var 语义

属性分为实例成员属性和静态成员属性。成员属性的使用方式和成员变量的使用方式一样:实例属性通过对象访问,静态属性通过类型名访问(成员变量访问规则详见 class.md#class-成员变量):

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声明的变量,不能被赋值。下面的代码中读取x.i合法,但x.i = 1会编译报错:

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

属性的更多落地场景:扩展、结构体、枚举与反射

属性在仓颉各类型体系中均可使用,以下场景可以帮你把属性用到更广阔的地方:

1. 扩展中定义属性(extend)。扩展可以为当前package中可见的类型(除函数、元组、接口)添加成员属性,适合在不破坏被扩展类型封装性的前提下补充数据访问能力(详见 extend_overview.md)。例如:

extend Boo { public prop x: Int64 { // 添加成员属性 get() { 123 } } }

不过扩展受限较多:扩展不能增加成员变量、扩展的属性和函数必须拥有实现、扩展的属性不能用open/override/redef修饰,也不能访问被扩展类型中private修饰的成员。

2. struct 与 enum 中的属性。struct的定义体中可声明成员变量、成员属性等(详见 define_struct.md);enum体中同样可以定义成员属性,但要求构造器、成员函数、成员属性之间不能重名(详见 enum.md)。

3. 通过反射检查与修改属性。仓颉的反射机制支持对属性进行检查与修改:TypeInfo的子类ClassTypeInfo提供接口访问类公开的成员变量、属性与函数;instanceProperties可列出实例成员属性,getInstanceProperty获取属性信息,isMutable判断属性是否可写(详见 dynamic_feature.md)。例如反射输出的属性描述形式为[prop p1: Int64, mut prop p2: Int64],其中mut前缀即标识可写属性——这与编译期属性声明的mut语义完全一致。

4. 跨平台(common/specific)场景中的属性限制。在多平台开发中,common属性可以有具体实现,也可以仅保留属性签名交由specific属性实现;若common属性有完整实现则可省略specific属性;common与specific属性的类型、可见性和可赋值性必须相同;common/specific的interface属性中,若common属性包含具体实现,则specific属性必须也包含具体实现(详见 common_platform.md)。

小结

仓颉的属性机制围绕prop关键字展开,核心要点可以归纳为:

维度规则要点
定义位置interface、class、struct、enum、extend
getter函数类型() -> T,属性作表达式时执行;无mut属性必须且只能定义 getter
setter函数类型(T) -> Unit,形参名需显式指定;mut属性必须同时定义 getter 与 setter
mut限制数值、元组、函数、Bool、Unit、Nothing、String、Range、enum类型不能声明/实现mut属性
修饰符public/private等访问修饰、static、open/override/redef;覆盖时mut与类型必须一致
抽象属性在interface与抽象类中声明,实现类型/非抽象子类必须实现
使用语义无mut属性 ≈let变量(只读);mut属性 ≈var变量(可读可写)
递归陷阱在 getter/setter 中访问属性自身会递归调用,可能导致死循环

属性把"数据访问"从"具体存储"中解放出来,让访问控制、数据监控、跟踪调试与数据绑定有了自然的实现位置,是仓颉面向对象编程中兼具封装性与表达力的核心语言特性。

  • 文档
  • 编程语言

【免费下载链接】cangjie_docs

仓颉编程语言开发者文档。

项目地址:https://gitcode.com/Cangjie/cangjie_docs
点击查看免费下载
上一篇:颠覆式缠论可视化:通达信Indicator插件革新技术分析范式
下一篇:轻量级高效解决方案:JPEGView的图像查看与编辑全功能工具

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 6:40:23

【单片机毕设案例分享】基于STM32的婴儿床远程监控与摇篮联动装置设计 基于单片机的智能婴儿摇篮远程监护与报警系统设计(032703)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于单片机&#xff0c;STM32单片机&#xff0c;51单片机&#xff0c;J…

作者头像 李华
网站建设 2026/10/14 6:40:11

浏览器内置大模型实战:零成本实现网页文案优化建议

1. 浏览器内置大模型到底是个什么东西1.1 从"调API"到"用浏览器自带能力"的转变过去两年&#xff0c;给网页加AI能力几乎只有一条路&#xff1a;申请某个云端大模型的API Key&#xff0c;然后在前端或后端发请求。这条路能跑通&#xff0c;但问题也很明显—…

作者头像 李华
网站建设 2026/10/14 6:40:07

Python+Django+Vue构建电影观众分析系统

1. 项目背景与核心价值电影产业作为文化娱乐领域的重要组成部分&#xff0c;观众特征研究一直是行业关注的焦点。传统的人工问卷调查方式效率低下且样本量有限&#xff0c;而基于PythonDjangoVue的技术方案能够实现自动化数据采集与分析&#xff0c;为电影制作、发行和营销提供…

作者头像 李华
网站建设 2026/10/14 6:40:04

Hermes 实战:让 AI 从聊天到执行,自动化工作流

1. 从“聊得挺好”到“干得漂亮”&#xff1a;Hermes 到底解决了什么问题你有没有过这种体验&#xff1a;跟某个 AI 助手聊得热火朝天&#xff0c;它引经据典、逻辑清晰&#xff0c;甚至还能跟你讨论哲学和人生。但一旦你关掉对话框&#xff0c;回到真实的工作流里&#xff0c;…

作者头像 李华
网站建设 2026/10/14 6:38:33

HTML帧时间轴:用data-frame和CSS变量实现MP4级动效控制

1. “hyperframes”不是新框架&#xff0c;而是HTML动效表达范式的悄然升级最近在几个前端技术群和代码片段分享平台里&#xff0c;“hyperframes”这个词突然高频出现&#xff0c;既不像React、Vue那样有明确的官网和文档&#xff0c;也不像Tailwind CSS那样有清晰的生态定位。…

作者头像 李华
网站建设 2026/10/14 6:38:05

PHP MySQLi预处理:execute与rowCount的坑及SELECT结果正确判断法

1. 拆解现场&#xff1a;第一眼看到这段代码时&#xff0c;我脑海里闪过的四个问题本行代码是典型的PHP MySQLi预处理语句执行与结果判断组合。写上这行代码的时候&#xff0c;说明你已经在用预处理&#xff08;Prepared Statements&#xff09;&#xff0c;没有把变量直接拼进…

作者头像 李华