NOTE阅读文档版本:
语言规约 Cangjie-0.53.18-Spec
具体开发指南 Cangjie-LTS-1.0.3
在阅读 了解仓颉的语言规约时, 难免会涉及到一些仓颉的示例代码, 但 我们对仓颉并不熟悉, 所以可以用 仓颉在线体验 快速验证
有条件当然可以直接 配置Canjie-SDK
WARNING博主在此之前, 基本只接触过C/C++语言, 对大多现代语言都没有了解, 所以在阅读过程中遇到相似的概念, 难免会与C/C++中的相似概念作类比, 见谅
且, 本系列是文档阅读, 而不是仓颉的零基础教学, 所以如果要跟着阅读的话最好有一门编程语言的开发经验
WARNING在阅读仓颉编程语言的开发指南之前, 已经大概阅读了一遍 仓颉编程语言的语言规约,已经对仓颉编程语言有了一个大概的了解
所以在阅读开发指南时,不会对类似: 类、函数、结构体、接口等解释起来较为复杂名称 做出解释
此样式内容, 表示文档原文内容
函数
如果你了解过C/C++, 相信对函数并不陌生
定义函数
仓颉使用关键字
func来表示函数定义的开始,func之后依次是函数名、参数列表、可选的函数返回值类型、函数体其中,函数名可以是任意的合法标识符,参数列表定义在一对圆括号内(多个参数间使用逗号分隔),参数列表和函数返回值类型(如果存在)之间使用冒号分隔,函数体定义在一对花括号内
函数定义举例:
func add(a: Int64, b: Int64): Int64 {return a + b}上例中定义了一个名为
add的函数,其参数列表由两个Int64类型的参数a和b组成,函数返回值类型为Int64,函数体中将a和b相加并返回下面依次对函数定义中的参数列表、函数返回值类型和函数体作进一步介绍
仓颉的函数定义语法:
func 函数名(参数列表): 返回值类型 { // 函数体}参数列表
一个函数可以拥有
0个或多个参数,这些参数均定义在函数的参数列表中根据函数调用时是否需要给定参数名,可以将参数列表中的参数分为两类: 非命名参数和命名参数
非命名参数的定义方式是
p: T,其中p表示参数名,T表示参数p的类型,参数名和其类型间使用冒号连接例如,上例中
add函数的两个参数a和b均为非命名参数命名参数的定义方式是
p!: T,与非命名参数的不同是在参数名p之后多了一个!可以将上例中
add函数的两个非命名参数修改为命名参数,如下所示:func add(a!: Int64, b!: Int64): Int64 {return a + b}命名参数还可以设置默认值,通过
p!: T = e方式将参数p的默认值设置为表达式e的值例如,可以将上述
add函数的两个参数的默认值都设置为1:func add(a!: Int64 = 1, b!: Int64 = 1): Int64 {return a + b}注意:
只能为命名参数设置默认值,不能为非命名参数设置默认值
仓颉函数的参数, 存在命名参数和非命名参数两种
非命名参数的声明语法为:参数名: 参数类型
而命名参数的声明语法为:参数名!: 参数类型, 命名参数的声明要多个感叹号
且可以为命名参数可以存在缺省值, 即 可以设置默认值也可以不设置默认值
如果函数的参数都为命名参数, 且都存在默认值, 那么调用时可以不传参
参数列表中可以同时定义非命名参数和命名参数,但是需要注意的是,非命名参数只能定义在命名参数之前,也就意味着 命名参数之后不能再出现非命名参数
例如,下例中
add函数的参数列表定义是不合法的:func add(a!: Int64, b: Int64): Int64 { // Error, 命名参数 'a' 必须定义在非命名参数 'b' 之后return a + b}非命名参数和命名参数的主要差异在于调用时的不同,具体可参见下文调用函数中的介绍
函数参数均为不可变变量,在函数定义内不能对其赋值
func add(a: Int64, b: Int64): Int64 {a = a + b // Errorreturn a}函数参数作用域从定义处起至函数体结束:
func add(a: Int64, b: Int64): Int64 {var a_ = a // OKvar b = b // Error, 重定义 'b'return a}
仓颉的非命名参数不允许声明在命名参数之前
且, 仓颉函数的参数均为不可变变量, 即 无法修改形参的值
函数返回值类型
函数返回值类型是函数被调用后得到的值的类型
函数定义时,返回值类型是可选的: 可以显式地定义返回值类型(返回值类型定义在参数列表和函数体之间),也可以不定义返回值类型,交由编译器推导确定
当显式地定义了函数返回值类型时,就要求函数体的类型(关于如何确定函数体的类型可参见下节函数体)、函数体中所有
return e表达式中e的类型是返回值类型的子类型例如,对于上述
add函数,显式地定义了它的返回值类型为Int64;如果将函数体中的return a + b修改为return (a, b),则会因为类型不匹配而报错:// Error, return 后表达式的类型 与 函数的返回类型不匹配func add(a: Int64, b: Int64): Int64 {return (a, b)}在函数定义时如果未显式定义返回值类型,编译器将根据函数体的类型以及函数体中所有的
return表达式来共同推导出函数的返回值类型例如,下例中
add函数的返回值类型虽然被省略,但编译器可以根据return a + b推导出add函数的返回值类型是Int64:func add(a: Int64, b: Int64) {return a + b}注意:
函数的返回值类型并不是任何情况下都可以被推导出来的,如果返回值类型推导失败,编译器会报错
指定返回类型为
Unit时,编译器会在函数体中所有可能返回的地方自动插入表达式return (),使得函数的返回类型总是为Unit
仓颉函数的返回值类型, 可以显式声明也可以省略, 省略时, 编译器会根据函数体内的上下文进行推导
当显式声明返回值类型时, 函数体内的返回值的类型, 必须要是目标类型或目标类型的子类型
函数体
函数体中定义了函数被调用时执行的操作,通常包含一系列的变量定义和表达式,也可以包含新的函数定义(即嵌套函数)
如下
add函数的函数体中首先定义了Int64类型的变量r(初始值为0),接着将a + b的值赋值给r,最后将r的值返回:func add(a: Int64, b: Int64) {var r = 0r = a + breturn r}在函数体的任意位置都可以使用
return表达式来终止函数的执行并返回
return表达式有两种形式:return和return expr(expr是一个表达式)对于
return expr,要求expr的类型与函数定义中的返回值类型保持一致例如,下例中会因为
return 100中100类型(Int64)和函数foo的返回值类型(String)不同而报错// Error, 无法将整型字面量转换为“Struct-String”类型func foo(): String {return 100}对于
return,其等价于return (),所以要求函数的返回值类型为Unitfunc add(a: Int64, b: Int64) {var r = 0r = a + breturn r}func foo(): Unit {add(1, 2)return}注意:
return表达式作为一个整体,其类型并不由后面跟随的表达式决定,而是Nothing类型
函数体是函数被调用时, 会执行的操作
函数体内, 是一系列的表达式或声明, 甚至是函数定义(嵌套函数)
在函数被调用时, 函数体内的表达式按顺序、逻辑会一一被执行
函数体内可以通过调用return表达式, 终止函数的执行并返回
可以是return也可以是return expr, expr的类型要满足是函数返回值类型或其子类型
expr值就是此次函数调用的返回值, return等价于return ()
在函数体内定义的变量属于局部变量的一种(如上例中的
r变量),它的作用域从其定义之后开始到函数体结束对于一个局部变量,允许在其外层作用域中定义同名变量,并且在此局部变量的作用域内,局部变量会“遮盖”外层作用域的同名变量
例如:
let r = 0func add(a: Int64, b: Int64) {var r = 0r = a + breturn r}上例中,
add函数之前定义了Int64类型的全局变量r,同时add函数体内定义了同名的局部变量r,那么在函数体内,所有使用变量r的地方(如r = a + b),用到的将是局部变量r,即(在函数体内)局部变量r“遮盖”了全局变量r
函数体内定义的变量, 叫做局部变量, 局部变量可以与更外层的变量重名, 此时 局部变量会遮盖更外层的变量
此时, 再访问同名的变量, 访问的是新定义的局部变量
函数返回值类型中提到函数体也是有类型的,函数体的类型是函数体内最后一“项”的类型: 若最后一项为表达式,则函数体的类型是此表达式的类型,若最后一项为变量定义或函数声明,或函数体为空,则函数体的类型为
Unit例如:
func add(a: Int64, b: Int64): Int64 {a + b}上例中,因为函数体的最后一“项”是
Int64类型的表达式(即a + b),所以函数体的类型也是Int64,与函数定义的返回值类型相匹配又如,下例中函数体的最后一项是
Unit,同样与函数定义的返回值类型相匹配:func foo(): Unit {let s = "Hello"print(s)}
仓颉的函数体也拥有类型, 其类型是函数体类的最后一个”项”的类型
如果是表达式, 那么函数体类型就是表达式的类型
如果是变量定义或函数声明等, 那么函数体类型就是Unit
函数体类型 要为 函数的返回值类型 或 其子类型
如果函数体内没有return表达式, 那么, 函数体内的最后一项的值会自动作为函数的返回值
调用函数
函数调用的形式为
f(arg1, arg2, ..., argn)其中,
f是要调用的函数的名字,arg1到argn是n个调用时的参数(称为实参),要求每个实参的类型必须是对应参数类型的子类型实参可以有
0个或多个,当实参个数为0时,调用方式为f()根据函数定义时参数是非命名参数还是命名参数的差异,函数调用时传实参的方式也有所不同:
- 对于非命名参数,它对应的实参是一个表达式
-对于命名参数,它对应的实参需要使用
p: e的形式,其中p是命名参数的名字,e是表达式(即传递给参数p的值)非命名参数调用举例:
func add(a: Int64, b: Int64) {return a + b}main() {let x = 1let y = 2let r = add(x, y)println("The sum of x and y is ${r}")}执行结果为:
The sum of x and y is 3命名参数调用举例:
func add(a: Int64, b!: Int64) {return a + b}main() {let x = 1let y = 2let r = add(x, b: y)println("The sum of x and y is ${r}")}执行结果为:
The sum of x and y is 3
仓颉的函数调用 与C/C++ 没有太大差别:
funcname(param1, param2, param3, ...)
传参, 需要按照函数声明的参数类型顺序进行
但仓颉函数还存在命名参数, 命名参数在进行传参时, 必须指定参数名:
func add(a: Int64, b!: Int64) { return a + b}
main() { let sum = add(1, b: 2) println("1 + 2 = ${sum}")}b: 2, b就是形参名, 2是实参值
对于多个命名参数,调用时的传参顺序可以和定义时的参数顺序不同
例如,下例中调用
add函数时b可以出现在a之前:func add(a!: Int64, b!: Int64) {return a + b}main() {let x = 1let y = 2let r = add(b: y, a: x)println("The sum of x and y is ${r}")}执行结果为:
The sum of x and y is 3对于拥有默认值的命名参数,调用时如果没有传实参,那么此参数将使用默认值作为实参的值
例如,下例中调用
add函数时没有为参数b传实参,那么参数b的值等于其定义时的默认值2:func add(a: Int64, b!: Int64 = 2) {return a + b}main() {let x = 1let r = add(x)println("The sum of x and y is ${r}")}执行结果为:
The sum of x and y is 3对于拥有默认值的命名参数,调用时也可以为其传递新的实参,此时命名参数的值等于新的实参的值,即定义时的默认值将失效
例如,下例中调用
add函数时为参数b传了新的实参值20,那么参数b的值就等于20:func add(a: Int64, b!: Int64 = 2) {return a + b}main() {let x = 1let r = add(x, b: 20)println("The sum of x and y is ${r}")}执行结果为:
The sum of x and y is 21
仓颉函数, 如果存在多个命名参数, 那么在函数调用进行传参时, 因为需要命名传参, 所以可以不按照顺序进行传参
func add(a!: Int64, b!: Int64) { return a + b}
main() { let sum = add(b: 2, a: 1) println("1 + 2 = ${sum}")}且, 仓颉函数的命名形参是可以存在默认值的, 类似C++函数的参数缺省值, 且只有命名形参可以存在默认值
拥有默认值的命名形参, 在函数调用时可以不用传参, 当然也可以传参
不过, 仓颉函数的命名形参默认值可以不按照顺序声明, 这与C++函数参数的缺省值不同, C++存在缺省值的参数 之后的参数也必须拥有缺省值
仓颉函数, 非命名参数和命名参数共存时, 非命名参数的传参依旧需要保持声明顺序, 命名参数需要在非命名参数传参之后, 再进行传参(此时 命名参数的传参可以不按照顺序)
func add4(a: Int64, b: Int64, c!: Int64, d!: Int64, e!: Int64 = 5 ) { return a + b + c + d + e}
main() { let sum = add4(1, 2, d: 4, c: 3) println("1 + 2 + 3 + 4 + 5(default) = ${sum}")}