仓颉语法-结构体、枚举和模式匹配

237 阅读4分钟

结构体类型

  1. struct 成员变量分为 实例成员变量 和 静态成员变量(使用 static 修饰符修饰,且必须有初值)。实例成员变量只能通过 struct 实例访问,静态成员变量只能通过 struct 类型名访问

  2. struct 成员函数分为实例成员函数 和 静态成员函数(使用 static 修饰符修饰)。实例成员函数只能通过 struct 实例访问,静态成员函数只能通过 struct 类型名访问;静态成员函数中不能访问实例成员变量,也不能调用实例成员函数,但在实例成员函数中可以访问静态成员变量以及静态成员函数

  3. struct 成员属性分为 实例成员属性 和 静态成员属性(使用 static 修饰符修饰)。实例成员属性只能通过 struct 实例访问,静态成员属性只能通过 struct 类型名访问

  4. 静态构造函数 以关键字组合 static init 开头,后跟无参参数列表和函数体,且不能被访问修饰符修饰。函数体中必须 完成对所有未初始化的静态成员变量的初始化,否则编译报错

  5. 一个 struct 中 最多定义一个静态初始化器

  6. struct 支持 普通构造函数 和 主构造函数主构造函数中 有普通形参和成员变量形参(需要在参数名前加上 let 或 var),普通形参必须在成员变量形参前面

  7. 一个 struct 中 最多定义一个主构造函数

  8. 如果没有构造函数并且所有成员变量都有初始值,编译器会自动生成一个无参的构造器

  9. struct 的成员有 4 种访问修饰符修饰:private、internal、protected 和 public

    • private 表示在 struct 定义内可见
    • internal 表示仅当前包及子包(包括子包的子包)内可见。
    • protected 表示当前模块可见。
    • public 表示模块内外均可见。
  10. struct 类型是值类型,其实例成员函数无法修改实例本身,需要使用mut函数,在 mut 函数内部,this 的语义是特殊的,这种 this 拥有原地修改字段的能力

  11. mut 只能修饰实例成员函数,不能修饰静态成员函数

public struct MyStruct {
    // 只能在本struct内可见
    private let valuePrivate: Int = 0
    // MyStruct所在的包可见
    internal let valueInternal: Int = 1
    // MyStruct 所属的模块可见
    protected let valueProtected: Int = 2
    // 所有模块都可见
    public let valuePublic: Int = 3

    // 静态成员变量只能通过 struct 类型名访问 eg. MyStruct.NAME
    // 使用 static 修饰符修饰,且必须有初值
    // 不可变 静态成员变量
    static let NAME: String = 'NAME'
    // 可变静态成员变量
    static var AGE: Int = 18

    // 实例成员变量只能通过 struct 实例 eg. let s = MyStruct(); printlnt(s.name)
    // 实例成员变量定义时可以不设置初值(但必须标注类型),也可以设置初值
    // 不可变 实例成员变量
    let name: String
    // 可变 实例成员变量
    var age: Int = 20

    // 静态成员函数
    // 静态成员函数只能通过 struct 类型名访问,不能访问实例成员变量和实例成员函数
    public static func nameAge(): String {
        "${NAME} ${AGE}"
    }

    // 实例成员函数
    // 实例成员函数只能通过 struct 实例访问。在实例成员函数中可以访问静态成员变量以及静态成员函数
    public func nameAgeAnother(): String {
        println("MyStruct ${NAME} ${AGE}")
        "${name} ${age} ${another}"
    }

    // 静态成员属性 getter。只能get,不能set
    public static prop GETNAME: String {
        get() {
            NAME
        }
    }
    // 静态成员属性 setter。既能get也能set
    public mut static prop SETAGE: Int {
        get() {
            AGE
        }
        set(v) {
            AGE = v
        }
    }

    // 实例成员属性 getter。只能get不能set
    public prop fullName: String {
        get() {
            NAME + name
        }
    }
    // 实例成员属性 setter。既能get也能set
    public mut prop fullAge: Int {
        get() {
            AGE + age
        }
        set(value) {
            age = value
        }
    }

