NOTE阅读文档版本:
语言规约 Cangjie-0.53.18-Spec
具体开发指南 Cangjie-LTS-1.0.3
在阅读 了解仓颉的语言规约时, 难免会涉及到一些仓颉的示例代码, 但 我们对仓颉并不熟悉, 所以可以用 仓颉在线体验 快速验证
有条件当然可以直接 配置 Canjie-SDK
WARNING博主在此之前, 基本只接触过 C/C++语言, 对大多现代语言都没有了解, 所以在阅读过程中遇到相似的概念, 难免会与 C/C++中的相似概念作类比, 见谅
且, 本系列是文档阅读, 而不是仓颉的零基础教学, 所以如果要跟着阅读的话最好有一门编程语言的开发经验
WARNING在阅读仓颉编程语言的开发指南之前, 已经大概阅读了一遍 仓颉编程语言的语言规约,已经对仓颉编程语言有了一个大概的了解
所以在阅读开发指南时,不会对类似: 类、函数、结构体、接口等解释起来较为复杂名称 做出解释
此样式内容, 表示文档原文内容
函数
嵌套函数
定义在源文件顶层的函数被称为全局函数
定义在函数体内的函数被称为嵌套函数
示例,函数
foo内定义了一个嵌套函数nestAdd,可以在foo内调用该嵌套函数nestAdd,也可以将嵌套函数nestAdd作为返回值返回,在foo外对其进行调用:func foo() {func nestAdd(a: Int64, b: Int64) {a + b + 3}println(nestAdd(1, 2)) // 6return nestAdd}main() {let f = foo()let x = f(1, 2)println("result: ${x}")}程序会输出:
6result: 6
仓颉函数, 除了在函数外(源文件顶层或结构体、类内)定义, 即 全局函数 或 成员函数
还可以在函数体内定义, 这样在函数体内定义的函数被称为 嵌套函数
嵌套函数可以直接在函数中被调用, 还可以作为函数的返回值返回
Lambda表达式
Lambda表达式定义
Lambda表达式是一种匿名函数(即没有函数名的函数), 其核心设计目的是在程序中快速定义简短的函数逻辑,无需显式声明函数名称这一概念起源于数学中的
λ演算(lambda calculus),后被引入多种编程语言(如C++、Python、C#等),用于简化代码并提升灵活性仓颉编程语言中也引入了
Lambda表达式,具体使用介绍将在本小节展开介绍
Lambda表达式的语法为如下形式:{ p1: T1, ..., pn: Tn => expressions | declarations }其中:
=>之前为参数列表,多个参数之间使用,分隔,每个参数名和参数类型之间使用:分隔
=>之前也可以没有参数
=>之后为Lambda表达式体,是一组表达式或声明序列
Lambda表达式的参数名的作用域与函数的相同,为Lambda表达式的函数体部分,其作用域级别可视为与Lambda表达式的函数体内定义的变量等同let f1 = { a: Int64, b: Int64 => a + b }var display = { => // 无参数 lambda 表达式println("Hello")println("World")}
lambda表达式是一种匿名函数语法, C++中也存在
仓颉的lambda表达式语法为:
{ 参数列表 => 一组声明或表达式序列 }要强调的是, 参数列表不能用()包裹, 同时 一组声明或表达式序列 也不能被{}包裹
lambda参数列表, 就像函数的参数列表一样, 只不过不用()包裹
一组声明或表达式序列, 在lambda表达式中 不用{}包裹 也是被看作一个整体的, 所以可以随意按合法语法换行
lambda表达式的参数, 也可以在lambda表达式函数体内使用
Lambda表达式不管有没有参数,都不可以省略=>,除非其作为尾随lambda例如:
var display = { => println("Hello") }func f2(lam: () -> Unit) {}let f2Res = f2 { println("World") } // OK, 省略 =>
仓颉lambda表达式的=>不允许被省略, 除非是尾随lambda
不过, 也只有在lambda参数为空时, 尾随lambda的=>可以省略
TIP尾随
lambda
lambda表达式作为函数的最后一个参数, 可以以**尾随lambda**形式传参即, 当函数的最后一个形参是函数类型时, 调用时最后一个参数要传入
lambda表达式时, 可以使用尾随lambda的形式尾随
lambda形式, 不是将lambda表达式传入函数的参数列表中, 而是在函数调用时将lambda表达式声明在函数调用之后:举个例子:
func function(param: Int64, lam: (Int64, String) -> Unit) {lam(param, "Cangjie")}main() {// 正常传参function(10, {param1: Int64, param2: String => println("result: ${param1 * param2.size}")})// 尾随lambdafunction(10) {param1: Int64, param2: String => println("result: ${param1 * param2.size}")}}这两种调用方式都是可以的, 一个
lambda表达式正常作为实参传入, 一个以尾随lambda的形式传入
Lambda表达式中参数的类型标注可缺省以下情形中, 若参数类型省略,编译器会尝试进行类型推断,当编译器无法推断出类型时会编译报错:
Lambda表达式 赋值给变量 时,其参数类型根据变量类型推断
Lambda表达式 作为 函数调用表达式的实参 使用时,其参数类型根据函数的形参类型推断// 参数类型由变量 sum1 的类型推断得出var sum1: (Int64, Int64) -> Int64 = { a, b => a + b }var sum2: (Int64, Int64) -> Int64 = { a: Int64, b => a + b }func f(a1: (Int64) -> Int64): Int64 {a1(1)}main(): Int64 {// lambda 的参数类型是从函数 f 的类型推断出来的f({ a2 => a2 + 10 })}
lambda表达式的参数的类型是可以省略的
但, 编译器要能根据上下文推断出来, 否则报错
比如, 给变量赋值时, 根据变量声明类型进行推断; 作为实参传入参数形参时, 根据形参类型进行推断
Lambda表达式中不支持声明返回类型,其返回类型总是从上下文中推断出来,若无法推断则报错
若上下文明确指定了
Lambda表达式的返回类型,则其返回类型为上下文指定的类型
Lambda表达式赋值给变量时,其返回类型根据 变量类型 推断返回类型:let f: () -> Unit = { ... }
Lambda表达式作为参数使用时,其返回类型根据 使用处所在的函数调用的形参类型 推断:func f(a1: (Int64) -> Int64): Int64 {a1(1)}main(): Int64 {f({ a2: Int64 => a2 + 10 })}
Lambda表达式作为返回值使用时,其返回类型根据 使用处所在函数的返回类型 推断:func f(): (Int64) -> Int64 {{ a: Int64 => a }}若上下文中类型未明确,与推导函数的返回值类型类似,编译器会根据
Lambda表达式体中所有return表达式return xxx中xxx的类型,以及Lambda表达式体的类型,来共同推导出Lambda表达式的返回类型
=>右侧的内容与普通函数体的规则一样,返回类型为Int64:let sum1 = { a: Int64, b: Int64 => a + b }
=>的右侧为空,返回类型为Unit:let f = { => }
仓颉ladmbda的参数类型 是可缺省的
但 返回类型是不可指定的, 只能根据上下文进行推导:
-
若 使用处的上下文 有指定返回值类型, 那么 就根据指定的类型进行推导
-
若 使用处的上下文 没有指定返回值类型, 那么 就根据
lambda表达式体中 所有return expr表达式中的expr类型 以及lambda表达式体的类型 共同进行推导
这部分在具体使用时应该可以有更熟悉、深入的了解
Lambda 表达式调用
Lambda表达式支持立即调用,例如:let r1 = { a: Int64, b: Int64 => a + b }(1, 2) // r1 = 3let r2 = { => 123 }() // r2 = 123
Lambda表达式也可以赋值给一个变量,使用变量名进行调用,例如:func f() {var g = { x: Int64 => println("x = ${x}") }g(2)}
其实调用在上面了解定义时, 就已经见过了
闭包 **
一个函数或
lambda从定义它的静态作用域中捕获了变量,函数或lambda和捕获的变量一起被称为一个闭包,这样即使脱离了闭包定义所在的作用域,闭包也能正常运行
闭包, 我个人理解就是:
-
闭包形成:
如果一个函数/
lambda, 捕获了定义时 函数/lambda外部的非全局或静态变量时, 此函数和其捕获的变量, 形成一个闭包闭包形成之后, 闭包封闭、包装, 将捕获的变量”包装”到函数/
lambda内部, 不再直接依赖外部变量但, 这里的”包装”形式 针对不同的类型也是有区别的:
var或let变量有区别, 值类型和引用类型变量也有区别 -
闭包调用
闭包可以直接像函数一样调用, 非特殊通常情况下也可以作为一等公民使用
而且, 调用时能够访问到捕获的变量, 即使 闭包调用时的作用域 已经不在 被捕获变量 定义时的有效作用域
函数或
lambda的定义中对于以下几种变量的访问,称为变量捕获:
函数的参数缺省值中 访问了 本函数之外定义的局部变量
函数或
lambda内 访问了 本函数或本lambda之外定义的局部变量
class/struct内定义的不是成员函数的函数或lambda访问了实例成员变量或this以下情形的变量访问不是变量捕获:
对定义在本函数或本
lambda内的局部变量的访问对本函数或本
lambda的形参的访问对全局变量和静态成员变量的访问
对实例成员变量在实例成员函数或属性中的访问
由于实例成员函数或属性将
this作为参数传入,在实例成员函数或属性内通过this访问所有实例成员变量变量的捕获发生在闭包定义时,因此变量捕获有以下规则:
被捕获的变量必须在闭包定义时可见,否则编译报错
被捕获的变量必须在闭包定义时已经完成初始化,否则编译报错
仓颉中, 捕获变量动作发生在闭包定义时
且仓颉闭包中使用到的外部变量, 会被自动捕获
并不是 所有变量在闭包访问都属于变量捕获
变量捕获只针对 非本函数/本lambda的局部变量, 以及成员变量或this
静态变量或全局变量的访问, 不属于变量捕获, 对本身形参的访问就更不属于了
具体下面有所展示
示例 1:闭包
add,捕获了let声明的局部变量num,之后通过返回值返回到num定义的作用域之外,调用add时仍可正常访问numfunc returnAddNum(): (Int64) -> Int64 {let num: Int64 = 10func add(a: Int64) {return a + num}add}main() {let f = returnAddNum()println(f(10))}程序输出的结果为:
20
此例中, 嵌套函数add 捕获了 外层函数中let声明的局部变量num, 形成闭包
变量num被”包装”到add中, add作为一等公民返回
示例 2:捕获的变量必须在闭包定义时可见
func f() {let x = 99func f1() {println(x)}let f2 = { =>println(y) // Error, 无法捕获尚未定义的'y'}let y = 88f1() // Print 99f2()}示例 3:捕获的变量必须在闭包定义前完成初始化
func f() {let x: Int64func f1() {println(x) // Error, x 还未初始化}x = 99f1()}
仓颉捕获变量, 只能捕获可见的变量, 且只能捕获已经初始化的变量, 否则编译错误
如果捕获的变量是引用类型,可修改其可变实例成员变量的值
class C {public var num: Int64 = 0}func returnIncrementer(): () -> Unit {let c: C = C()func incrementer() {c.num++}incrementer}main() {let f = returnIncrementer()f() // c.num 增加 1}
仓颉中, 闭包捕获引用类型变量, 可以通过捕获的变量修改原实例的成员变量
为了防止捕获了
var声明变量的闭包逃逸,这类闭包只能被调用,不能作为一等公民使用,包括不能赋值给变量,不能作为实参或返回值使用,不能直接将闭包的名字作为表达式使用func f() {var x = 1let y = 2func g() {println(x) // OK, 捕获一个可变变量}let b = g // Error, g 不能赋值给变量g // Error, g 不能用作表达式g() // OK, g 可以被调用g // Error, g 不能用作返回值}
仓颉规定, var声明的变量被捕获之后, 闭包只能被调用, 不能作为一等公民使用
主要是为了防止引用类型变量的闭包逃逸
值类型变量被捕获之后, 闭包中拥有的是变量的副本
但引用类型变量被捕获则不同, 引用类型变量被捕获捕获的就是原引用类型变量, 你是可以对原实例的成员做出修改的
需要注意的是,捕获具有传递性
如果一个函数
f调用了捕获var变量的函数g,且g捕获的var变量不在函数f内定义,那么函数f同样捕获了var变量,此时,f也不能作为一等公民使用以下示例中,
g捕获了var声明的变量x,f调用了g,且g捕获的x不在f内定义,f同样不能作为一等公民使用:func h(){var x = 1func g() { x } // 捕获一个可变变量func f() {g() // 调用 g}return f // Error}以下示例中,g 捕获了 var 声明的变量 x,f 调用了 g。但 g 捕获的 x 在 f 内定义,f 没有捕获其他 var 声明的变量。因此,f 仍作为一等公民使用:
func h(){func f() {var x = 1func g() { x } // 捕获一个可变变量g()}return f // Ok}静态成员变量和全局变量的访问,不属于变量捕获,因此访问了 var 修饰的全局变量、静态成员变量的函数或 lambda 仍可作为一等公民使用。
class C {static public var a: Int32 = 0static public func foo() {a++ // OKreturn a}}var globalV1 = 0func countGlobalV1() {globalV1++C.a = 99let g = C.foo // OK}func g(){let f = countGlobalV1 // OKf()}