21 KiB
Functions And Calls
文档类型:语法主线 是否可直接用于生成代码:是 是否含已验证可执行示例:是 是否含已验证反例:是 遇到不确定时跳转到:03_core_model.md(优先)、10_units_and_scope.md、21_external_calls_and_threads.md
手册位置:第 6 篇,共 32 篇。上一篇:05_variables_and_constants.md。下一篇:07_expressions_and_operators.md。
这一篇只负责 function / procedure 的定义、调用、参数传递和值返回,不延伸到业务函数库。
这一篇解决什么问题
回答“如何正确声明 function 和 procedure、如何组织主函数和子函数、怎样使用参数修饰、默认参数与可变参数,以及哪些混写方式会直接编译失败”。
必须记住的规则
- 最稳妥的函数骨架仍然是
function Name(...); begin ... end;。 - 不需要返回值时,可以改用
procedure Name(...); begin ... end;。 - 在文件模型层,
function和procedure归同一类顶层外形;见 03_core_model.md。 - 当前解释器接受省略函数头后的分号,但文档默认仍保留这个分号。
- 一个函数定义体里可以同时出现主函数和子函数。
- 函数支持参数类型注解和返回值类型注解。
- 不带类型注解时,多个参数用逗号分隔。
- 带类型注解时,多个参数用分号分隔。
- 当前解释器接受
const与var形参修饰。 - 当前解释器的运行时默认行为是:未修饰参数可以写回调用方;这是运行时默认,不是语言规范保证。
{$VarByRef-}会把未修饰参数切换成按值传递;var形参仍保持引用语义。- 在
{$VarByRef-}下,调用时可以用in/out前缀逐个参数覆盖默认传递方式。 - 如果不确定当前任务是否需要写回语义,优先显式用
const形参,或先切到{$VarByRef-};不要依赖未修饰参数的运行时默认行为。 return expr;会直接返回当前函数结果。exit;会立即结束当前函数;在当前最小样例里,如果此前没有写入返回结果,调用方观察到的是默认值0。- 调用时支持命名参数,写法是
name: value。 - 命名参数当前也支持
call(...)这类按函数名或函数指针转调的模型。 - 一旦某次调用里开始使用命名参数,后面的参数就不能再退回位置参数。
- 对二进制函数 / 系统函数直接使用命名参数,当前会报
named parameter mode can't support here;这类函数要先用 TSL 再封一层。 - 函数参数支持默认值。
- 普通函数的默认值规则不要直接等同到
unit interface声明;跨unit的默认参数边界见这一篇后面的对照例子,以及 10_units_and_scope.md。 - 当前解释器支持尾部
...形式的可变参数。 - 在可变参数函数体里,
Params、ParamCount、RealParamCount都已验证可用。 - 可变参数组可以通过
...转发给另一个函数调用。 - 可变参数组也可以通过
call(fc, ...)、##fc(...)、invoke(obj, name, 0, ...)转发。 - 当前解释器接受
a := function(...) begin ... end;这种匿名函数写法。 - 匿名函数可以直接作为参数传入另一个函数。
- 当前解释器接受
ThisFunction(FuncName)把已知函数绑定成函数值。 - 当前解释器里,匿名函数和函数指针的稳定调用方式仍是
call(f, ...)或##f(...)。 - 当前解释器没有通过
f(...)这种“函数变量直接调用”写法;无论f是匿名函数、FindFunction(...)还是ThisFunction(...)返回的函数指针,都不要默认写成直调。 - 当前解释器接受
::FuncName(...)指向全局/系统函数,用来绕过当前作用域里的同名局部函数。 external、MakeInstance和线程调用统一移到 21_external_calls_and_threads.md。- 不要把顶层函数定义和松散语句混在同一个文件模型里。
已验证语法
基础函数 / 过程骨架
最短函数骨架:
代码块身份:已验证可执行示例
function Add(a, b);
begin
return a + b;
end;
主函数加子函数:
代码块身份:已验证可执行示例
function MultiFunc();
begin
return Twice(3);
end;
function Twice(x);
begin
return x * 2;
end;
函数头省略分号在当前解释器里也可编译:
代码块身份:已验证可执行示例
function MissingSemi()
begin
return 1;
end;
最短 procedure 骨架:
代码块身份:已验证可执行示例
procedure LogDone();
begin
end;
procedure 写回参数的最小运行样例:
这是验证样例外壳,不作为正式顶层模型归类依据。
代码块身份:已验证可执行示例
program test;
procedure Bump(var x);
begin
x := x + 1;
end;
begin
a := 1;
Bump(a);
WriteLn(a);
end.
已验证运行结果:
Bump(a)后输出2
签名增强
带参数类型和返回值类型:
代码块身份:已验证可执行示例
function Demo(a: integer): integer;
begin
return a;
end;
带类型时的多参数分隔:
代码块身份:已验证可执行示例
function Demo(a: integer; b: integer);
begin
return a + b;
end;
类型名可以写成更偏说明性的名字:
代码块身份:已验证可执行示例
function Demo(a: input_value; b: handler): result_type;
begin
return a;
end;
参数修饰:
代码块身份:已验证可执行示例
program test;
function ReadConst(const x);
begin
return x + 1;
end;
procedure SetVar(var x);
begin
x := x + 5;
end;
begin
a := 10;
WriteLn(ReadConst(a));
SetVar(a);
WriteLn(a);
end.
已验证运行结果:
ReadConst(a)输出11SetVar(a)之后,a输出15- 直接给
const形参赋值会编译失败
参数传递方式
默认参数写回调用方:
代码块身份:已验证可执行示例
program test;
function TouchDefault(a);
begin
a := 9;
end;
begin
x := 1;
TouchDefault(x);
WriteLn(x);
end.
已验证运行结果:
TouchDefault(x)之后,x输出9
{$VarByRef-} 与 var 形参:
代码块身份:已验证可执行示例
program test;
function TouchDefault(a);
begin
a := 9;
end;
{$VarByRef-}
function TouchValue(a);
begin
a := 8;
end;
function TouchForcedVar(var a);
begin
a := 7;
end;
{$VarByRef+}
begin
x := 1;
TouchDefault(x);
WriteLn(x);
y := 1;
TouchValue(y);
WriteLn(y);
z := 1;
TouchForcedVar(z);
WriteLn(z);
end.
已验证运行结果:
- 默认模式下,
TouchDefault(x)后x输出9 {$VarByRef-}下,未修饰参数版本TouchValue(y)之后,y仍输出1{$VarByRef-}下,var形参版本TouchForcedVar(z)之后,z输出7
in / out 调用前缀:
代码块身份:已验证可执行示例
program test;
{$VarByRef-}
procedure Touch3(a, b, c);
begin
a := 1;
b := 2;
c := 3;
end;
begin
a := 0;
b := 0;
c := 0;
Touch3(a, b, c);
WriteLn(a);
WriteLn(b);
WriteLn(c);
Touch3(in a, out b, c);
WriteLn(a);
WriteLn(b);
WriteLn(c);
end.
已验证运行结果:
- 在
{$VarByRef-}下,直接调用Touch3(a, b, c)后依次输出0、0、0 - 同样在
{$VarByRef-}下,Touch3(in a, out b, c)后依次输出0、2、0 - 说明
in/out可以在调用点逐个参数覆盖当前默认传递方式
return 与 exit
代码块身份:已验证可执行示例
program test;
function Demo(x);
begin
if x > 0 then
exit;
return 99;
end;
begin
WriteLn(Demo(1));
WriteLn(Demo(0));
end.
已验证运行结果:
Demo(1)输出0Demo(0)输出99- 说明
exit;会立即结束当前函数体 - 在这个最小例子里,因为
exit;之前没有写入返回结果,调用方观察到的是默认值0
调用增强
命名参数调用:
代码块身份:已验证可执行示例
function NamedArgsDemo();
begin
return Pack(a: 1, b: 2);
end;
function Pack(a, b);
begin
return a * 10 + b;
end;
已验证运行结果:
Pack(a: 1, b: 2)返回12Pack(b: 2, a: 1)返回12Pack(1, b: 2)返回12
跳过中间参数时,未命中的参数当前保持 nil:
代码块身份:已验证可执行示例
program test;
function TestFunc(a, b, c);
begin
WriteLn(IfNil(b));
return 0;
end;
begin
TestFunc(1, c: 3);
end.
已验证运行结果:
- 输出
1 - 说明
TestFunc(1, c: 3)这种调用里,中间参数b当前会保持nil
通过 call(...) 也支持命名参数:
代码块身份:已验证可执行示例
program test;
function TestFunc(a, b, c);
begin
return a * 100 + b * 10 + c;
end;
begin
WriteLn(call("TestFunc", a: 1, c: 2, b: 3));
end.
已验证运行结果:
- 输出
132 - 说明
call(...)当前也支持按参数名传值 - 说明命名参数传入后不再按位置解释,而是按名字绑定到形参
命名参数可以调换顺序:
代码块身份:已验证可执行示例
function NamedArgsDemo();
begin
return Pack(b: 2, a: 1);
end;
function Pack(a, b);
begin
return a * 10 + b;
end;
命名参数也可以和位置参数混用:
代码块身份:已验证可执行示例
function NamedArgsDemo();
begin
return Pack(1, b: 2);
end;
function Pack(a, b);
begin
return a * 10 + b;
end;
默认参数
默认值参数:
代码块身份:已验证可执行示例
function AddOne(a = 1);
begin
return a + 1;
end;
带类型时也支持默认值:
代码块身份:已验证可执行示例
function TypedAdd(a: integer = 1): integer;
begin
return a + 1;
end;
多个参数时,后面的参数可以带默认值:
代码块身份:已验证可执行示例
function Pack(a, b = 2);
begin
return a * 10 + b;
end;
代码块身份:已验证可执行示例
function Pack(a: integer; b: integer = 2): integer;
begin
return a * 10 + b;
end;
默认值也可以写成表达式:
代码块身份:已验证可执行示例
function ExprDefault(a = 1 + 2);
begin
return a;
end;
已验证运行结果:
AddOne()返回2AddOne(5)返回6TypedAdd()返回2TypedAdd(5)返回6Pack(1)返回12Pack(a: 1)返回12ExprDefault()返回3
unit interface 声明下的默认参数要单独看:
以下是不可照写的对比:普通函数默认参数能跑通,不等于跨 unit 声明边界也同样可靠。
代码块身份:反例 / 不可照写
// P.tsf
function P(a = 5, b = 6);
begin
return a + b;
end;
// UnitLiteral.tsf
unit UnitLiteral;
interface
function H(a = 5);
implementation
function H(a);
begin
return a;
end;
end.
// UnitConst.tsf
unit UnitConst;
interface
const CS = 888;
function F(a, b = 100, c = CS);
implementation
function F(a, b, c);
begin
return a + b + c;
end;
end.
// main.tsl
program test;
uses UnitLiteral, UnitConst;
begin
WriteLn(P());
WriteLn(H());
WriteLn(F(1));
WriteLn(F(1, 2));
end.
// command
tsl .\main.tsl -LIBPATH "D:\path\to\dir\"
已验证运行结果:
- 普通函数
P()输出11 unit interface里声明的字面量默认值H()输出5F(1)输出101,F(1, 2)输出3- 同一组文件下,
UnitConst.CS当前仍可读到888,而F(1, 2, 3)输出6 - 因此当前解释器下,不要把“普通函数默认参数可用”直接泛化成“
unit interface里引用unit const的默认参数也同样可靠” - 这组多文件结果依赖真实查找路径;当前验证命令是把对应目录放进
-LIBPATH - 这类跨
unit的声明边界,统一回看 10_units_and_scope.md
可变参数 ...
尾部可变参数:
代码块身份:已验证可执行示例
function SumAll(...);
begin
s := 0;
for i, v in Params do
s := s + v;
return s;
end;
可变参数转发:
代码块身份:已验证可执行示例
function Forward(...);
begin
return SumAll(...);
end;
function SumAll(...);
begin
s := 0;
for i, v in Params do
s := s + v;
return s;
end;
ParamCount 与 RealParamCount:
代码块身份:已验证可执行示例
function CountArgs(a, b, ...);
begin
return ParamCount * 10 + RealParamCount;
end;
已验证运行结果:
SumAll(1, 2, 3, 4)返回10Forward(1, 2, 3, 4)返回10CountArgs(1, 2, 3, 4)返回44CountArgs(1, 2)返回22
通过 call 与 ## 转发可变参数:
代码块身份:已验证可执行示例
program test;
function Sum3(a, b, c);
begin
return a + b + c;
end;
function DoFunc(fc, ...);
begin
return call(fc, ...);
end;
function DoFunc2(fc, ...);
begin
return ##fc(...);
end;
begin
WriteLn(DoFunc("Sum3", 1, 2, 3));
WriteLn(DoFunc2(thisfunction(Sum3), 1, 2, 3));
end.
已验证运行结果:
DoFunc("Sum3", 1, 2, 3)输出6DoFunc2(thisfunction(Sum3), 1, 2, 3)输出6
通过 invoke 转发可变参数:
代码块身份:已验证可执行示例
program test;
type TestC = class
public
function FuncA(a, b, c);
begin
return a + b + c;
end;
function FuncB(a, b, c);
begin
return a * 100 + b * 10 + c;
end;
function Dispatch(kind, ...);
begin
if kind = "Int" then
return FuncA(...)
else
return FuncB(...);
end;
end;
function DoInvoke(fc_name, ...);
begin
obj := new TestC();
return invoke(obj, fc_name, 0, ...);
end;
begin
WriteLn(DoInvoke("Dispatch", "Int", 2, 20, 200));
WriteLn(DoInvoke("Dispatch", "Other", 1, 2, 3));
end.
已验证运行结果:
DoInvoke("Dispatch", "Int", 2, 20, 200)输出222DoInvoke("Dispatch", "Other", 1, 2, 3)输出123
匿名函数与函数指针
匿名函数变量:
代码块身份:已验证可执行示例
program test;
begin
a := function(x, y)
begin
return x + y;
end;
WriteLn(call(a, 1, 2));
WriteLn(##a(5, 6));
end.
已验证运行结果:
call(a, 1, 2)输出3##a(5, 6)输出11
匿名函数也可以直接作为参数传入:
代码块身份:已验证可执行示例
program test;
function Apply(fun);
begin
return call(fun, 2, 3);
end;
begin
WriteLn(Apply(function(x, y)
begin
return x * y;
end));
end.
已验证运行结果:
- 输出
6
函数指针变量也可以通过 FindFunction(...) 拿到:
代码块身份:已验证可执行示例
program test;
function Add(a, b);
begin
return a + b;
end;
begin
f := FindFunction("Add");
WriteLn(##f(1, 2));
end.
已验证运行结果:
- 输出
3
也可以通过 ThisFunction(...) 从已知函数名直接拿到:
代码块身份:已验证可执行示例
program test;
function Add(a, b);
begin
return a + b;
end;
begin
f := ThisFunction(Add);
WriteLn(Call(f, 3, 4));
end.
已验证运行结果:
- 输出
7 - 说明
ThisFunction(Add)当前可以得到稳定可调用的函数值
直接 f(...) 在当前解释器里不要当成可靠写法:
代码块身份:反例 / 不可照写
// 匿名函数变量直接调用
program test;
begin
a := function(x, y)
begin
return x + y;
end;
WriteLn(a(7, 8));
end.
// FindFunction 返回值直接调用
program test;
function Add(a, b);
begin
return a + b;
end;
begin
f := FindFunction("Add");
WriteLn(f(3, 4));
end.
// ThisFunction 返回值直接调用
program test;
function Add(a, b);
begin
return a + b;
end;
begin
f := ThisFunction(Add);
WriteLn(f(3, 4));
end.
已验证结果:
- 上面三种
f(...)直调写法都会报function:<name> compile error or not found - 因此当前手册只把
call(f, ...)和##f(...)写成稳定规则
:: 指向全局函数
当当前作用域里有同名局部函数时,可以用 ::FuncName(...) 指定去调全局/系统函数:
代码块身份:已验证可执行示例
program test;
function Demo();
begin
return array(StrToInt("123"), ::StrToInt("123"));
end;
function StrToInt(s);
begin
return 888;
end;
begin
r := Demo();
WriteLn(r[0]);
WriteLn(r[1]);
end.
已验证运行结果:
- 第一项
StrToInt("123")命中局部函数,输出888 - 第二项
::StrToInt("123")命中全局系统函数,输出123 - 说明
::可以绕过当前作用域的同名局部函数,直接指向全局/系统函数
如果 ::FuncName(...) 指向的全局函数本身不存在,当前解释器会在运行时报 function:<name> compile error or not found
系统交互专题
external、动态库函数指针、MakeInstance 和线程调用,统一见 21_external_calls_and_threads.md。这一篇只保留“普通函数怎样定义和调用”的主线。
最小可编译示例
如果你只是要写一个能被 session 稳定续写的函数 / 过程,从下面任一骨架起步:
代码块身份:已验证可执行示例
function Hello();
begin
return 1;
end;
代码块身份:已验证可执行示例
procedure HelloProc();
begin
end;
常见误写
- 把顶层函数定义和松散语句混写。
- 以为函数头后的分号是当前解释器的硬性要求。
- 以为
procedure只是function的别名,不涉及参数传递语义。 - 带类型注解时仍然用逗号分隔参数。
- 以为默认未修饰参数天然就是按值传递。
- 把
a = 1这种比较表达式误当成命名参数调用。 - 以为默认值只能用于无类型参数。
- 在
const形参上直接赋值。 - 把普通函数的默认值规则原样套到
unit interface里的const默认参数上。 - 把匿名函数或
FindFunction(...)返回值默认写成f(...)直调。 - 把命名参数直接套到二进制函数或系统函数上。
- 在一次调用里先进入命名参数模式,后面又退回位置参数。
代码块身份:反例 / 不可照写
function Add(a, b);
begin
return a + b;
end;
value := Add(1, 2);
上面这种混写方式会编译失败,问题不在 Add 本身,而在于文件模型混了“函数定义体”和“松散语句”两种写法。
代码块身份:反例 / 不可照写
function Demo(a: integer, b: integer);
begin
return a + b;
end;
上面这种写法会编译失败;参数一旦带类型,分隔符应改成分号。
代码块身份:反例 / 不可照写
Pack(a = 1, b = 2)
这类写法不要当成命名参数。它虽然可能编译通过,但在我于 2026-04-09 的实测里返回结果不对,不能当成可靠的命名参数语法。
代码块身份:反例 / 不可照写
function Demo(a: integer, b: integer = 2): integer;
begin
return a + b;
end;
上面这种写法也不对;参数一旦带类型,分隔符仍然应保持分号。
代码块身份:反例 / 不可照写
function TouchDefault(a);
begin
a := 9;
end;
不要把上面这种未修饰参数自动理解成“按值传递”。在当前解释器里,它会把调用方实参改掉;如果当前任务要求按值语义,先看 {$VarByRef-} 和 in / out 的例子。
代码块身份:反例 / 不可照写
procedure Bad(const x);
begin
x := 2;
end;
上面这种写法会编译失败;const 形参在当前解释器里不能被重新赋值。
代码块身份:反例 / 不可照写
unit UnitConst;
interface
const CS = 888;
function F(a, b = 100, c = CS);
不要把上面这种 unit interface 声明直接当成已经等价于普通函数默认参数规则。在当前解释器里,对应的 F(1) 实测输出是 101,不是按 CS = 888 补成的结果;具体边界见 10_units_and_scope.md。
代码块身份:反例 / 不可照写
a := function(x, y)
begin
return x + y;
end;
WriteLn(a(7, 8));
不要把上面这种匿名函数变量的直调写法直接当成可靠规则。在当前解释器里,它会报 function:a compile error or not found;稳定写法仍然是 call(a, 7, 8) 或 ##a(7, 8)。
代码块身份:反例 / 不可照写
WriteLn(IntToStr(value: 200));
不要把上面这种写法直接套到二进制函数或系统函数上。当前解释器里,它会报 named parameter mode can't support here;如果确实要命名传参,先用 TSL 函数再封一层。
代码块身份:反例 / 不可照写
function Pack(a, b, c);
begin
return a * 100 + b * 10 + c;
end;
begin
WriteLn(Pack(a: 1, 2, c: 3));
end.
不要在同一次调用里“先进入命名参数模式,再退回位置参数”。当前这类写法会直接编译失败,错误信息包含 paramname: not found。
跳转指引
- 回看最短骨架:见 02_quickstart.md
- 回看流程控制:见 08_control_flow.md
- 当函数变多时:见 10_units_and_scope.md
- 看
external、MakeInstance和线程调用:见 21_external_calls_and_threads.md