重学仓颉-5结构体(Struct)完全指南:从基础到高级用法

引言

在仓颉语言中,结构体(Struct)是一种重要的复合数据类型,它允许我们将相关的数据和行为组织在一起。与类(Class)不同,结构体是值类型,具有复制语义,这使得它们在很多场景下更加高效和安全。

1. 结构体的基本概念

1.1 什么是结构体

结构体是仓颉语言中的一种复合数据类型,它以关键字 struct 开头,后跟结构体名称,然后是一对花括号包围的定义体。结构体定义体中可以包含:

  • 成员变量(实例变量和静态变量)
  • 成员属性(实例属性和静态属性)
  • 静态初始化器
  • 构造函数(主构造函数和普通构造函数)
  • 成员函数(实例函数和静态函数)

重要限制:结构体只能定义在源文件的顶层作用域中。

1.2 基本结构体定义示例

cangjie 复制代码
struct Rectangle {
    let width: Int64
    let height: Int64

    public init(width: Int64, height: Int64) {
        this.width = width
        this.height = height
    }

    public func area() {
        width * height
    }
}

这个例子定义了一个名为 Rectangle 的结构体,包含:

  • 两个 Int64 类型的成员变量 widthheight
  • 一个接受两个参数的构造函数
  • 一个计算面积的成员函数

2. 结构体成员变量详解

2.1 实例成员变量

实例成员变量是结构体实例的组成部分,每个实例都有自己独立的成员变量副本。

cangjie 复制代码
struct Person {
    // 不设置初值,但必须标注类型
    let name: String
    let age: Int64
    
    // 设置初值
    let country = "China"
    let isStudent = true
    
    public init(name: String, age: Int64) {
        this.name = name
        this.age = age
    }
}

2.2 静态成员变量

使用 static 修饰符修饰的成员变量属于结构体类型本身,而不是实例。

cangjie 复制代码
struct MathConstants {
    static let PI: Float64
    static let E: Float64
    static let GOLDEN_RATIO: Float64
    
    static init() {
        PI = 3.14159265359
        E = 2.71828182846
        GOLDEN_RATIO = 1.61803398875
    }
}

