华为仓颉语言的class(类)初步
华为仓颉语言的class(类)初步
class 概念
【官方文档 https://cangjie-lang.cn/docs?url=%2F1.0.0%2Fuser_manual%2Fsource_zh_cn%2Fclass_and_interface%2Fclass.html 】
class 是仓颉面向对象体系的核心,用来描述“引用类型”对象。与 struct(值类型)相比,class 实例在赋值、传参时是引用语义,并天然支持继承与多态 。注意:class 只能定义在源文件的顶层作用域。
class 类型的定义以关键字 class 开头,后跟 class 的名字,接着是定义在一对花括号中的 class 定义体。class 定义体中可以定义一系列的成员变量(member variables)、成员属性(member properties)、静态初始化器(static initializers)、构造函数(constructors)、成员函数(member functions)和操作符函数(operator functions)。
一个类的基本定义语法如下:
class 类名 {
// 成员变量
// 构造函数
// 成员函数
// 成员属性
}
说明:
类名通常推荐采用帕斯卡命名法(首字母大写,如 Person、Car)。
类体中可包含成员变量、成员方法、构造函数、成员属性等,用于描述类的特征数据和行为。
成员属性:提供了对成员变量更灵活和安全的访问方式,可以包含自定义的 getter 和 setter。
【关于Property(属性)的说明
在仓颉语言(Cangjie)的官方文档中,统一使用 Property 来表示“属性”。
属性(Properties)提供了一个 getter 和一个可选的 setter 来间接获取和设置值。
仓颉语言的class 定义体中
数据成员变量不像有些语言那样称为attribute。
成员属性就是指属性(Properties)。
术语 | 英文 | 定义 |
属性 | Property | 通过getter/setter间接访问的成员,提供计算、验证等逻辑 |
成员变量 | Member Variable | 直接存储数据的普通字段(无getter/setter) |
attribute | ❌ 非官方术语 | 在仓颉中不适用(其他语言如Python的术语) |
】
例如:
class Rectangle {
// 成员变量
let width: Int64
let height: Int64
// 构造函数
public init(width: Int64, height: Int64) {
this.width = width
this.height = height
}
// 成员函数
public func area(): Int64 {
return width * height
}
}
创建对象
语法
let/var 变量名[: 类型] = 类型名(构造函数参数)
通常[: 类型]部分可省略,由编译器自动推断
示例:简单类的定义及对象创建示例
// 定义一个简单的 Person 类
class Person {var name: Stringvar age: Int64// 构造函数public init(name: String, age: Int64) {this.name = namethis.age = age}public func introduce(): Unit {println("Hello, my name is ${name} and I am ${age} years old.")}
}// 在 main 函数或其他方法中创建对象
main() {// 1. 使用 var 声明,可以改变引用指向的对象var person1 = Person("Alice", 30) // 调用 init(name, age) , 等号前也可写为var person1: Person =person1.introduce() // 输出: Hello, my name is Alice and I am 30 years old.// person1 引用可以重新赋值,指向一个新的 Person 对象person1 = Person("Bob", 25)person1.introduce() // 输出: Hello, my name is Bob and I am 25 years old.// 2. 使用 let 声明,引用不可变let person2 = Person("Charlie", 40)person2.introduce() // 输出: Hello, my name is Charlie and I am 40 years old.// 下面这行代码会编译错误,因为 person2 是不可变引用// person2 = Person("David", 35)// 但是,对象内部的成员变量如果是 var,仍然可以通过引用修改person2.age = 41 // 这是允许的,因为 person2.age 是 varperson2.introduce() // 输出: Hello, my name is Charlie and I am 41 years old.
}
编译运行截图:
仓颉class 几点说明
• 成员变量写在类体;可追加 public / private / protected 等可见性修饰符(缺省为 internal)。
【访问控制修饰符:
private 私有,表示在 class 定义内可见。
internal 内部,表示仅当前包及子包(包括子包的子包,详见包)内可见。缺省。
protected 受保护,表示当前模块(详见包)及当前类的子类可见。
public 公开,表示模块内外均可见。】
• 仓颉class 支持两类构造函数,规则与初始化顺序全部与 struct 一致 :
普通构造函数:名字固定为 init,可重载。
主构造函数:名字与类名相同,最多一个,可把形参同时声明为成员变量。
一个 类可以有多个构造函数。
例如:
class Person {
var name: String
var age: Int64
// 主构造函数
public init(name: String, age: Int64) {
this.name = name
this.age = age
}
// 辅助构造函数,调用主构造
public init(name: String) {
this.init(name, 0) // 调用主构造函数
}
}
初始化顺序:
① 先初始化主构造之外、带缺省值的变量;
② 如果构造函数体内未显式调用父类构造函数或本类其他构造函数,则调用父类的无参构造函数 super(),如果父类没有无参构造函数,则报错;
③ 执行构造函数体。
示例
open class Parent {var a: Int64 = 1 // 步骤1.1:父类字段初始化var b: Int64// 父类主构造函数public init(b: Int64) {// 步骤3.1:执行父类构造函数体this.b = bprintln("Parent constructor body")}
}class Child <: Parent {var x: Int64 = 3 // 步骤1.2:子类字段初始化var y: Int64// 子类主构造函数public init(b: Int64, y: Int64) {// 隐式或显式调用父类构造函数!这是步骤2super(b) // 步骤2:调用父类构造函数// 步骤3.2:执行子类构造函数体this.y = yprintln("Child constructor body")}
}main() {// 创建实例时: let obj = Child(2, 4)
}
输出:
Parent constructor body
Child constructor body
发生的顺序实际上是:
1.Parent.a 被赋值为 1
2.Child.x 被赋值为 3
3.开始执行 Child.init 中的代码:
a. 首先调用 super(b),即 Parent.init(b)。
b. 执行 Parent.init:
- Parent.b 被赋值为 2 (来自参数)。
- 打印 "Parent constructor body"。
c. Parent.init 执行完毕,返回。
4.继续执行 Child.init 的剩余部分:
a. Child.y 被赋值为 4。
b. 打印 "Child constructor body"。
示例:父类没有无参构造,子类忘记显式 super → 编译错误
open class Parent {var b: Int64public init(b: Int64) {this.b = bprintln("Parent body") } // 只有这一个构造函数
}class Child <: Parent {public init() {super(0) // 注意:若无此行,编译报错!println("Child body")}
}main() {// 创建实例时: let obj = Child()
}
属性(Property)
仓颉的属性(Property)提供了对类内部状态的受控访问,它可以包含 getter 和 setter。
示例:
class Person {private var _age: Int64 = 0// 1) 可读写属性:带 mut,可在 set 中拦截或记录public mut prop age: Int64 {get() {println("getter: 读取 _age")_age}set(value) {println("setter: 写入 _age = ${value}")_age = value}}// 2) 只读计算属性:没有 setter,始终根据 _age 计算public prop isAdult: Bool {get() {_age >= 18}}
}// ---------------- 演示 ----------------
main(): Int64 {let p = Person()println("--- 写属性 ---")p.age = 15 // 触发 setterprintln("--- 读属性 ---")let a = p.age // 触发 getterprintln("读取到的 age = ${a}")println("--- 只读属性 ---")println("isAdult = ${p.isAdult}") // 15 < 18 → falsep.age = 20 // 再次触发 setterprintln("isAdult = ${p.isAdult}") // 20 ≥ 18 → true0 // return 0
}
输出:
--- 写属性 ---
setter: 写入 _age = 15
--- 读属性 ---
getter: 读取 _age
读取到的 age = 15
--- 只读属性 ---
isAdult = false
setter: 写入 _age = 20
isAdult = true
抽象类(abstract class)
抽象类使用 abstract 修饰,不能实例化的类,用于定义子类的共同接口,可包含抽象方法(无实现)和具体方法(有实现),子类必须实现抽象方法。
抽象类定义时的 open 修饰符是可选的——抽象类默认可继承(无需open)。
注意:
• 抽象类中禁止定义 private 的抽象函数;
• 不能为抽象类创建实例;
• 抽象类的非抽象子类必须实现父类中的所有抽象函数。
抽象类还可以被 sealed 修饰,表示该抽象类只能在其定义所在的包内被其他类继承。
示例1:
// 1. 抽象类定义,本身就隐含 open,无需再写
abstract class Shape {// 抽象方法:public func getArea(): Float64// 具体方法:实现写在方法体里public func printArea(): Unit {println("面积:${this.getArea()}")}
}// 2. 子类
class Circle <: Shape {let radius: Float64 // 成员变量用 let/var 声明// 构造函数:仓颉只有 init,没有 constructorpublic init(radius: Float64) {super() // 调用父类构造函数this.radius = radius}// 实现抽象方法public override func getArea(): Float64 {3.1415926 * radius * radius}
}// 3. 创建对象
main() {let circle: Shape = Circle(2.0)circle.printArea() // 输出:面积:12.5663704
}
示例 2:
// 1. 抽象类定义,本身就隐含 open,无需再写
abstract class AbstractBase {// 抽象方法:public func doSomething(): Unit
}// 2. 同包下的实现类
class Concrete <: AbstractBase {// 实现抽象方法public override func doSomething(): Unit {println("实现抽象方法")}
}// 3. 密封抽象类:仅本包可继承
sealed abstract class SealedAbstract {public func baseFunc(): Unit {} // 具体方法
}// 4. 同包子类继承密封抽象类——合法
class SamePackageChild <: SealedAbstract {}// 6. 演示 main
main() {let obj1: AbstractBase = Concrete()obj1.doSomething() // 输出:实现抽象方法let obj2 = SamePackageChild()obj2.baseFunc() // 无输出,仅演示可实例化
}
class是引用类型
华为仓颉语言的class是引用类型,定义为引用类型的变量,变量名中存储的是指向数据值的引用,因此在进行赋值或函数传参等操作时,拷贝的是引用。
示例
class MyClass {var value: Int64 = 0
}main(): Int64 {let a = MyClass() // a 是一个引用,指向新创建的 MyClass 对象a.value = 10let b = a // 将 a 的引用拷贝给 b。现在 a 和 b 指向同一个对象!b.value = 20 // 通过 b 修改对象println(a.value) // 输出 20,因为 a 指向的对象已被修改return 0
}
继承 / 实现 / 多态要点
• 单继承:用 <: 符号,如 class Student <: Person { ... } 。
• 父类必须被 open 修饰才能被继承。
• 方法重写:子类方法前加 override;如果一个方法没有 open 修饰,则子类不能重写它。
【注意,“方法重写(override)”只是实现运行时多态的一种技术手段;多态(Polymorphism)本身是一个更宽泛的概念。】
示例:
// 1. 父类定义:// 类必须用 open 才能被继承,默认实现返回 0
open class Shape {public open func area(): Float64 { // 用 open 声明可重写0.0}func show() { // 非 open,子类不能重写println("面积 = ${this.area()}")}
}// 2. 子类 Circle:重写 area()
class Circle <: Shape {let r: Float64init(r: Float64) {super()this.r = r}public override func area(): Float64 { // 必须写 override3.1415926 * r * r}
}// 3. 子类 Rectangle:再次重写 area()
class Rectangle <: Shape {let w: Float64let h: Float64init(w: Float64, h: Float64) {super()this.w = wthis.h = h}public override func area(): Float64 {w * h}
}// 4. 演示多态
main(): Int64 {let shapes: Array<Shape> = [Circle(2.0), Rectangle(3.0, 4.0)]for (s in shapes) {s.show() // 运行时根据实际对象调用对应 override}0 return 0
}
输出:
面积 = 12.566370
面积 = 12.000000