    // mut函数,用于修改自身属性
    public mut func incAge() {
        this.age += 1
    }

    // 静态初始化器。一个 struct 中最多允许定义一个静态初始化器
    // 在静态初始化器中通过赋值表达式来对静态成员变量进行初始化
    static init() {
        // 这里再次给静态成员变量赋值
        AGE = 20
        // 不能给 不可变静态成员变量 赋值
        // sName = 'aa'
    }
    // 定义(最多)一个主构造函数
    // 主构造函数的名字和 struct 类型名相同,它的参数列表中可以有两种形式的形参:普通形参和成员变量形参(需要在参数名前加上 let 或 var)
    // 成员变量形参同时扮演定义成员变量和构造函数参数的功能
    // 普通形参必须在成员变量形参前面
    public MyStruct(name: String, let another: String) {
        this.name = name
        this.age = 18
    }
    // 普通构造函数以关键字 init 开头,后跟参数列表和函数体
    // 函数体中必须完成对所有未初始化的实例成员变量的初始化
    // 如果参数名和成员变量名无法区分,可以在成员变量前使用 this 加以区分,this 表示 struct 的当前实例
    public init(name: String) {
        // 调用本类的其他 普通构造函数
        this(name, 21)
    }
    // 一个 struct 中可以定义多个普通构造函数,但它们必须构成重载,否则报重定义错误
    public init(name: String, age: Int) {
        this.name = name
        this.age = age
        this.another = 'init(name,age)'
    }
}

public func demo() {
    // 使用 init(name) 普通构造函数 创建一个不可变的MyStruct
    let s = MyStruct('unravel')
    // 访问 实例成员变量
    println(s.name)
    // 因为s是一个let变量,同时MyStruct是一个值类型。所以即使age变量被var修饰。这里也不能修改它
    // s.age = 10
    // 访问 静态成员变量
    println(MyStruct.NAME)

    // 访问 实例成员函数
    println(s.nameAgeAnother())
    // 访问 静态成员函数
    println(MyStruct.nameAge())

    // 访问 实例成员属性
    println(s.fullName)
    // 访问 静态成员属性
    println(MyStruct.GETNAME)

    // 使用 init(name,age) 创建一个可变的MyStruct
    var varS = MyStruct('unravel', 22)
    // 这里可以修改 实例成员变量。因为varS是var,同时age也是var
    varS.age = 22
     // 设置 实例成员属性
    varS.fullAge = 28
    // 调用mut函数修改age
    varS.incAge()
    // 设置 静态成员属性
    MyStruct.SETAGE = 20
}

枚举类型

  1. 定义 enum 时把所有可能的取值一一列出,称这些值为 enum 的构造器(或者 constructor)

  2. enum 体中定义了若干构造器,多个构造器之间使用 | 进行分隔(第一个构造器之前的 | 是可选的)。这些 构造器可以带参也可以不带参

  3. 支持 多个同名构造器,要求这些构造器的 参数个数不同

  4. 支持递归定义,可以定义成员函数、操作符函数和成员属性,构造器、成员函数、成员属性之间不能重名。只能定义get类型的成员属性

  5. enum支持泛型定义,最经典的类型是Option。它包含两个构造器:Some 和 None。其中,Some 会携带一个参数,表示有值,None 不带参数,表示无值

  6. 为了简化使用Option,可以 在类型名前加?表示可选类型。对于任意类型 Ty,?Ty 等价于 Option<Ty>

  7. 当明确知道某个位置需要的是 Option<T> 类型的值时,可以直接传一个 T 类型的值,编译器会用 Option<T> 类型的 Some 构造器将 T 类型的值封装成 Option<T> 类型的值(注意:这里并不是类型转换)

enum MyEnum <: ToString {
    // 这里第一个|可有可无,为了美观,我这里保留了
    // 构造器Red(不带参) 和下面的Red可以共存
    // 认为 没有参数的构造器 的 参数个数等于 0
    | Red
    // 构造器Red(带参)
    | Red(Int)
    // 构造器Green
    | Green(Int)
    // 构造器Blue
    | Blue(Int)
    // 构造器Hud
    | Hud(Int, Int, Int)

