结构体类型
-
struct 成员变量分为 实例成员变量 和 静态成员变量(使用 static 修饰符修饰,且必须有初值)。实例成员变量只能通过 struct 实例访问,静态成员变量只能通过 struct 类型名访问
-
struct 成员函数分为实例成员函数 和 静态成员函数(使用 static 修饰符修饰)。实例成员函数只能通过 struct 实例访问,静态成员函数只能通过 struct 类型名访问;静态成员函数中不能访问实例成员变量,也不能调用实例成员函数,但在实例成员函数中可以访问静态成员变量以及静态成员函数
-
struct 成员属性分为 实例成员属性 和 静态成员属性(使用 static 修饰符修饰)。实例成员属性只能通过 struct 实例访问,静态成员属性只能通过 struct 类型名访问
-
静态构造函数 以关键字组合 static init 开头,后跟无参参数列表和函数体,且不能被访问修饰符修饰。函数体中必须 完成对所有未初始化的静态成员变量的初始化,否则编译报错
-
一个 struct 中 最多定义一个静态初始化器
-
struct 支持 普通构造函数 和 主构造函数。主构造函数中 有普通形参和成员变量形参(需要在参数名前加上 let 或 var),普通形参必须在成员变量形参前面
-
一个 struct 中 最多定义一个主构造函数
-
如果没有构造函数并且所有成员变量都有初始值,编译器会自动生成一个无参的构造器
-
struct 的成员有 4 种访问修饰符修饰:
private、internal、protected 和 publicprivate表示在 struct 定义内可见internal表示仅当前包及子包(包括子包的子包)内可见。protected表示当前模块可见。public表示模块内外均可见。
-
struct 类型是值类型,其实例成员函数无法修改实例本身,需要使用mut函数,在 mut 函数内部,this 的语义是特殊的,这种 this 拥有原地修改字段的能力
-
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
}
枚举类型
-
定义 enum 时把所有可能的取值一一列出,称这些值为 enum 的构造器(或者 constructor)
-
enum 体中定义了若干构造器,多个构造器之间使用 | 进行分隔(第一个构造器之前的 | 是可选的)。这些 构造器可以带参也可以不带参
-
支持 多个同名构造器,要求这些构造器的 参数个数不同
-
支持递归定义,可以定义成员函数、操作符函数和成员属性,构造器、成员函数、成员属性之间不能重名。只能定义get类型的成员属性
-
enum支持泛型定义,最经典的类型是Option。它包含两个构造器:Some 和 None。其中,Some 会携带一个参数,表示有值,None 不带参数,表示无值
-
为了简化使用Option,可以 在类型名前加?表示可选类型。对于任意类型 Ty,?Ty 等价于 Option<Ty>
-
当明确知道某个位置需要的是 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
}
模式匹配
-
模式匹配适用于match语句、if let语句、while let语句等。
-
目前有常量模式、通配符模式、绑定模式、tuple 模式、类型模式和 enum 模式
-
Tuple 模式和 enum 模式可以嵌套任意模式
-
模式可以分为两类:refutable 模式和 irrefutable 模式
- refutable 模式:有可能和待匹配值不匹配
- irrefutable 模式:总是可以和待匹配值匹配
| refutable 模式 | irrefutable 模式 |
|---|---|
| 常量模式 | 通配符模式 |
| 类型模式 | 绑定模式 |
-
Tuple 模式是 irrefutable 模式,当且仅当其包含的每个模式都是 irrefutable 模式
-
enum 模式是 irrefutable 模式,当且仅当它对应的 enum 类型中只有一个有参构造器,且 enum 模式中包含的其他模式也是 irrefutable 模式
常量模式
- 常量模式可以是整数字面量、浮点数字面量、字符字面量、布尔字面量、字符串字面量(不支持字符串插值)、Unit 字面量
- 需要常量的值与待匹配值的类型相同、数值相等
- 在匹配Rune类型的值时,Rune字面量 和 单个字符的字符串字面量都可进行匹配
- 在匹配Byte类型的值时,Byte字面量 和 表示 ASCII 字符的字符串字面量可进行匹配
通配符模式
使用下划线 _ 表示,可以匹配任意值
绑定模式
- 使用 | 连接多个模式时不能使用绑定模式 ,也不可嵌套出现在其它模式中
- 绑定模式 id(一个合法标识符) 相当于新定义了一个名为 id 的不可变变量(其作用域从引入处开始到该 case 结尾处)
- 当模式的 identifier 为 enum 构造器时,该模式会被当成 enum 模式进行匹配,而不是绑定模式
tuple模式
- 给定一个 tuple值tv 和一个 tuple模式tp,当且仅当 tv 每个位置处的值均能与 tp 中对应位置处的模式相匹配,才称 tp 能匹配 tv
- tuple模式要求匹配和待匹配的元组类型相同、长度相等
- 同一个 tuple 模式中不允许引入多个名字相同的绑定模式
类型模式
-
类型模式用于判断一个值的运行时类型是否是某个类型的子类型。
-
有两种形式:
_: Type(嵌套一个通配符模式 _)和 id: Type(嵌套一个绑定模式 id) -
对于待匹配值 v 和类型模式 id: Type(或 _: Type)
- 判断 v 的运行时类型是否是 Type 的子类型,若成立则视为匹配成功,否则视为匹配失败;
- 如匹配成功,则将 v 的类型转换为 Type 并与 id 进行绑定(对于 _: Type,不存在绑定这一操作
enum模式
- 给定一个 enum实例ev 和一个 enum模式ep,当且仅当 ev的构造器名字和 ep的构造器名字相同,且 ev参数列表中每个位置处的值均能与 ep中对应位置处的模式相匹配,才称 ep能匹配 ev
- 模式要求匹配和待匹配的enum类型相同、构造器也是同一个
- enum 模式支持使用 | 连接,多个模式之间是 或的关系
- 使用 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表达式
-
match表达式有两种形式,一种包含待匹配值,一种不包含待匹配值
-
包含待匹配值的match
- 每个case后可以有|分割的多个模式,|分割的多个模式是或的关系,只要一个符合就执行后面语句
- 每个case语句可以有where子句
- 所有的case语句需要能穷尽所有情况。一般最后一个使用_兜底
-
不包含待匹配值的match
- 每个case语句后面是结果为Bool的表达式
- 依次判断这些case中为true的。然后执行后面的语句
- 同样可以使用_兜底
-
match表达式也是有类型的
- 如果上下文指定了类型,所有case语句是这个类型的子类型。
- 如果没有指定类型,match表达式的类型是所有case类型的最小父类型
-
if-let 表达式首先对条件中 <- 右侧的表达式进行求值,如果此值能匹配 <- 左侧的模式,则执行 if 分支,否则执行 else 分支(可省略)
- 可以简单把if let表达式理解成只有两个case的match表达式
- 这个match表达式一个case是iflet的case。另一个case是通配符case _
-
while-let 表达式首先对条件中 <- 右侧的表达式进行求值,如果此值能匹配 <- 左侧的模式,则执行循环体,然后重复执行此过程。如果模式匹配失败,则结束循环,继续执行 while-let 表达式之后的代码
-
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")
}
}
参考资料
- 仓颉编程语言开发指南 developer.huawei.com/consumer/cn…
- 仓颉编程语言白皮书 developer.huawei.com/consumer/cn…
- 仓颉编程语言语言规约developer.huawei.com/consumer/cn…