华为仓颉语言的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: String
    var age: Int64

    // 构造函数
    public init(name: String, age: Int64) {
        this.name = name
        this.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 是 var
    person2.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 = b
        println("Parent constructor body")
    }
}

class Child <: Parent {
    var x: Int64 = 3 // 步骤1.2:子类字段初始化
    var y: Int64

    // 子类主构造函数
    public init(b: Int64, y: Int64) {
        // 隐式或显式调用父类构造函数!这是步骤2
        super(b) // 步骤2:调用父类构造函数

        // 步骤3.2:执行子类构造函数体
        this.y = y
        println("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: Int64
    public init(b: Int64) {
        this.b = b
        println("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        // 触发 setter
    println("--- 读属性 ---")
    let a = p.age     // 触发 getter
    println("读取到的 age = ${a}")

    println("--- 只读属性 ---")
    println("isAdult = ${p.isAdult}")   // 15 < 18 → false

    p.age = 20        // 再次触发 setter
    println("isAdult = ${p.isAdult}")   // 20 ≥ 18 → true

    0 // 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,没有 constructor
    public 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 = 10

    let 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: Float64
    init(r: Float64) {
        super()
        this.r = r
    }

     public override func area(): Float64 {     // 必须写 override
        3.1415926 * r * r
    }
}

// 3. 子类 Rectangle:再次重写 area()
class Rectangle <: Shape {
    let w: Float64
    let h: Float64
    init(w: Float64, h: Float64) {
        super()
        this.w = w
        this.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

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