    // 实现ToString接口
    public func toString() {
         match (this) {
                case Red => "Red"
                case Red(v) => "Red(${v})"
                case Green(v) => "Green(${v})"
                case Blue(v) => "Blue(${v})"
                case Hud(a, b, c) => "Hud(${a},${b},${c})"
            }
    }

    // 定义 实例成员函数
    public func getRedValue(): Int {
        if (let Red(v) <- this) {
            v
        }
        0
    }

    // 定义 静态成员函数
    public static func anyString(): String {
        '返回任意字符串'
    }

    // 定义 实例成员属性 getter
    public prop sum: Int {
        get() {
            match (this) {
                case Red => 0
                case Red(v) => v
                case Green(v) => v
                case Blue(v) => v
                case Hud(a, b, c) => a + b + c
            }
        }
    }

    // // 定义 实例成员属性 setter
    // 不能定义setter。因为enum不可变
    // public mut prop change: Int {
    //     get() {
    //         match (this) {
    //             case Red => 0
    //             case Red(v) => v
    //             case Green(v) => v
    //             case Blue(v) => v
    //             case Hud(a,b,c) => a + b + c
    //         }
    //     }
    //     set(v) {
    //         println(v)
    //     }
    // }

    // 定义 静态成员属性
    public static prop SUM: Int {
        get() {
            println('静态成员变量 SUM 被调用')
            0
        }
    }

    // 重载运算符()
    public operator func ()(n: UInt): Unit { // cjlint-ignore !G.OPR.01 !G.OPR.02
        for (_ in 0..n) {
            print(this.toString())
        }
    }
}

public func enumDemo() {
    // 通过Blue构造器 构建一个MyEnum
    let myEnum = MyEnum.Blue(12)
    // 调用 实例成员函数
    println(myEnum.getRedValue())
    // 调用 静态成员函数
    println(MyEnum.anyString())
    // 调用 实例成员属性
    println(myEnum.sum)
    // 调用 静态成员属性
    println(MyEnum.SUM)
    // 调用重载 运算符()
    myEnum(2)

    // 使用Option。下面的a和b是一样的,都表示 Option.Some(100)
    let a: Option<Int> = Some(100)
    let b: ?Int = 100

    // c的类型明确标注为Option<Int>,此时编译器会使用Option.Some将100封装成Option.Some(100)
    let c: Option<Int> = 100
    // d也一样,?Int 等价于 Option<Int>
    let d: ?Int = 100

    // 上下文类型不明确时,需要显示标注类型
    let e = None<Int>
    let f = Option<Int>.None
}

模式匹配

  1. 模式匹配适用于match语句、if let语句、while let语句等。

  2. 目前有常量模式、通配符模式、绑定模式、tuple 模式、类型模式和 enum 模式

  3. Tuple 模式和 enum 模式可以嵌套任意模式

  4. 模式可以分为两类:refutable 模式和 irrefutable 模式

    • refutable 模式:有可能和待匹配值不匹配
    • irrefutable 模式:总是可以和待匹配值匹配
refutable 模式irrefutable 模式
常量模式通配符模式
类型模式绑定模式
  • Tuple 模式是 irrefutable 模式,当且仅当其包含的每个模式都是 irrefutable 模式

  • enum 模式是 irrefutable 模式,当且仅当它对应的 enum 类型中只有一个有参构造器,且 enum 模式中包含的其他模式也是 irrefutable 模式

常量模式

  1. 常量模式可以是整数字面量、浮点数字面量、字符字面量、布尔字面量、字符串字面量(不支持字符串插值)、Unit 字面量
  2. 需要常量的值与待匹配值的类型相同、数值相等
  3. 匹配Rune类型的值时,Rune字面量 和 单个字符的字符串字面量都可进行匹配
  4. 匹配Byte类型的值时,Byte字面量 和 表示 ASCII 字符的字符串字面量可进行匹配

通配符模式