访问方式

  • 实例成员变量:通过结构体实例访问(如 person.name
  • 静态成员变量:通过结构体类型名访问(如 MathConstants.PI

3. 静态初始化器

3.1 静态初始化器的定义

静态初始化器用于初始化静态成员变量,以 static init 开头,后跟无参参数列表和函数体。

cangjie 复制代码
struct Configuration {
    static let maxConnections: Int64
    static let timeout: Int64
    static let debugMode: Bool
    
    static init() {
        maxConnections = 100
        timeout = 30
        debugMode = false
    }
}

3.2 重要规则

  • 一个结构体中最多只能定义一个静态初始化器
  • 静态初始化器不能被访问修饰符修饰
  • 函数体中必须完成对所有未初始化的静态成员变量的初始化

错误示例

cangjie 复制代码
struct Rectangle {
    static let degree: Int64
    static init() {
        degree = 180
    }
    static init() { // 编译错误:重复定义静态初始化器
        degree = 180
    }
}

4. 构造函数详解

4.1 普通构造函数

普通构造函数以关键字 init 开头,后跟参数列表和函数体。

cangjie 复制代码
struct Point {
    let x: Int64
    let y: Int64

    public init(x: Int64, y: Int64) {
        this.x = x
        this.y = y
    }
    
    public init() {
        this.x = 0
        this.y = 0
    }
}

4.2 构造函数重载

仓颉语言支持构造函数重载,但必须确保参数列表不同:

cangjie 复制代码
struct Circle {
    let radius: Float64
    let centerX: Float64
    let centerY: Float64

    public init(radius: Float64) {
        this.radius = radius
        this.centerX = 0.0
        this.centerY = 0.0
        // 也可以简便的直接调用其他构造函数
        // this(radius, 0.0, 0.0)
    }

    public init(radius: Float64, centerX: Float64, centerY: Float64) {
        this.radius = radius
        this.centerX = centerX
        this.centerY = centerY
    }
}

错误示例

cangjie 复制代码
struct Rectangle {
    let width: Int64
    let height: Int64

    public init(width: Int64) {
        this.width = width
        this.height = width
    }

    public init(height: Int64) { // 编译错误:与第一个构造函数重载冲突
        this.width = height
        this.height = height
    }
}

4.3 主构造函数

主构造函数是结构体的一种特殊构造函数,名字与结构体类型名相同,最多只能定义一个。

cangjie 复制代码
struct Student {
    public Student(let name: String, let age: Int64, let grade: String) {}
}

主构造函数的参数列表中可以使用 letvar 修饰符来定义成员变量形参,这些参数同时扮演定义成员变量和构造函数参数的功能。

带普通参数的主构造函数

cangjie 复制代码
struct Employee {
    public Employee(id: String, let name: String, let department: String) {}
}

4.4 自动生成的构造函数

如果结构体没有自定义构造函数,且所有实例成员变量都有初始值,编译器会自动生成一个无参构造函数:

cangjie 复制代码
struct DefaultRectangle {
    let width: Int64 = 10
    let height: Int64 = 10
    /* 自动生成的无参构造函数:
    public init() {
    }
    */
}

5. 结构体成员函数

5.1 实例成员函数

实例成员函数通过结构体实例调用,可以访问实例成员变量和静态成员变量。

cangjie 复制代码
struct Calculator {
    public Calculator(let baseValue: Int64) {}

    // 被public修饰的函数,必须标注返回类型,否则编译错误
    public func add(value: Int64): Int64 {
        this.baseValue + value
    }

    public func multiply(value: Int64): Int64 {
        this.baseValue * value
    }

    public func getBaseValue(): Int64 {
        this.baseValue
    }
}

5.2 静态成员函数

使用 static 修饰符修饰的成员函数属于结构体类型本身。

cangjie 复制代码
struct StringUtils {
    static func isEmpty(str: String): Bool {
        str.isEmpty()
    }

    static func reverse(str: String): String {
        // 实现字符串反转逻辑
        str
    }
}

5.3 函数中的this关键字

在实例成员函数中,可以通过 this 关键字访问实例成员变量:

cangjie 复制代码
struct BankAccount {
    var balance: Int64 = 0

    // 如果需要修改成员变量的值,函数需要被mut修饰
    public mut func deposit(amount: Int64): Unit {
        this.balance = this.balance + amount
    }

    public mut func withdraw(amount: Int64): Bool {
        if (this.balance >= amount) {
            this.balance = this.balance - amount
            true
        } else {
            false
        }
    }
}

6. 访问修饰符详解

6.1 四种访问级别

仓颉语言提供四种访问修饰符:

  • private:仅在结构体定义内可见
  • internal:当前包及子包内可见(默认修饰符)
  • protected:当前模块可见
  • public:模块内外均可见

6.2 访问控制示例

cangjie 复制代码
package cangjie_blog.a
public struct User {
    public var username: String // 模块内外均可访问
    var email: String // 默认internal,仅当前包及子包可见
    private var password: String // 仅结构体内部可见
    protected var lastLogin: Int64 // 仅当前模块可见

    public init(username: String, email: String, password: String) {
        this.username = username
        this.email = email
        this.password = password
        this.lastLogin = 0
    }

    public func getEmail():String {
        this.email
    }

    private func validatePassword(input: String): Bool {
        input == this.password
    }
}

同包内访问

cangjie 复制代码
package cangjie_blog.a

func samePackageFunction() {
    var user = User("john", "john@example.com", "secret123")
    user.username = "jane" // OK:public成员
    user.email = "jane@example.com" // OK:internal成员
    // user.password = "newpass"     // 编译错误:private成员无法访问
    user.lastLogin = 1234567890   // OK: protected成员可以被同模块访问
}

其他包访问

cangjie 复制代码
package cangjie_blog.b
import cangjie_blog.a.*

func otherPackageFunction() {
    var user = User("john", "john@example.com", "secret123")
    user.username = "jane" // OK:public成员
    user.email = "jane@example.com"   // 编译错误:internal成员无法访问
    // user.password = "newpass"        // 编译错误:private成员无法访问
    user.lastLogin = 1234567890   // OK: protected成员可以被同模块访问
}

7. 创建结构体实例

7.1 基本实例化

通过调用构造函数创建结构体实例:

cangjie 复制代码
package cangjie_blog

import std.math.sqrt

struct Point {
    public var x: Int64
    public var y: Int64

    public init(x: Int64, y: Int64) {
        this.x = x
        this.y = y
    }

    public func distance(): Float64 {
        sqrt(Float64(this.x * this.x + this.y * this.y))
    }
}

main() {
    let point = Point(3, 4)
    let dist = point.distance() // 5.0
    println("Distance: ${dist}")
}

7.2 可变实例

要修改结构体实例的成员变量,需要将变量声明为可变变量(使用 var),且被修改的成员变量也必须是可变的:

cangjie 复制代码
struct Counter {
    public var count: Int64 = 0

    // public 函数必须标注返回值类型
    // 修改成员变量的函数必须被mut修饰
    public mut func increment(): Unit {
        this.count = this.count + 1
    }

    public mut func reset(): Unit {
        this.count = 0
    }
}

main(): Unit {
    var counter = Counter() // 使用var声明可变变量
    println("Initial count: ${counter.count}")
    counter.increment()
    println("After increment: ${counter.count}")
    counter.count = 10 // 直接修改成员变量
    println("After direct assignment: ${counter.count}")
    counter.reset()
    println("After reset: ${counter.count}")
}

7.3 值类型特性

结构体是值类型,赋值和传参时会进行复制:

cangjie 复制代码
struct Color {
    public var red: Int64
    public var green: Int64
    public var blue: Int64

    public init(red: Int64, green: Int64, blue: Int64) {
        this.red = red
        this.green = green
        this.blue = blue
    }

    public func isRed(): Bool {
        this.red > this.green && this.red > this.blue
    }
}

main() {
    var color1 = Color(255, 100, 100)
    var color2 = color1 // 复制color1的值给color2

    println("Color1 is red: ${color1.isRed()}") // true
    println("Color2 is red: ${color2.isRed()}") // true

    color1.red = 100 // 修改color1
    color1.green = 255
    color1.blue = 100

    println("After modification:")
    println("Color1 is red: ${color1.isRed()}") // false
    println("Color2 is red: ${color2.isRed()}") // true(不受影响)
}

8. Mut函数详解

8.1 为什么需要Mut函数

由于结构体是值类型,普通的实例成员函数无法修改实例本身。Mut函数是一种特殊的实例成员函数,可以修改结构体实例的成员变量。

普通函数无法修改成员变量

cangjie 复制代码
struct Counter {
    var count = 0

    public func increment(): Unit {
        // count += 1 // 编译错误:无法在普通函数中修改实例成员变量
    }
}

8.2 Mut函数定义

使用 mut 关键字修饰的实例成员函数:

cangjie 复制代码
struct Counter {
    var count = 0

    public mut func increment(): Unit {
        this.count = this.count + 1 // OK:mut函数可以修改成员变量
    }

    public mut func decrement(): Unit {
        this.count = this.count - 1
    }

    public mut func reset(): Unit {
        this.count = 0
    }

    public func getCount(): Int {
        this.count
    }
}

8.3 Mut函数的使用限制

8.3.1 静态函数不能使用Mut

cangjie 复制代码
struct A {
    public mut func f(): Unit {}        // OK
    public mut static func g(): Unit {} // 编译错误:静态函数不能使用mut修饰
}

8.3.2 Mut函数中的this限制

Mut函数中的 this 不能被捕获,也不能作为表达式:

cangjie 复制代码
struct Foo {
    var i = 0
    
    public mut func f(): Foo {
        // let f1 = { => this }           // 编译错误:mut函数中的this不能被捕获
        // let f2 = { => this.i = 2 }     // 编译错误:实例成员变量不能被捕获
        // let f3 = { => this.i }         // 编译错误:实例成员变量不能被捕获
        // let f4 = { => i }              // 编译错误:实例成员变量不能被捕获
        // this                           // 编译错误:mut函数中的this不能作为表达式
        
        Foo()  // 返回新实例
    }
}

8.3.3 接口中的Mut函数

接口中的实例成员函数也可以使用 mut 修饰:

cangjie 复制代码
interface Mutable {
    mut func increment(): Unit
    func getValue(): Int64
}

struct Counter <: Mutable {
    var value = 0
    
    public mut func increment(): Unit {
        this.value = this.value + 1
    }
    
    public func getValue(): Int64 {
        this.value
    }
}

重要规则

  • 结构体实现接口时必须保持相同的 mut 修饰
  • 类实现接口时不能使用 mut 修饰
cangjie 复制代码
interface I {
    mut func f1(): Unit
    func f2(): Unit
}

struct A <: I {
    public mut func f1(): Unit {}  // OK:与接口保持一致
    public func f2(): Unit {}      // OK:与接口保持一致
}

class C <: I {
    public func f1(): Unit {}      // OK:类不需要mut修饰
    public func f2(): Unit {}      // OK
}

8.3.4 可变性要求

使用 let 声明的结构体变量无法调用 mut 函数:

cangjie 复制代码
struct Counter {
    var count = 0
    
    public mut func increment(): Unit {
        this.count = this.count + 1
    }
}

 main() {
    let counter = Counter()        // 使用let声明,不可变
    // counter.increment()         // 编译错误:let声明的变量无法调用mut函数
    
    var mutableCounter = Counter() // 使用var声明,可变
    mutableCounter.increment()     // OK
}

8.3.5 函数调用限制

mut 函数不能直接调用 mut 函数:

cangjie 复制代码
import std.collection.ArrayList
struct DataProcessor {
    let data: ArrayList<Int64> = ArrayList()
    
    public mut func addValue(value: Int64): Unit {
        this.data.add(value)
        this.processData()  // OK:mut函数可以调用非mut函数
    }
    
    public func processData(): Unit {
        // this.addValue(100)  // 编译错误:非mut函数不能调用mut函数
        println("Processing ${this.data.size} items")
    }
}

9. 递归结构体限制

仓颉语言禁止递归和互递归定义的结构体:

cangjie 复制代码
// 编译错误:递归引用自身
struct R1 {
    let other: R1
}

// 编译错误:互递归定义
struct R2 {
    let other: R3
}
struct R3 {
    let other: R2
}

10. 实际应用示例

10.1 几何图形计算

cangjie 复制代码
struct Circle {
    public var radius: Float64
    public var centerX: Float64
    public var centerY: Float64
    
    public init(radius: Float64, centerX: Float64, centerY: Float64) {
        this.radius = radius
        this.centerX = centerX
        this.centerY = centerY
    }
    
    public func area(): Float64 {
        MathConstants.PI * this.radius * this.radius
    }
    
    public func circumference(): Float64 {
        2.0 * MathConstants.PI * this.radius
    }
    
    public func containsPoint(x: Float64, y: Float64): Bool {
        let dx = x - this.centerX
        let dy = y - this.centerY
        (dx * dx + dy * dy) <= (this.radius * this.radius)
    }
    
    public mut func scale(factor: Float64): Unit {
        this.radius = this.radius * factor
    }
}

struct MathConstants {
    static let PI: Float64 = 3.14159265359
}

10.2 银行账户管理

cangjie 复制代码
import std.collection.ArrayList

struct BankAccount {
    private var accountNumber: String
    private var balance: Int64
    private let transactions: ArrayList<String> = ArrayList()

    public init(accountNumber: String, initialBalance: Int64) {
        this.accountNumber = accountNumber
        this.balance = initialBalance
        this.addTransaction("Account opened with balance: ${initialBalance}")
    }

    public func getAccountNumber(): String {
        this.accountNumber
    }

    public func getBalance(): Int64 {
        this.balance
    }

    public func getTransactionHistory(): Array<String> {
        this.transactions.toArray()
    }

    public mut func deposit(amount: Int64): Bool {
        if (amount > 0) {
            this.balance = this.balance + amount
            this.addTransaction("Deposit: +${amount}")
            true
        } else {
            false
        }
    }

    public mut func withdraw(amount: Int64): Bool {
        if (amount > 0 && this.balance >= amount) {
            this.balance = this.balance - amount
            this.addTransaction("Withdrawal: -${amount}")
            true
        } else {
            false
        }
    }

    private mut func addTransaction(description: String): Unit {
        this.transactions.add(description)
    }
}

10.3 学生信息管理

cangjie 复制代码
import std.collection.{ArrayList, fold}

struct Student {
    public var name: String
    public var studentId: String
    public let grades: ArrayList<Float64> = ArrayList()
    public var isActive: Bool = true

    public init(name: String, studentId: String) {
        this.name = name
        this.studentId = studentId
    }

    public func addGrade(grade: Float64): Unit {
        if (grade >= 0.0 && grade <= 100.0) {
            this.grades.add(grade)
        }
    }

    public func getAverageGrade(): Float64 {
        if (this.grades.size == 0) {
            0.0
        } else {
            let sum = fold(0.0) {
                acc, grade => acc + grade
            }(this.grades)
            sum / Float64(this.grades.size)
        }
    }

    public func getGradeCount(): Int64 {
        this.grades.size
    }

    public mut func deactivate(): Unit {
        this.isActive = false
    }

    public mut func reactivate(): Unit {
        this.isActive = true
    }

    public func getStatus(): String {
        if (this.isActive) {
            "Active"
        } else {
            "Inactive"
        }
    }
}

11. 最佳实践和注意事项

11.1 设计原则

  1. 单一职责:每个结构体应该只负责一个特定的功能领域
  2. 不可变性优先 :优先使用 let 声明成员变量,只在必要时使用 var
  3. 封装性:使用适当的访问修饰符控制成员的可见性
  4. 值语义:充分利用结构体的值类型特性

11.2 性能考虑

  1. 避免大型结构体:大型结构体在复制时可能影响性能
  2. 合理使用静态成员:对于不依赖实例的数据,使用静态成员
  3. 谨慎使用Mut函数:只在确实需要修改实例状态时使用

11.3 常见陷阱

  1. 忘记初始化:确保所有成员变量在构造函数中被正确初始化
  2. 访问修饰符错误:注意不同包之间的访问权限
  3. Mut函数误用:理解何时需要使用Mut函数

参考资料

相关推荐
HarderCoder1 小时前
重学仓颉-7类与接口完全指南:从基础到高级特性
harmonyos
HarderCoder4 小时前
重学仓颉-6枚举与模式匹配完全指南
harmonyos
li理5 小时前
鸿蒙应用开发完全指南:深度解析UIAbility、页面与导航的生命周期
前端·harmonyos
HarmonyOS小助手7 小时前
【推荐+1】HarmonyOS官方模板优秀案例 (第4期:餐饮行业 · 美食菜谱)
harmonyos·鸿蒙·鸿蒙生态
HarderCoder8 小时前
重学仓颉-4函数系统完全指南
harmonyos
HarderCoder13 小时前
重学仓颉-3基本数据类型详解:从理论到实践的全面指南
harmonyos
鸿蒙小灰13 小时前
鸿蒙OpenCV移植技术要点
opencv·harmonyos
鸿蒙先行者13 小时前
鸿蒙分布式能力调用失败解决方案及案例
分布式·harmonyos
大雷神1 天前
鸿蒙中应用闪屏解决方案
华为·harmonyos