NOTE阅读文档版本:
语言规约 Cangjie-0.53.18-Spec
具体开发指南 Cangjie-LTS-1.0.4
在阅读 了解仓颉的语言规约时, 难免会涉及到一些仓颉的示例代码, 但 我们对仓颉并不熟悉, 所以可以用 仓颉在线体验 快速验证
有条件当然可以直接 配置 Canjie-SDK
WARNING博主在此之前, 基本只接触过 C/C++语言, 对大多现代语言都没有了解, 所以在阅读过程中遇到相似的概念, 难免会与 C/C++中的相似概念作类比, 见谅
且, 本系列是文档阅读, 而不是仓颉的零基础教学, 所以如果要跟着阅读的话最好有一门编程语言的开发经验
WARNING在阅读仓颉编程语言的开发指南之前, 已经大概阅读了一遍 仓颉编程语言的语言规约, 已经对仓颉编程语言有了一个大概的了解
所以在阅读开发指南时, 不会对类似: 类、函数、结构体、接口等解释起来较为复杂名称 做出解释
此样式内容, 表示文档原文内容
类和接口
属性
属性(
Properties)提供了一个getter和一个可选的setter来间接获取和设置值使用属性的时候与普通变量无异,只需要对数据操作,对内部的实现无感知,可以更便利地实现访问控制、数据监控、跟踪调试、数据绑定等机制
属性在使用时可以作为表达式或被赋值
此处以类和接口为例进行说明,但属性不仅限于类和接口
以下是一个简单的例子,
b是一个典型的属性,封装了外部对a的访问:class Foo {private var a = 0public mut prop b: Int64 {get() {println("get")a}set(value) {println("set")a = value}}}main() {var x = Foo()let y = x.b + 1 // getx.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做到同样地访问和修改操作,实现了有效的封装性所以程序的输出如下:
getset
属性, 使用关键字prop声明, 语法为:
class Sample { prop p: Int64}仓颉属性, 提供了两个功能:
-
getter, 可以通过属性获取目标数据通过属性的
get()语法实现 -
setter, 可以通过属性设置目标数据通过属性的
set()语法实现
虽然属性可以声明类型, 但是属性本身并不存储数据, 属性的类型是是给getter和setter使用的
getter的功能是, 通过属性获取目标数据
属性自身并不存储数据, 但是通过 访问属性, 可以调用对应属性实现的get(), get()返回值就是要获取的目标数据
同样的, 如果 给属性赋值, 可以调用对应属性的set(value), set()就会接收数据并执行内部代码
而get()返回值类型 与 set(value)接收的value的类型, 就是属性声明的类型
简单理解, 属性的类型, 其实是给get()声明返回值类型, 是给set()声明参数类型
就像文档中提供的例子, 属性b, getter实际返回的是私有成员变量a: Int64, setter实际是给私有成员变量a: Int64赋值
所以, 而私有成员变量a是Int64类型的, 很显然 获取它要使用Int64类型, 给它赋值也要接收Int64类型数据, 所以属性b声明为Int64类型
这就是属性需要声明类型的原因, 这也是 接口能够定义成员属性, 但也可以多继承的原因(属性只是提供了一个间接的访问数据、修改数据的方法而不存储数据)
属性定义
属性可以在
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都是属性,a和b的类型都是Int64
a是 无mut修饰符 的属性,这类属性**有且仅有定义getter(对应取值)**实现
b是 使用mut修饰 的属性,这类属性**必须分别定义getter(对应取值)和setter(对应赋值)**的实现注意:
对于数值、元组、函数、
Bool、Unit、Nothing、String、Range和enum类型,在它们的扩展和声明中不能声明mut修饰的属性,也不能实现有mut属性的接口属性的
getter和setter分别对应两个不同的函数
getter函数类型是() -> T,T是该属性的类型,当使用该属性作为表达式时会执行getter函数
setter函数类型是(T) -> Unit,T是该属性的类型,形参名需要显式指定,当对该属性赋值时会执行setter函数
getter和setter的实现中可以和函数体一样包含声明和表达式,与函数体的规则一样,详见 函数体 章节
setter中的参数对应的是赋值时传入的值class Foo {private var j = 0public mut prop i: Int64 {get() {j}set(v) {j = v}}}需要注意的是,在属性的
getter和setter中访问属性自身属于递归调用,与函数调用一样可能会出现死循环的情况
仓颉属性, 没有mut的属性只能实现且必须实现getter, 有mut修饰的属性才能实现且必须额外实现setter
与上面分析的一致, 属性的类型声明, 是为getter和setter使用的, 是get()的返回值类型, 是set(value)的参数类型
修饰符
可以在
prop前面声明需要的修饰符class Foo {public prop a: Int64 {get() {0}}private prop b: Int64 {get() {0}}}和成员函数一样,成员属性也支持
open、override、redef修饰,所以也可以在子类型中覆盖/重定义父类型属性的实现子类型覆盖父类型的属性时,如果父类型属性带有
mut修饰符,则子类型属性也需要带有mut修饰符,同时也必须保持一样的类型如下代码所示,
A中定义了x和y两个属性,B中可以分别对x和y进行override/redef:open class A {private var valueX = 0private static var valueY = 0public open prop x: Int64 {get() { valueX }}public static mut prop y: Int64 {get() { valueY }set(v) {valueY = v}}}class B <: A {private var valueX2 = 0private static var valueY2 = 0public override prop x: Int64 {get() { valueX2 }}public redef static mut prop y: Int64 {get() { valueY2 }set(v) {valueY2 = v}}}
属性也可以通过可访问性修饰符修饰, 具体可访问性限制不再赘述, 与其他成员的可访问性限制保持一致
属性除了实例成员属性之外, 还可以定义静态成员属性
既然有静态成员属性, 那么就有实例成员和静态成员之间的访问问题
-
静态成员属性
只能访问其他静态成员变量, 不能访问实例成员变量
只能访问其他静态成员属性, 不能访问实例成员属性
只能访问其他静态成员函数, 不能访问实例成员函数
-
实例成员属性
可以通过类型名访问其他静态成员变量, 也可以访问其他实例成员变量
可以通过类型名访问其他静态成员属性, 也可以访问其他实例成员属性
可以通过类型名访问其他静态成员函数, 也可以访问其他实例成员函数
其实总结起来, 仓颉各类型:
静态成员函数或成员属性, 只能访问其他静态成员, 不能访问实例成员
而实例成员函数和成员属性, 可以访问其他实例成员, 也可以通过类型名访问静态成员
另外, 属性也可以用open修饰, 表示可以覆盖或重定义, override和redef同样也是可选的
覆盖或重定义时, 属性是否被mut修饰, 属性类型 都要与父类型保持一致
抽象属性
类似抽象函数,在
interface和抽象类中也可以声明抽象属性,这些抽象属性没有实现interface I {prop a: Int64}abstract class C {public prop a: Int64}当实现类型实现
interface或者非抽象子类继承抽象类时,必须要实现这些抽象属性与覆盖的规则一样,实现类型或子类在实现这些属性时,如果父类型属性带有
mut修饰符,则子类型属性也需要带有mut修饰符,同时也必须保持一样的类型interface I {prop a: Int64mut prop b: Int64}class C <: I {private var value = 0public 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(): Int64func setSize(value: Int64): Unit}class C <: I1 & I2 {private var mySize = 0public 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 = 5println(a.size)let b: I2 = C()b.setSize(5)println(b.getSize())}55
抽象属性, 就是没有具体实现, 只有声明的属性
可以在interface和抽象类中定义, 实现或继承的类型必须要实现
同样的, 也需要与父类型中的属性声明保持一致, 即 类型一致, mut修饰一致
属性使用
属性分为 实例成员属性 和 静态成员属性
成员属性的使用和成员变量的使用方式一样,详见 成员变量 章节
class A {public prop x: Int64 {get() {123}}public static prop y: Int64 {get() {321}}}main() {var a = A()println(a.x) // 123println(A.y) // 321}结果为:
123321无
mut修饰符的属性类似let声明的变量,不能被赋值class A {private let value = 0public prop i: Int64 {get() {value}}}main() {var x = A()println(x.i) // OKx.i = 1 // Error}带有
mut修饰符的属性类似var声明的变量,可以取值也可以被赋值class A {private var value: Int64 = 0public mut prop i: Int64 {get() {value}set(v) {value = v}}}main() {var x = A()println(x.i) // OKx.i = 1 // OK}0
属性的使用与成员变量的使用方式是一样的
只不过, 一般来说属性是为了操作成员变量的, 当然也可以不操作任何其他数据, 即固定返回等操作
属性都可以取值, 但赋值操作需要属性被mut修饰