使用下划线 _ 表示,可以匹配任意值

绑定模式

  1. 使用 | 连接多个模式时不能使用绑定模式 ,也不可嵌套出现在其它模式中
  2. 绑定模式 id(一个合法标识符) 相当于新定义了一个名为 id 的不可变变量(其作用域从引入处开始到该 case 结尾处
  3. 当模式的 identifier 为 enum 构造器时,该模式会被当成 enum 模式进行匹配,而不是绑定模式

tuple模式

  1. 给定一个 tuple值tv 和一个 tuple模式tp,当且仅当 tv 每个位置处的值均能与 tp 中对应位置处的模式相匹配,才称 tp 能匹配 tv
  2. tuple模式要求匹配和待匹配的元组类型相同、长度相等
  3. 同一个 tuple 模式中不允许引入多个名字相同的绑定模式

类型模式

  1. 类型模式用于判断一个值的运行时类型是否是某个类型的子类型

  2. 有两种形式:_: Type(嵌套一个通配符模式 _)和 id: Type(嵌套一个绑定模式 id)

  3. 对于待匹配值 v 和类型模式 id: Type(或 _: Type)

    • 判断 v 的运行时类型是否是 Type 的子类型,若成立则视为匹配成功,否则视为匹配失败;
    • 如匹配成功,则将 v 的类型转换为 Type 并与 id 进行绑定(对于 _: Type,不存在绑定这一操作

enum模式

  1. 给定一个 enum实例ev 和一个 enum模式ep,当且仅当 ev的构造器名字和 ep的构造器名字相同,且 ev参数列表中每个位置处的值均能与 ep中对应位置处的模式相匹配,才称 ep能匹配 ev
  2. 模式要求匹配和待匹配的enum类型相同、构造器也是同一个
  3. enum 模式支持使用 | 连接,多个模式之间是 或的关系
  4. 使用 match 匹配 enum 值时,要覆盖待匹配 enum 类型中的所有构造器
public func patternDemo() {
    // 需要常量的值与待匹配值的类型相同、数值相等
    // 支持整数字面量、浮点数字面量、字符字面量、布尔字面量、字符串字面量(不支持字符串插值)、Unit 字面量
    // 整数字面量
    match (90) {
        // 常量模式
        case 90 => println('90 match')
        // 绑定模式
        case n where n > 50 => println('${n} match')
        // 通配符模式 _
        case _ => println('not match')
    }
    // 在 匹配Byte类型的值 时,Byte 和 表示 ASCII 字符的字符串字面量 可进行匹配
    let a: Byte = 51
    match (a) {
        // 51是ASCII的字符串3,这里51 和 ‘3’ 都能匹配
        case '3' | 51 => println('3 | 51 match')
        case _ => println('not match')
    }
    // 浮点数字字面量
    match (1.2) {
        case 1.2 => println('1.2 match')
        case _ => println('not match')
    }
    // 字符字面量
    // 在匹配Rune类型的值时,Rune字面量 和 单个字符的字符串字面量 都可进行匹配
    match (r'a') {
        // 这里无论是r'a' 还是 单个字符'a'的字符串 都能匹配到
        case r'a' | 'a' => println('r\'a\' match')
        case _ => println('not match')
    }
    // 布尔字面量
    match (false) {
        case false => println('false match')
        case _ => println('not match')
    }
    // 字符串字面量(不支持字符串插值)
    match ("one") {
        case "one" => println('one match')
        case _ => println('not match')
    }
    // Unit 字面量
    match (()) {
        case () => println('match')
        case _ => println('not match')
    }

    // tuple 模式
    match ((1, 2, 3)) {
        case (1, 2, 3) => println("常量模式匹配元组")
        case (one, 2, 3) => println("绑定+常量模式匹配元组 第一个元素是${one}")
        // 这里的三个模式都会匹配到,分别表示
        // 第一个元素是1的长度为3的元组
        // 第二个元素是2的长度为3的元组
        // 第三个元素是3的长度为3的元组
        case (1, _, _) | (_, 2, _) | (_, _, 3) => println('绑定+通配符模式匹配元组')
        // 类型模式
        case tp: (Int, Int, Int) => println("使用类型模式匹配元组")
        case _ => println('通配符模式匹配元组')
    }
    //  enum 模式
    match (Some(5)) {
        case Some(5) => println("enum模式匹配")
        case _ => println("没有匹配到")
    }
}

match、if-let、while-let表达式

  1. match表达式有两种形式,一种包含待匹配值,一种不包含待匹配值

  2. 包含待匹配值的match

    • 每个case后可以有|分割的多个模式,|分割的多个模式是或的关系,只要一个符合就执行后面语句
    • 每个case语句可以有where子句
    • 所有的case语句需要能穷尽所有情况。一般最后一个使用_兜底
  3. 不包含待匹配值的match

    • 每个case语句后面是结果为Bool的表达式
    • 依次判断这些case中为true的。然后执行后面的语句
    • 同样可以使用_兜底
  4. match表达式也是有类型的

    • 如果上下文指定了类型,所有case语句是这个类型的子类型。
    • 如果没有指定类型,match表达式的类型是所有case类型的最小父类型
  5. if-let 表达式首先对条件中 <- 右侧的表达式进行求值,如果此值能匹配 <- 左侧的模式,则执行 if 分支,否则执行 else 分支(可省略)

    • 可以简单把if let表达式理解成只有两个case的match表达式
    • 这个match表达式一个case是iflet的case。另一个case是通配符case _
  6. while-let 表达式首先对条件中 <- 右侧的表达式进行求值,如果此值能匹配 <- 左侧的模式,则执行循环体,然后重复执行此过程。如果模式匹配失败,则结束循环,继续执行 while-let 表达式之后的代码

  7. irrefutable 的模式还能使用在变量定义和 for in 表达式中


import std.random.Random

public func matchIfWhileLetDemo() {
    // 带匹配值的match
    match (1) {
        // 可以带where子句,where子句是一个结果为Bool的表达式或语句
        case n where n == 1 => println('1 match')
        case _ => println('not match')
    }
    // 不带匹配值的match。可以默认为是 match (true)
    match {
        // 下面第一条,第二条case都符合
        case true => println('true match')
        case 2 > 1 => println('2 > 1 match')
        case _ => println('not match')
    }
    // match表达式的类型
    let x = 2
    // 指定上下文类型为String,所有的case语句的类型必须是String的子类型
    let s: String = match (x) {
        case 0 => "x = 0"
        case 1 => "x = 1"
        case _ => "x != 0 and x != 1"
    }
    // 没有指定上下文类型时,所有case的最小父类型被推断为String。所以match表达式的类型为String
    let s2 = match (x) {
        case 0 => "x = 0"
        case 1 => "x = 1"
        case _ => "x != 0 and x != 1"
    }
    // 没有指定上下文类型时,所有case的最小父类型被推断为Unit。所以match表达式的类型为Unit
    let s3 = match (x) {
        case 0 => println("x = 0")
        case 1 => println("x = 1")
        case _ => println("x != 0 and x != 1")
    }
    // if-let
    let result = Option<Int64>.Some(2023)
    if (let Some(value) <- result) {
        println("操作成功,返回值为:${value}")
    } else {
        println("操作失败")
    }
    // while-let
    // 此函数模拟在通信中接收数据,获取数据可能失败
    func recv(): Option<UInt8> {
        let number = Random().nextUInt8()
        if (number < 128) {
            return Some(number)
        }
        return None
    }
    // 模拟循环接收通信数据,如果失败就结束循环
    while (let Some(data) <- recv()) {
        println(data)
    }
    // 在 for in和 变量定义中使用irrefutable模式
    let _ = 100
    for (_ in 1..5) {
        println("0")
    }
}

参考资料

  1. 仓颉编程语言开发指南 developer.huawei.com/consumer/cn…
  2. 仓颉编程语言白皮书 developer.huawei.com/consumer/cn…
  3. 仓颉编程语言语言规约developer.huawei.com/consumer/cn…