NOTE阅读文档版本:
语言规约 Cangjie-0.53.18-Spec
具体开发指南 Cangjie-LTS-1.0.3
在阅读 了解仓颉的语言规约时, 难免会涉及到一些仓颉的示例代码, 但 我们对仓颉并不熟悉, 所以可以用 仓颉在线体验 快速验证
有条件当然可以直接 配置 Canjie-SDK
WARNING博主在此之前, 基本只接触过 C/C++语言, 对大多现代语言都没有了解, 所以在阅读过程中遇到相似的概念, 难免会与 C/C++中的相似概念作类比, 见谅
此样式内容, 表示文档原文内容
函数
函数是一段完成特定任务的独立代码片段, 可以通过函数名字来标识, 这个名字可以被用来调用函数
仓颉编程语言中函数是一等公民, 函数可以赋值给变量, 或作为参数传递, 或作为返回值返回
函数嘛, C/C++中很熟悉了
但是现代语言中应该新增了很多特性, 逐步了解
函数定义
仓颉编程语言中, 函数的定义遵循以下语法规则:
functionDefinition: functionModifierList? 'func'identifiertypeParameters?functionParameters(':' type)?(genericConstraints)?block;可以总结为如下:
通过关键字
func来定义一个函数
func前可以用函数修饰符进行修饰
func后需要带函数名可选的类型形参列表, 类型形参列表由
<>括起, 多个类型形参之间用,分隔函数的参数由
()括起, 多个参数用,分隔, 并且需要给定每个参数的参数类型可缺省的函数返回类型, 由
:type表示函数定义时必须有函数体, 函数体是一个块(不包含函数形参)
以下示例是一个完整的函数定义具备的所有要素, 它没有使用访问修饰符
public修饰表示其在包内部可访问, 函数名为foo, 有一个Int64类型的参数a, 有返回类型Int64, 有函数体func foo(a: Int64): Int64 { a }函数名不能被进行赋值, 即函数名不能以表达式左值的形式在程序中出现
如以下示例中的操作是禁止的
func f(a: Int64): Int64 { a }// f = {a: Int64 => a + 1} // compile-time error
仓颉中, 函数的定义直观表示是这样的:
修饰符 func 函数名<类型参数列表(可选)>(函数参数列表): 返回值类型 { 函数体}// 类型参数列表格式为: <T1, T2, T3>// 函数参数列表可为空, 不为空格式为: (param1: Type, param2: Type, ...)函数修饰符
全局函数的修饰符
全局函数可以被所有访问修饰符修饰, 默认的可访问性为
internal详细内容请参考包和模块管理章节[访问修饰符]
全局函数是属于包的, 相关的修饰, 应该在包管理相关内容中有介绍
局部函数的修饰符
局部函数无可用修饰符
仓颉允许在函数内定义函数, 不过不能用修饰符
成员函数的修饰符
类成员函数可用修饰符有:
public,protected,private,internal,static,open,override,redef详见[类的成员]以及包和模块管理章节[访问修饰符]接口成员函数可用修饰符有:
static,mut, 详见[接口成员]
struct成员函数可用修饰符有:mut,public,private,internal,static, 详见[struct 类型]
enum成员函数可用修饰符有:public,private,internal,static, 详见[enum 类型]如果不提供可访问修饰符, 接口成员函数以外的成员函数可以在当前包及子包内被访问, 接口成员函数默认是
public语义
struct和enum类型在语言规约之前的文档中有提及, 但是并无修饰符的具体介绍, 所以应该还是在类相关内容中
参数
函数定义时参数列表中参数的顺序: 非命名参数, 命名参数(包括: 不带默认值命名参数和带默认值的参数)
参数列表的语法定义如下:
functionParameters: ('(' (unnamedParameterList (',' namedParameterList)? )? ')')| ('(' (namedParameterList)? ')');nondefaultParameterList: unnamedParameter (',' unnamedParameter)* (',' namedParameter)*| namedParameter (',' namedParameter)*;namedParameterList: (namedParameter | defaultParameter) (',' (namedParameter | defaultParameter))*;namedParameter: identifier '!' ':' type;defaultParameter: identifier '!' ':' type '=' expression;unnamedParameterList: unnamedParameter (',' unnamedParameter)*;unnamedParameter: (identifier | '_') ':' type;
仓颉中, 函数的参数类型有两种: 非命名参数 和 命名参数
且命名参数可以存在默认值, 对应 C++函数参数中的缺省值
对于非命名参数, 可以使用一个
_代替一个函数体中不会使用到的参数func bar(a: Int64 , b!: Float64 = 1.0, s!: String) {} // OKfunc bar2(a: Int64 = 1, b!: Float64 = 1.0, s: String = "Hello") {} // Errorfunc foo(a: Int64, b: Float64)(s: String = "Hello") {} // Errorfunc f1(a: Int64, _: Int64): Int64 {return a + 1}func f2(_: String): Unit {print("Hello Cangjie")}函数参数均为不可变变量, 即均有
let修饰, 在函数定义内不能对其进行赋值func foo(a: Int64, b: Float64) {a = 1 // Error: 参数'a'是不可变的, 不能被赋值b = 1.0 // Error: 参数'b'是不可变的, 不能被赋值}函数的参数类型不受下述”是否是命名形参”、“是否有默认值”的影响;
且讨论参数类型时, 一个类型与它的
alias被视为相同的类型
函数定义例子中有两个错误示例:
- 非命名参数不允许设置初始值
- 命名参数之后, 不允许再出现非命名参数
仓颉中函数的参数均为不可变类型, 是不允许赋值的
命名形参
函数定义时通过 在形参名后添加
!来定义命名形参namedParameter: identifier '!' ':' type;
函数定义时, 命名形参后不允许有非命名形参
func add1(a: Int32, b!: Int32): Int32 { a + b } // okfunc add2(a!: Int32, b: Int32): Int32 { a + b } // error当形参被定义成命名形参后, 调用这个函数时, 则必须在实参值前使用
参数名:前缀来指定这个实参对应的形参, 否则编译报错func add(a: Int32, b!: Int32): Int32 { a + b }add(1, b: 2) // okadd(1, 2) // error如果抽象函数或
open修饰的函数有命名形参, 那么实现函数或override修饰的函数也需要保持同样的命名形参open class A {public open func f(a!: Int32): Int32 {return a + 1}}class B <: A {public override func f(a!: Int32): Int32 { // okreturn a + 1}}class C <: A {public override func f(b!: Int32): Int32 { // errorreturn b + 1}}
仓颉中, 如果函数存在命名参数, 则必须通过参数名: 值来进行传参
参数的默认值
函数的参数可以有默认值, 通过
=来为参数定义默认值当默认值被定义后, 调用这个函数可以忽略这个参数, 函数体会使用默认值
为了便于理解, 有默认值的参数称为可选参数
函数定义时, 可选参数是一种命名形参, 可选参数名后必须添加
!, 否则编译报错;defaultParameter: identifier '!' ':' type '=' expression;如下示例, 我们定义了一个
add函数, 它的参数类型列表(Int32, Int32)函数定义时,
b具有默认值1因此, 当我们调用
add函数, 并且只传递一个值3时,3会被赋值给a, 从而返回结果4如果传入两个值
3和2, 那么b的值为2func add(a: Int32, b!: Int32 = 1): Int32 { a + b }add(3) // invoke add(3, 1), return 4add(3, b: 2) // return 5
了解过 C++的话, 仓颉中存在默认值参数的使用, 也没有太大区别
类或接口中被
open关键字修饰的函数不允许有可选参数操作符函数不允许有可选参数
匿名函数(lambda 表达式)不允许有可选参数
函数参数默认值中引入的名字从静态作用域中获得, 即引入的名字为函数定义时可访问到的名字
函数参数默认值中引入的名字在函数定义时可访问即可, 无需和函数本身的可访问性一致
函数参数和其默认值不属于该函数的函数体
参数默认值在函数调用时求值, 而非在函数定义时求值
规定, 函数调用时参数求值顺序是按照定义时顺序从左到右, 函数参数的默认值可以引用定义在该参数之前的形参
函数调用时, 通过函数名调用可以使用默认值, 通过变量名调用不支持使用默认值
// 编译时错误// func f(a: Int32, b!: Int32 = 2, c: Int32): Int32 { ... }// OK.func f1(a: Int32, b: Int32, c!: Int32 = 3, d!: Int32 = 4): Int32 {a + b + c + d}func test() {f1(1, 2) // 10, f1(1, 2, 3, 4)f1(1, 2, c: 5) // 12, f1(1, 2, 5, 4)}/* 在默认值中引入的名称, 不需要具有与函数相同或更严格的可访问性 */var x = 10var y = 10func g() {}public func f2(a!: Int64 = x * 2 + y, b!: ()->Unit = g) {} // OK.class AAA {static private var x = 10func f(a!: Int64 = x) { // OK, public 方法可以使用私有静态字段print("${a}")x = x + 10print("${x}")}}/*调用函数时, 函数声明中的名称可以使用默认值使用变量名调用函数时, 参数不能是可选的, 即不支持使用默认值*/func f1(): (Int64) -> Unit { g1 }func g1(a!: Int64 = 42) {print("g1: ${a}")}let gg1 = f1()let x = gg1() // Error, 不能省略参数let gg3 = g1let a = gg3() // Error, 不能省略参数
文档中有两句比较不明确的介绍:
- 函数参数默认值中引入的名字从静态作用域中获得, 即引入的名字为函数定义时可访问到的名字
- 函数参数默认值中引入的名字在函数定义时可访问即可, 无需和函数本身的可访问性一致
这两句话的意思其实是:
函数参数的默认值表达式中所引用的名字(变量、函数等), 按照静态作用域规则在函数定义处进行解析, 即 解析的是函数定义时可以看到的名字, 而不是调用时可能可以看到的名字
只要在该位置这些名字是可访问的(无论其访问权限是 private 还是 public), 就可以使用, 不要求默认值中引用的名字具有 与函数定义本身的访问级别(如 public) 相同或更宽松的访问权限
在例子中就是, public成员函数可以引用private成员变量作为参数的默认值
由于函数的形参和其默认值不属于该函数的函数体
所以下面例子中的
return表达式缺少包围它的函数体它既不属于外层函数
f(因为内层函数定义g已经开始), 也不在内层函数f的函数体中:func f() {func g(x! :Int64 = return) { // Error: return 必须在函数体内使用0}1}
这个意思是, 仓颉中函数体 就只是{}的内容, 参数列表什么的都是不算的
函数体
函数体由一个
block组成
局部变量
在仓颉编程语言中, 允许在函数体内定义变量, 将其称为局部变量
变量可以用
var修饰为可变变量或let修饰为不可变变量func foo(): Unit {var a = 1let b = 1}
熟悉 C/C++, 对局部变量再熟悉不过了
嵌套函数
在仓颉编程语言中, 允许在函数体内定义函数, 将其称为嵌套函数
嵌套函数中可以捕获外部函数中定义的变量或其他嵌套函数
嵌套函数支持递归
func foo(): Unit {func add(a: Int32, b: Int32) {a + b}let c = 1func nest(): Unit {print("${c}") // 1var b = add(1, 2) // b = 3}}
仓颉中, 嵌套函数就是在函数体内定义函数
嵌套函数 可以捕获外部函数定义的变量或其他嵌套函数, 其实与顶层作用域定义函数相似
函数的返回类型
函数的返回类型有以下情况:
任何类型
返回值为元组的函数: 可以使用元组类型作为函数的返回类型, 将多个值作为一个复合返回值返回
如以下例子, 它的返回是一个元组
(a, b), 返回类型是(Int32, Int32)func returnAB(a: Int32, b: Int32): (Int32, Int32) {(a, b)}函数类型作为返回类型: 可以使用函数类型作为另一个函数的返回类型
如下示例, 在
:后紧跟的是add函数的类型(Int32, Int32) -> Int32func returnAdd(a: Int32, b: Int32): (Int32, Int32) -> Int32 {return {a, b => a + b} // 返回一个 lambda 表达式}
函数作为仓颉语言中的一等公民, 是可以作为返回值的, 返回值类型也就有函数类型
函数返回元组, 实现多个返回值返回
如果指定函数的返回类型, 则在函数定义的参数列表后使用
:Type指定此时要求函数体的类型、函数体中所有
return e表达式中e的类型是标注的类型(Type)的子类型, 否则编译报错class A {}class B <: A {}// Return is not written.func add(a: Int32, b: Int32): B {var c = a + bif (c > 100) {return B()}else {return A() // 编译错误, 因为 A 不是 B 的子类型}}特别地, 指定返回类型为 Unit 时(如
func f(x:Bool):Unit { x }), 则编译器会在函数体中所有可能返回的地方自动插入表达式return (), 使得函数的返回类型总是为Unit示例如下:
func Column(c: (Data) -> Unit): Unit {2} // return () 会被自动插入func Column(c: (Data) -> Unit) {2} // 返回值类型为 Int64
仓颉的函数还比较有意思, 如果函数返回值类型是Unit, 编译器会在函数体有可能返回的地方 自动添加return ()
所以此时, 可以不用显式书写return ()
如果不指定函数的返回类型 , 则编译器会根据函数体的类型以及函数体中的所有
return表达式来共同推导出函数的返回类型此过程不是完备的, 如遇到(互)递归函数而无法推导它们的返回类型时, 编译报错
(注意: 不能为没有函数体的函数推导其返回类型)
函数的返回类型推导规则如下:
函数体是表达式和声明的序列, 我们将序列的最后一项的类型记为
T0(若块的最后一项是表达式, 则为表达式的类型; 若最后一项为声明, 则T0 = Unit), 再将函数体中所有return e(包括所有子表达式中的return e)表达式中e的类型记为T1 ... Tn, 则函数的返回类型是T0, T1, ..., Tn的最小公共父类型如果不存在最小公共父类型, 则产生一个编译错误
示例如下:
open class Base {}class Child <: Base {}func f(a: Rune) {if (false) {return Base()}return Child()}
- 函数体的类型是块的最后一项的类型, 即
return Child()的类型, 其类型为Nothing- 第一个
return e表达式return Base()中e的类型是Base- 第二个
return e表达式return Child()中e的类型为Child- 由于
Nothing,Base,Child三者的最小公共父类型是Base, 所以该函数的返回类型为Base注: 函数的返回值具有
let修饰的语义
仓颉中, 如果函数定义未指定返回值类型, 那么编译器将自动推导返回类型
推导过程为:
- 记录块中 最后一个表达式 或 声明的类型:
T0 - 记录所有
return e表达式中,e的类型为T1, T2, T3, ... - 找
T0, T1, T2, T3, ...的最小公共父类型, 作为函数的返回类型
仓颉中, 函数的返回值不能修改, 具有let修饰的语义
函数声明
仓颉编程语言中, 函数声明和函数定义的区别是, 前者没有函数体
函数声明的语法如下:
functionDeclaration: functionModifierList? 'func'identifiertypeParameters?functionParameters(':' type)?genericConstraints?;函数声明可以出现在抽象类, 接口中
仓颉中的函数声明与 C/C++中的函数声明有些不同
C/C++中的函数声明, 只是声明一下函数的符号, 此函数是否已经被实现是不确定的
但仓颉中的函数声明, 好像也只是证明一下函数的符号, 且只能出现在抽象类或接口中, 这意味着 仓颉中的函数声明, 表示这个函数一定没有被实现
所以在之后的继承或接口实现中, 必须要实现声明的函数
仓颉中函数的声明 与 函数定义的区别, 就是没有函数体
函数的重定义
对于非泛型函数, 在同一个作用域中, 参数类型完全相同的同名函数被视为重定义, 将产生一个编译错误
以下几种情况要格外注意:
同名函数即使返回类型不同也构成重定义
同名的泛型与非泛型函数永不构成重定义
在继承时, 对于子类中的一个与父类同名且参数类型完全相同的函数, 若其返回类型是父类中的版本的子类型, 则构成覆盖, 也不构成重定义
这是因为子类与父类作用域不同
对于两个泛型函数, 如果重命名一个函数的泛型形参后, 其非泛型部分与另一个函数的非泛型部分构成重定义, 则这两个泛型函数构成重定义
举例如下:
下面这两个泛型函数构成重定义, 因为存在一种
[T1 |-> T2]的重命名(作用到第一个函数上), 使得两个函数的非泛型部分构成重定义func f<T1>(a: T1) {}func f<T2>(b: T2) {}下面这两个泛型函数不构成重定义, 因为找不到上述的一种重命名
func f<X, Y>(a:X, b:Y) {}func f<Y, X>(a:X, b:Y) {}下面的这两个泛型函数构成重定义, 因为存在一种
[X |-> Y, Y |-> X]的重命名使得两个函数的非泛型部分构成重定义func f<X, Y>(a:X, b:Y) {}func f<Y, X>(a:Y, b:X) {}
[T1 |-> T2]是泛型类型参数重命名映射的一种表示, 意思是 将T1映射为T2
文档中的例子, func f<T1>(a: T1) {} 与 func f<T2>(b: T2) {}构成重定义, 因为当[T1 |-> T2]时, 两个泛型函数类型完全一致
但为什么第二个例子不存在这样的情况呢?
对于func f<X, Y>(a:X, b:Y) {}和func f<Y, X>(a:X, b:Y) {}, 如果 [X |-> Y], 得到func f<Y, Y>(a:Y, b:Y) {} 和 func f<Y, Y>(a:Y, b:Y) {}, 两个泛型函数类型不也是完全一致吗?
是完全一致, 但是这是非法的泛型定义, 因为多个泛型类型参数名字一样, 所以多类型参数的泛型, 不存在单独类型参数的映射, 因为泛型的类型参数名字不能存在重复
函数类型
函数类型由函数的参数类型和返回类型组成, 其中参数类型与返回类型之间用
->分隔functionType:: '(' (type (, type)*)? ')' '->' type以下是一些示例:
示例 1: 没有参数、返回类型为
Unitfunc hello(): Unit { print("Hello!") }// function type: () -> Unit示例 2: 参数类型为
Int32, 返回类型为Unitfunc display(a: Int32): Unit { print("${a}") }// function type: (Int32) -> Unit示例 3: 两个参数类型均为
Int32, 返回类型为Int32func add(a: Int32, b: Int32): Int32 { a + b }// function type: (Int32, Int32) -> Int32示例 4: 参数类型为
(Int32, Int32) -> Int32,Int32和Int32, 返回类型为Unitfunc printAdd(add: (Int32, Int32) -> Int32, a: Int32, b: Int32): Unit {print("${add(a, b)}")}// function type: ((Int32, Int32) -> Int32, Int32, Int32) -> Unit示例 5: 两个参数类型均为
Int32, 返回类型为函数类型(Int32, Int32) -> Int32func returnAdd(a: Int32, b: Int32): (Int32, Int32) -> Int32 {{a, b => a + b}}// function type: (Int32, Int32) -> (Int32, Int32) -> Int32示例 6: 两个参数类型均为
Int32, 返回为一个元组类型为:(Int32, Int32)func returnAB(a: Int32, b: Int32): (Int32, Int32) { (a, b) }// function type: (Int32, Int32) -> (Int32, Int32)
仓颉中的每个函数都拥有自己的类型: (参数类型列表) -> 返回值类型
函数调用
有关函数调用表达式的语法, 请参考 函数调用表达式
命名实参
命名实参是指在函数调用时, 在实参值前使用
形参名 :前缀来指定这个实参对应的形参只有在函数定义时使用
!定义的命名形参, 才可以在调用时使用命名实参的语法函数调用时, 所有的命名形参均必须使用命名实参来传参, 否则报错
在函数调用表达式中, 命名实参后不允许出现非命名实参
使用命名实参指定实参值时, 可以不需要和形参列表的顺序保持一致
func add(a!: Int32, b!: Int32): Int32 {a + b}var sum1 = add(1, 2) // errorvar sum2 = add(a: 1, b: 2) // OK, 3var sum3 = add(b: 2, a: 1) // OK, 3
因为函数定义时, 命名形参之后不允许出现非命名形参, 所以后面所有的命名形参都可以不按照参数列表顺序进行传参
函数调用类型检查
本节介绍的是, 给定一个调用表达式, 对调用表达式所需要进行的类型检查
如果被调用的函数涉及重载, 则需要根据函数重载的规则进行类型检查和重载决议
如果函数调用表达式中指定了类型参数, 只有类型实参的个数与类型形参的个数相同才可能通过类型检查, 即假设函数调用表达式为:
f<T1, ..., Tm>(A1, ..., An), 其中给定了m个类型实参, 则函数类型形参数量须为mopen class Base {}class Sub <: Base {}func f<X, Y>(a: X, b: Y) {} // f1func f<X>(a: Base, b: X) {} // f2f<Base>(Base(), Sub()) // f2 may pass the type checking根据调用表达式中的实参 和 调用表达式所在的类型检查上下文中 指定的返回类型
R对函数进行类型检查假设函数定义为:
如果调用表达式带了类型实参:
fi<T1, ..., Tp>(A1, ..., Ak), 那么对于函数fi的类型检查规则如下:
类型实参约束检查: 类型实参
<T1, ..., Tp>需要满足函数fi的类型约束参数类型检查: 将类型实参代入函数
fi的形参后, 满足实参类型(A1, ..., Ak)是类型实参代入形参后类型的子类型返回类型检查: 如果调用表达式的上下文对其有明确类型要求
R, 则需要根据返回类型进行类型检查, 将类型实参代入函数fi的返回类型Ri后, 满足类型实参代入后的返回类型是R的子类型如果调用表达式不带类型实参:
f(A1, ..., An), 那么对于函数fi的类型检查规则如下:
如果**
fi是非泛型函数**, 则按如下规则进行类型检查:
参数类型检查: 实参类型
(A1, ..., Ak)是形参类型的子类型返回类型检查: 如果调用表达式的上下文对其有明确类型要求
R, 则检查函数fi的返回类型Ri是R的子类型open class Base {}class Sub <: Base {}func f(a: Sub) {1} // f1func f(a: Base) {Base()} // f2let x: Base = f(Sub()) // f2 can pass the type checking如果
fi是泛型函数, 则按如下规则进行类型检查:
参数类型检查: 存在代换使得实参类型
(A1, ..., Ak)是形参类型代换后的类型的子类型返回类型检查: 如果调用表达式的上下文对其有明确类型要求
R, 则需要根据返回类型进行类型检查, 将 a) 中的代换代入函数fi的返回类型Ri后, 满足代换后的返回类型是R的子类型需要注意的是:
- 如果函数有缺省值, 在类型检查时会补齐缺省值之后再进行类型检查;
- 如果函数有命名参数, 命名参数的顺序可能会和形参顺序不一致, 在类型检查时, 命名实参要与名字匹配的命名形参对应
总结几点就是:
-
泛型函数, 传入的类型实参数量 需要与 目标泛型函数的类型形参数量 保持一致
-
泛型函数, 如果显式传入类型实参, 则会:
- 进行类型约束的检查, 即 判断是否满足 函数定义时 对实参类型指定的约束条件(
where 条件) - 类型实参传入之后, 函数调用实参的类型要满足 是传入类型实参的子类型
- 根据传入类型实参, 检查最终的返回值类型 是否满足 函数定义的返回类型的子类型
- 进行类型约束的检查, 即 判断是否满足 函数定义时 对实参类型指定的约束条件(
-
如果不显式传入类型实参, 则有可能是非泛型函数
-
如果是非泛型函数, 则会:
- 检查函数调用实参类型 是否满足 函数定义的形参类型的子类型
- 检查返回值类型 是否满足 函数定义的返回类型的子类型
-
如果是泛型函数, 则会:
-
检查调用参数类型
根据函数调用传入的实参类型, 推导类型代换 σ(上面的类型映射数学公式), 使得实参类型满足代换后的形参类型
即 传入的实参的类型, 属于函数形参类型的子类型
-
检查返回类型
还是根据推导和代换的形参类型, 去推导最后的返回值类型 是否满足 函数定义的返回类型的子类型
-
-
文本上比较绕, 但其实核心很简单: 根据实参类型推导、代换形参类型, 再根据形参类型 进行实际的参数检查、返回值类型检查等操作
尾随Lambda
当函数最后一个形参是函数类型, 并且函数调用对应的实参是
lambda时, 我们可以使用尾随lambda语法, 将lambda放在函数调用的尾部, 括号外面func f(a: Int64, fn: (Int64)->Int64) {fn(a)}f(1, { i => i * i }) // 普通函数调用f(1) { i => i * i } // 尾随 lambdafunc g(a!: Int64, fn!: (Int64)->Int64) {fn(a)}g(a: 1, fn: { i => i * i }) // 普通函数调用g(a: 1) { i => i * i } // 尾随 lambda当函数调用有且只有一个
lambda实参时, 我们还可以省略(), 只写lambdafunc f(fn: (Int64)->Int64) {fn(1)}f{ i => i * i }如果尾随
lambda不包含形参,=>可以省略func f(fn: ()->Int64) {fn()}f{ i * i }需要注意的是, 尾随
lambda语法只能用在具有函数名/变量名的函数调用上, 并且尾随lambda的lambda表达式只会解释为函数名/变量名对应的函数的参数这意味着以下两种调用例子是不合法的:
func f(): (()->Unit)->Unit {{a => }}f() {} // Error, lambda 表达式不是 f 的参数func g(a: ()->Unit) {}if (true) { g } else { g } () {} // Error, 非法尾随 lambda 语法必须先将以上的表达式赋值给变量, 使用变量名调用时才可以使用尾随
lambda语法如下面的代码所示:
let f2 = f()f2 {} // oklet g2 = if (true) { g } else { g }g2() {} // ok普通函数调用和构造函数调用都可以使用这个语法, 包含
this()和super()this(1, { i => i * i } )this(1) { i => i * i }super(1, { i => i * i } )super(1) { i => i * i }
仓颉中, 如果函数的最后一个形参类型为函数类型, 就可以使用尾随Lambda语法
即, 实参可以正常传入lambda表达式, 也可以正常函数调用之后(不传入lambda), 跟上一个{lambda}来实现尾随lambda
func f(param1: Int64, param2: Int64, lam: (Int64, Int64)->Int64) { lam(param1, param2)}
f(1, 2, { p1, p2 => p1 * p2 }) // 普通函数调用f(1, 2) { p1, p2 => p1 * p2} // 尾随 lambda这是一个在 C/C++中从未见过的lambda表达式传参方法
变长参数
变长参数是一种特殊的函数调用语法糖, 当形参最后一个非命名参数是
Array类型时, 实参中对应位置可以直接传入参数序列代替Array字面量
变长参数没有特殊的声明语法, 只要求函数声明处最后一个非命名参数是
Array类型变长参数在函数调用时可以使用普通参数列表的形式逐个传入
Array的多个元素非命名参数中, 只有最后一个位置的参数可以使用变长参数
命名参数不能使用这个语法糖
变长参数对全局函数、静态成员函数、实例成员函数、局部函数、构造函数、函数变量、
lambda、函数调用操作符重载、索引操作符重载的调用都适用, 不支持其它操作符重载、compose、pipeline这几种调用方式变长参数的个数可以是 0 个或以上
变长参数只有在函数重载所有情况都不匹配的情况下, 才判断是否可以应用语法糖, 优先级最低
func f1(arr: Array<Int64>) {}func f2(a: Int64, arr: Array<Int64>) {}func f3(arr: Array<Int64>, a: Int64) {}func f4(arr1!: Array<Int64>, a!: Int64, arr2!: Array<Int64>) {}func g() {let li = [1, 2, 3]f1(li)f1(1, 2, 3) // 使用变长参数f1() // 使用变长参数f2(4, li)f2(4, 1, 2, 3) // 使用变长参数f3(1, 2, 3) // Error, Array 不是最后一个参数f4(arr1: 1,2,3, a: 2, arr2: 1,2,3) // Error, 命名参数不能使用变长参数}
仓颉中, 只要函数定义的最后一个参数是Array类型的, 那么就可以当作变长参数使用
当然也可以直接传入Array类型的数据
函数重载决议总是会优先考虑不使用变长参数就能匹配的函数, 只有在所有函数都不能匹配, 才尝试使用变长参数解析
当编译器无法决议时会报错
open class A {func f(v: Int64): Unit { // f1}}class B <: A {func f(v: Array<Int64>): Unit { // f2}}func p1() {let x = B()x.f(1) // call the f1}func g<T>(arg: T): Unit { // g1}func g(arg: Array<Int64>): Unit { // g2}func p2() {g(1) // call the g1}func h(arg: Any): Unit { // h1}func h(arg: Array<Int64>): Unit { // h2}func p3() {h(1) // call the h1}
如果使用变长参数的函数存在重载, 那么编译器会优先决议使用不使用变长参数的版本
函数作用域
在仓颉编程语言中, 函数可以在源程序顶层定义或者在函数内部定义:
全局函数
在源程序顶层定义函数称为全局函数, 它的作用域是全局的
如下示例, 函数
globalFunction是全局函数它的作用域是全局作用域
func globalFunction() {}嵌套函数
在函数体内部定义的函数成为嵌套函数, 它的作用域是局部的, 详见[作用域]
如下示例, 函数
nestedFunction是嵌套函数, 它的作用域是从定义之后开始, 到globalFunction函数体结束func globalFunction() {func nestedFunction() {}}成员函数
在类型定义中可以声明或定义成员函数
成员函数的作用域是整个类型及其扩展
interface Myinterface {func foo(): Unitstatic func bar(): Unit}class MyClass {func foo() {}static func bar() {}}扩展成员函数
在扩展中可以声明额外的成员函数
它的作用域是被扩展类型的所有扩展, 同时受访问修饰符限制
extend MyType {func foo(): Unit {}}
如果你了解过任意一门有面向对象思想的编程语言, 这一部分应该没有什么特别的问题