仓颉编程语言青少年基础教程:class(类)(下)
仓颉编程语言青少年基础教程:class(类)(下)
本文接着上一篇博文,继续讲解仓颉编程语言class(类)。
继承(Inheritance)
仓颉只支持单继承。用 <: 指定父类,父类必须用 open(非抽象类需用 open 修饰) 或 abstract 修饰才能被继承。
父类必须用 open 或 abstract 修饰才能被继承:
• 非抽象类 —— 必须加 open 才能被继承;什么都不写就是 internal 且 封闭。
• 抽象类 —— 默认即“可继承”,写不写 open 都行;若想再限制只能本包继承,就加 sealed。
示例 1:普通类加上 open,继承才成功
// 1. 普通类,显式 open,才允许继承open class Base {public func hello() { println("Base") }
}// 2. 继承
class Child <: Base { public func hi() { println("Child") }
}main(): Int64 {let c = Child()c.hello() // Basec.hi() // Childreturn 0
}
编译运行截图:
示例 2 :抽象类无需 open,直接可继承
// 1. 抽象类,默认就是“可继承”
abstract class AbstractBase {public func core(): Unitpublic func helper() { println("helper") }
}// 2. 非抽象子类直接继承 —— 合法
class Impl <: AbstractBase {public override func core() { println("Impl::core") }
}main(): Int64 {let obj: AbstractBase = Impl()obj.core() // Impl::coreobj.helper() // helperreturn 0
}
抽象类使用 sealed 修饰符
抽象类可以使用 sealed 修饰符,表示被修饰的类定义只能在本定义所在的包内被其他类继承。sealed 已经蕴含了 public/open 的语义,因此定义 sealed abstract class 时若提供 public/open 修饰符,编译器将会告警。sealed 的子类可以不是 sealed 类,仍可被 open/sealed 修饰,或不使用任何继承性修饰符。若 sealed 类的子类被 open 修饰,则其子类可在包外被继承。sealed 的子类可以不被 public 修饰。
sealed修饰抽象类的核心是 “限制自身的继承范围在本包内”,但允许子类通过open等修饰符打破这个限制,兼顾了封装性和灵活性。
【在仓颉语言中,sealed 修饰符属于非访问修饰符(也称为 “功能修饰符”),而非访问修饰符(如 public、private 等)。
sealed修饰抽象类的核心是 “限制自身的继承范围在本包内”,但允许子类通过open等修饰符打破这个限制,兼顾了封装性和灵活性。
• 同包内的继承、实现、多级继承和方法重写均合法,程序可正常运行并输出预期结果。
• 跨包的继承 / 实现尝试会在编译阶段被拦截,确保密封类型的实现范围被严格控制在设计的包内,增强代码封装性。】
sealed 仅允许同包内的类型实现,先看一个简单示例:
示例项目结构:
demo12/
└── src
├── A
│ ├── base.cj // 定义 sealed 基类
│ └── derived_ok.cj // 同包继承,应成功
├── B
│ └── derived_bad.cj // 跨包继承,应报错
└── main.cj // 入口
1. A/base.cj 内容如下:
package demo12.A// sealed 基类
sealed abstract class Base { // 不需要public ?public func greet(): Unit
}
2.A/derived_ok.cj 内容如下:
package demo12.Apublic class DerivedOk <: Base { // 必须加 public// 显式空参构造public init() {}public override func greet(): Unit {println("Hello from DerivedOk")}
}
3.derived_bad.cj内容如下:
package demo12.Bimport demo12.A.Base// 会编译失败: error: ……'
// class DerivedBad <: Base {
// public override func greet(): Unit {
// println("Hello from DerivedBad")
// }
// }
4.入口文件:
package demo12//import demo12.A.Base // ?
import demo12.A.DerivedOk// 主函数
main() {let ok = DerivedOk()ok.greet() // 正常输出: Hello from DerivedOk
}
再看一个全面点的sealed作用示例:
示例项目结构:
demo/
└── src
├─sealed
│ ├── SamePackageImplementations.cj
│ └── SealedTypes.cj
├─other
│ └── DifferentPackageAttempt.cj
└── main.cj // 入口
1.sealed/SealedTypes.cj内容如下:
package demo.sealed // 定义sealed抽象类
sealed abstract class SealedClass {public func hello(): String {return "Hello from SealedClass" }public func whoAmI(): String
}// 定义sealed接口
sealed interface SealedInterface { func doSomething(): Unit
}
2.sealed/SamePackageImplementations.cj 内容如下:
package demo.sealed // 同包内继承sealed类(合法)
public class SamePackageClass <: SealedClass {public func whoAmI(): String {return "I'm SamePackageClass (extends SealedClass)" }
}// 同包内实现sealed接口(合法)
public class SamePackageInterfaceImpl <: SealedInterface {public func doSomething(): Unit {println("SamePackageInterfaceImpl is doing something") }
}// 同包内继承并开放给同包进一步继承
public open class OpenSamePackageClass <: SealedClass {// 关键:给方法添加 open,允许子类重写public open func whoAmI(): String { return "I'm OpenSamePackageClass (extends SealedClass)" }
}// 同包内继承开放类(现在合法)
public class AnotherSamePackageClass <: OpenSamePackageClass {public override func whoAmI(): String { // 此时 override 有效return "I'm AnotherSamePackageClass (extends OpenSamePackageClass)" }
}
3.other/DifferentPackageAttempt.cj 内容如下:
package demo.other import demo.sealed.SealedClass
import demo.sealed.SealedInterface
import demo.sealed.OpenSamePackageClass // // 尝试在不同包继承sealed类(不合法)
// class DifferentPackageClass <: SealedClass { // 编译错误:无法继承sealed类
// public func whoAmI(): String {
// return "This won't compile"
// }
// }// // 尝试在不同包实现sealed接口(不合法)
// class DifferentPackageInterfaceImpl <: SealedInterface { // 编译错误:无法实现sealed接口
// public func doSomething(): Unit {
// println("This won't compile either")
// }
// }// // 尝试在不同包继承sealed类的同包子类(不合法)
// class DifferentPackageExtendsOpen <: OpenSamePackageClass { // 编译错误:即使父类是open的,也不能跨包继承
// public override func whoAmI(): String {
// return "This won't compile too"
// }
// }
4.入口文件 main.cj内容如下:
package demoimport demo.sealed.SamePackageClass
import demo.sealed.SamePackageInterfaceImpl
import demo.sealed.AnotherSamePackageClassmain() {// 测试同包内的sealed类继承let obj1 = SamePackageClass() println(obj1.hello()) println(obj1.whoAmI()) // 测试同包内的sealed接口实现let obj2 = SamePackageInterfaceImpl() obj2.doSomething() // 测试同包内的多级继承let obj3 = AnotherSamePackageClass() println(obj3.whoAmI())
}
说明:
1. sealed 修饰符的核心作用
• 限制跨包继承 / 实现:被 sealed 修饰的类或接口,只能在当前包内被继承(类)或实现(接口),跨包尝试会直接编译报错。
示例中,demo.sealed 包的 SealedClass(密封类)和 SealedInterface(密封接口),在 demo.other 包中无法被继承或实现。
2. 同包内的合法操作
• 同包继承密封类:在 demo.sealed 包内,SamePackageClass 和 OpenSamePackageClass 可以直接继承 SealedClass(合法)。
• 同包实现密封接口:SamePackageInterfaceImpl 在 demo.sealed 包内实现 SealedInterface(合法)。
• 同包多级继承:密封类的子类(如 OpenSamePackageClass)若被 open 修饰,允许在同包内被进一步继承(如 AnotherSamePackageClass 继承 OpenSamePackageClass)。
3. 跨包的禁止操作
• 无法在其他包(如 demo.other)继承密封类(SealedClass)或其同包子类(OpenSamePackageClass)。
• 无法在其他包实现密封接口(SealedInterface)。
• 即使密封类的子类被 open 修饰(如 OpenSamePackageClass),跨包继承依然被禁止(sealed 限制具有传递性)。
4.还需要注意:方法重写的严格规则
• open 与 override 配对使用:
父类方法必须用 open 修饰,才能允许子类重写(如 OpenSamePackageClass 中的 open func whoAmI())。
子类重写父类方法时,必须显式添加 override 关键字(如 AnotherSamePackageClass 中的 override func whoAmI())。
• 若父类方法未加 open,即使类被 open 修饰,子类也无法重写该方法(会触发编译错误)。
方法覆盖(Override)和重定义(redef)
法覆盖:子类用 override 重写父类 open 方法——父类用open修饰的实例函数,子类可通过override重写实现,调用时根据对象运行时类型决定执行哪个版本(动态派发);
静态方法重定义:子类用 redef 重写父类静态方法(按类类型调用)。
示例 1:实例方法覆盖(动态派发)
// 父类 Vehicle
open class Vehicle {public open func run() { // 需用 open 允许覆盖println("车辆行驶")}
}// 子类 Car
class Car <: Vehicle {public override func run() { // 覆盖父类方法println("汽车飞驰")}
}main() {let v: Vehicle = Car() // 编译时类型为 Vehicle,运行时为 Carv.run() // 输出:汽车飞驰(动态派发,调用子类实现)
}
示例 2:静态方法重定义(按类调用)
// 父类 Tool
open class Tool {static func name(): String {return "工具"}
}// 子类 Hammer
class Hammer <: Tool {redef static func name(): String { // 重定义静态方法return "锤子"}
}main() {println(Tool.name()) // 输出:工具(父类方法)println(Hammer.name()) // 输出:锤子(子类重定义方法)
}
说明,子类 Hammer使用 redef 关键字重定义了父类 Tool 的静态方法 name(),返回字符串 "锤子"。
属性 (Properties)
属性(Properties)是对 “数据访问” 的封装,通过getter(取值逻辑)和setter(赋值逻辑)间接操作底层数据,隐藏实现细节,支持访问控制、数据验证等功能。使用时与普通变量无异,但内部通过函数实现逻辑。
属性不是变量,而是 “取值函数(getter)+ 可选赋值函数(setter)” 的组合:
• getter:无参数,返回属性类型的值(当访问属性时执行)。
• setter:接收一个参数(赋值时的输入值),无返回值(当给属性赋值时执行)。
属性的分类
类型 | 特点 | 语法关键字 |
只读属性 | 仅有 getter,不可赋值 | prop |
可读写属性 | 有 getter 和 setter,可赋值 | mut prop |
抽象属性 | 无实现(仅声明),需子类 / 实现类实现 | 抽象类 / 接口中用prop |
属性的定义语法
[访问修饰符] [open/override/redef] [mut] prop 属性名: 类型 {
// getter:取值逻辑(必须实现)
get() {
取值表达式; // 返回属性类型的值
}
// setter:赋值逻辑(仅mut prop需实现)
set(参数名) {
赋值逻辑; // 通常操作底层私有变量
}
}
注意:
①命名形参约束
如果父类/接口里的属性带 mut,子类在 override/redef 时:
• 必须保留 mut
• 必须保持类型完全一致
• setter 的形参名可以不同,但仓颉推荐保持一致以增强可读性。
②属性不能“递归”访问自身
在 get()/set(v) 里直接写 属性名 会无限递归,应改为读写背后的私有字段。
③计算属性 ≠ 缓存属性
getter 每次访问都会重新执行函数体,若计算成本高又想缓存,需手动在类里加私有变量保存结果。
使用属性语法:
①读取 (调用 getter)
let x = object.propertyName
②写入 (调用 setter,仅 mut prop 可用)
object.propertyName = newValue
示例1:
class User {// 底层私有变量:存储实际数据(外部不可直接访问)private let _name: String;private var _age: Int64 = 0;// 1. 只读属性:姓名(仅允许读取,不允许修改)public prop name: String {get() {_name; // 直接返回底层变量}}// 2. 可读写属性:年龄(赋值时验证合法性,取值时打印日志)public mut prop age: Int64 {get() {println("[Get] 用户${_name}的年龄:${_age}");_age;}set(newAge) {println("[Set] 尝试设置${_name}的年龄为:${newAge}");// 验证逻辑:年龄必须在0-150之间if (newAge < 0 || newAge > 150) {println(" 错误:年龄不合法,设为默认值0");_age = 0;} else {_age = newAge;println(" 成功:年龄更新为${_age}");}}}// 构造函数:初始化姓名public init(name: String) {this._name = name;}
}// 程序入口:main函数
main(): Int64 {// 1. 创建User对象let user = User("张三");// 2. 访问只读属性name(不可赋值)println("用户名:${user.name}"); // 输出:用户名:张三// user.name = "李四"; // 错误:只读属性不可赋值// 3. 操作可读写属性age(带验证)user.age = 25; // 合法年龄,成功更新user.age; // 读取年龄,触发getter日志user.age = 200; // 不合法年龄,设为默认值0user.age; // 读取更新后的默认年龄return 0;
}
编译运行结果:
用户名:张三
[Set] 尝试设置张三的年龄为:25
成功:年龄更新为25
[Get] 用户张三的年龄:25
[Set] 尝试设置张三的年龄为:200
错误:年龄不合法,设为默认值0
[Get] 用户张三的年龄:0
示例2:
class Temperature {// 内部的私有成员变量,存储摄氏温度private var _celsius: Float64 = 0.0// 1. 公开的 Celsius 属性,可读写 (mut)public mut prop Celsius: Float64 {get() {_celsius // 直接返回内部存储的值}set(value) {// 可以在 setter 中加入逻辑,比如限制范围_celsius = value}}// 2. 公开的 Fahrenheit 属性,只读 (无 mut)// 它是一个计算属性,值由 Celsius 转换而来,并不直接存储public prop Fahrenheit: Float64 {get() {(Celsius * 9.0 / 5.0) + 32.0}// 无 setter,所以是只读的}// 3. 公开的 Kelvin 属性,可读写// 它的 getter 和 setter 都涉及计算public mut prop Kelvin: Float64 {get() {Celsius + 273.15}set(value) {Celsius = value - 273.15}}
}main() {let temp = Temperature()// 设置 Celsius,会调用它的 settertemp.Celsius = 25.0println("Celsius: ${temp.Celsius}") // 输出: Celsius: 25.000000// 读取 Fahrenheit,会调用它的 getter 进行计算println("Fahrenheit: ${temp.Fahrenheit}") // 输出: Fahrenheit: 77.000000// 读取 Kelvin,会调用它的 getter 进行计算println("Kelvin: ${temp.Kelvin}") // 输出: Kelvin: 298.150000// 通过设置 Kelvin 来改变温度temp.Kelvin = 300.0println("\nAfter setting Kelvin to 300.0:")println("Celsius: ${temp.Celsius}") // 输出: Celsius: 26.850000println("Fahrenheit: ${temp.Fahrenheit}") // 输出: Fahrenheit: 80.330000println("Kelvin: ${temp.Kelvin}") // 输出: Kelvin: 300.000000// 尝试设置只读属性 Fahrenheit 会编译报错// temp.Fahrenheit = 100.0 // Error: Cannot assign to value: 'Fahrenheit' is a get-only property
}
编译运行结果:
Celsius: 25.000000
Fahrenheit: 77.000000
Kelvin: 298.150000
After setting Kelvin to 300.0:
Celsius: 26.850000
Fahrenheit: 80.330000
Kelvin: 300.000000