函数(Function)
func 声明函数。返回注解可省,省略即 void;需要返回值时必须写 -> T,return 的类型严格对照声明。
基本形态
func Add(a: int, b: int) -> int
{
return a + b;
}
func Dbl(n: int) -> int => n * 2;
func Fib(n: int) -> int
{
if n < 2
{
return n;
}
return Fib(n - 1) + Fib(n - 2);
}- 表达式体
=> expr;是单表达式函数的紧凑写法,语义与块体一致。 - 递归直接写名字调用,上面的
Fib(10)得 55。 - 语句块的返回类型检查是双向严格的:声明
-> int却漏 return、或 return 类型不符,均无法通过编译。
参数修饰
形参首列修饰符决定传递方式:
| 修饰 | 名字 | 语义 |
|---|---|---|
| (无) | 默认 | 同 val 不可变传递 |
val | 不可变传递 | 形参体内不可赋值 |
var | 可变传递 | 形参是实参的副本,体内可改,不影响调用方 |
in | 只读传递 | 只读视图,适合大对象免拷 |
ref | 可写回 | 形参与实参同一存储,体内的写回对实参生效 |
out | 仅写回 | 进函数时不需要值,专门用于带回结果 |
params | 变参 | 末位形参收任意个实参,体内是数组 |
ref 与 out 的调用位要显式写修饰符:
func Swap(ref a: int, ref b: int)
{
val t = a;
a = b;
b = t;
}
func Divide(a: int, b: int, out remainder: int) -> int
{
remainder = a % b;
return a / b;
}
var x = 1;
var y = 2;
Swap(ref x, ref y);
print(x, y);
var rem = 0;
print(Divide(7, 2, out rem), rem);2 1
3 1Swap(ref x, ref y) 调用后 x、y 已交换;Divide(7, 2, out rem) 返回商 3,rem 带回余数 1。
默认参数
参数可以带默认值,值必须是编译期常量。默认值参数调用时可省:
func Greet(name: string, greeting: string = "你好", punct: string = "!") -> string
{
return $"{greeting},{name}{punct}";
}
print(Greet("世界"));
print(Greet("世界", "早上好"));你好,世界!
早上好,世界!命名实参
实参可以写成 名: 值,按名绑定形参槽:可乱序、可跳过有默认值的参数,位置实参要放在命名实参之前:
print(Greet("世界", punct: "。"));
print(Greet(greeting: "晚安", name: "月亮"));你好,世界。
晚安,月亮!params 与展开调用
params 修饰末位形参,实参个数任意,体内是一个长度固定的数组。要把一个集合整体交给 params 形参,用展开调用:实参列表写成 ... 加集合名,集合逐元素摊开:
func Total(params nums: int[]) -> int
{
var total = 0;
for n in nums
{
total += n;
}
return total;
}
val xs: list<int> = [4, 5];
print(Total(1, 2, 3));
print(Total(...xs));
print(Total(1, ...xs));6
9
10Total(...xs) 把列表逐元素展开,等价 Total(4, 5);字面量同样能展开(Total(...[7, 8]))。
展开实参不必独占实参列表。params 调用的位实参表就是 params 数组的字面量构形,与列表字面量 [1, ...xs] 同构:...xs 在哪个位置,元素就摊进数组的哪个位置——Total(1, ...xs) 直写合法,等价 Total(1, 4, 5);展开可以放在中间、也可以多个(Total(...a, 2, ...b)),集合元素类型要对照 params 的元素类型:
val head: list<int> = [1, 2];
val tail: list<int> = [8, 9];
print(Total(...head, 5, ...tail));25固定形参优先吃位实参:位实参先按声明顺序填固定形参,溢出的和展开的一起进 params 数组。展开后面的位实参照旧按声明序吃固定槽——Join(...xs, "a") 里 "a" 是第一个位实参,填的是 sep:
func Join(sep: string, params items: int[]) -> string
{
var s = "";
var first = true;
for n in items
{
if (!first) { s = s + sep; }
s = s + n.ToString();
first = false;
}
return s;
}
val xs: list<int> = [4, 5];
print(Join(";", 1, ...xs));
print(Join(...xs, "a"));1;4;5
4a5命名实参永不摊开:按名绑定自己的形参槽,位实参与展开的照常展平——Join(sep: "-", ...xs) 的 xs 照样摊进 items。每个实参按源码顺序恰求值一次。
重载
同名函数构成重载集,调用按实参类型解析,取最佳匹配:
func Show(v: int) -> string => $"int {v}";
func Show(v: string) -> string => $"string {v}";
print(Show(7), Show("七"));int 7 string 七消歧按评分层级(精确 > 可赋值 > 提升 > 装箱),省略尾参少者优先,定元形优先于 params 展开。
泛型与 where
类型参数写在名字后,约束用 where 子句。子句顺序固定:uses → fail → where,然后才是 ->:
import Math.*;
func Bigger<T>(a: T, b: T) where T: Ordered -> T
{
if a < b
{
return b;
}
return a;
}
print(Bigger(4, 9));9Ordered 是 std Math 提供的协议(含序比较),内建标量隐式满足,泛型体内的 a < b 按约束协议的运算符签名放行。协议名在类型位裸名书写需要 import Math.*; 激活。
具名函数作值
函数名出现在值位(实参、赋值、返回、装容器)时物化为函数值,类型是函数类型:
func On(n: int) -> void => print("on", n);
val f: func(int) -> void = On;
f(4);
val g = On;
g(5);on 4
on 5同声明的函数物化成同一个值(单例);lambda 则每次求值都是新实例,见 lambda 与闭包。== 按这个实例身份判:同单例恒等,不同实例不等,见判等与哈希。
注意
- 顶层 func 是模块成员,可见性由模块系统管理,见组织代码部分;类型成员方法带
self接收者,见类型部分。 async func见异步部分,本文不过多赘述。
