函数调用表达式
语法
- 可调用表达式1 ( 实参列表2? )
注释:
- 可调用表达式支持如下形式:
| 形式 | 语法 |
|---|---|
| 1 | 标识符 |
| 2 | 类型 . 标识符 |
| 3 | 表达式 ?? . | => ?? 标识符 |
| 4 | 表达式 . call |
| 5 | ( * 表达式 ) |
- 实参列表的语法为:
描述
在 GS 中,通过函数调用表达式使用指定的参数调用一个函数,可以获取该函数的返回值。
函数调用表达式的可调用表达式部分用于指定被调用的函数,可以是一个函数名(形式1),
也可以是一个类型拓展函数或对象的成员函数(形式2)。如果需要调用一个 function 实例,可以使用
形式3 或形式4,或者使用 function 的其他方法(例如 call_domain 或 call_local)。
具体地说,函数调用表达式的各个形式详细介绍如下:
-
形式1:调用一个
默认函数或 当前对象的(以及其组件的)成员函数当不带有
.或=>时,函数调用表达式调用的是一个默认函数或 当前对象的(以及其组件的)成员函数。默认函数是指:
-
定义在
system.*下的函数,例如system.core.printf,system.type.is_string。此类函数既可以使用其完整形式调用,亦可以省略前缀,直接使用其函数名调用
-
定义在被标注为默认对象中的成员函数
使用
#pragma default_object注解可以将某个程序对应的静态对象实例标注为默认对象,可以直接 使用其成员函数名调用。
-
-
形式2:调用一个静态的
类型拓展函数在
.前指定类型名,可以调用该类型下的静态函数。这些静态函数可以是 GS 内部定义的拓展函数(EFUN), 也可以是脚本中定义的函数;如果要调用其他脚本(例如 gs.lang.array)或模块(json,msgpack,language 等) 定义的类型拓展函数,应当提前 导入 相关脚本或模块。 -
形式3:调用对象的
成员函数,class_map实例的方法或表达式的 非静态类型拓展函数在
.(或=>,仅限对象的成员函数) 前指定一个表达式,可以调用该表达式结果的成员函数,class实例的方法或 表达式的 非静态类型拓展函数。具体调用的目标取决于表达式的类型;- 对象(如果表达式是一个
object,或者是一个指向某个脚本的string字面量)的成员函数 class_map实例的方法- 表达式的 非静态
类型拓展函数
当尝试调用一个
object的成员函数时,如果目标的成员函数不是parallel的,函数调用时必须跨入目标对象 所在的域:使用.指定调用对象成员函数时,相当于 显式不跨域调用,使用=>指定调用对象成员函数时, 相当于 显式跨域调用。除此之外,=>不能用于调用其他类型的函数。可以使用
?指示可选调用,如果?在.前,则表示如果表达式为nil则不进行调用;如果?在.后, 则表示如果成员函数不存在则不进行调用(这种情况下为参数数量检查提供额外的豁免:允许超出形参声明数量的实参传 递,多余参数将被忽略);如果(因为表达式为nil或目标函数不存在)调用失败,函数调用表达式的值为nil。 - 对象(如果表达式是一个
-
形式4/形式5:调用一个
function实例当尝试调用一个
function实例时,有如下四种方式,分别使用不同的跨域规则:形式 说明 f.call(..)形式4,检查 function实例的参数域,如果没有参数域或参数域与当前域相同,直接在当前域调用;否则跨入参数域f.call_local(..)形式3,实际上调用的是 function类型的call_local函数,显式不跨域调用f.call_domain(..)形式3,实际上调用的是 function类型的call_domain函数,显式跨域调用,跨入目标的参数域,如果没有参数域则在当前域执行(*f)(..)形式5,等同于 f.call_local(..)
函数调用表达式的实参列表部分用于指定被调用函数的实参。如果被调用的函数能在编译期确定,则传入实参 时会进行类型检查(不会在编译期检查参数数量);如果传入的实参与形参类型不匹配,则会导致编译错误。
如果函数的形参没有指定默认参数,在函数调用时必须提供实参;除非函数是变长参数,或通过 .? 调用,
否则不允许提供超过形参数量的实参;当通过 .? 调用时,超过形参的实参将被忽略。
GS 尝试在编译期对函数调用表达式进行类型检查;如果无法确定函数的声明,对于实参的参数类型检查将被 推迟到运行时进行。
无论是调用对象的成员函数,或者调用一个 function 实例,只要以 显式跨域调用,为函数提供的
RW的
引用容器类型 实参都会被如同跨域调用时一样复制。
如果 显式不跨域调用 一个必须跨域执行的函数,GS 将抛出运行时错误。
跨域调用
当指示了 => 时,函数调用表达式会尝试跨域调用目标函数。协程的域(当前域)是否实际切换,取决于以下两点:
- 如果调用的成员函数是一个
parallel成员函数,则不需要跨域; - 如果调用的成员函数所属的对象与当前协程所在的域相同,则不需要跨域;
如果确实需要跨域,按照跨域规则使得当前协程进入目标域。
无论是否需要跨域,只要以 => 方式调用,非 parallel 和 readonly 的 引用容器类型
实参都会被如同跨域调用时一样复制。
调用其他脚本中的函数
可以通过脚本路径的字符串常量调用对应脚本中的函数:
// a.gs
public void foo()
{
write("a.foo\n");
}
// b.gs
"/a.gs".foo(); // Ok
使用 import 导入脚本,或 component 组合脚本后, 会引入与目标脚本文件名同名的局部常量,可以简化上述示例:
// a.gs
public void foo()
{
write("a.foo\n");
}
// b.gs
import a;
a.foo(); // Ok, import 引入了名为 a 的常量,a 的值为字符串 "/a.gs"
在以这种方式调用其他脚本中的函数时,相当于调用对应脚本的 静态对象示例 的成员函数。 如果调用时,静态对象实例尚不存在,则在当前域创建该静态对象实例。
如果目标静态对象实例不属于当前域,并且目标函数不是 parallel 函数,则应当使用 => 进行跨域调用:
// a.gs
public void foo()
{
write("a.foo\n");
}
// b.gs
import a;
a=>foo(); // 跨域调用 a.gs 中的 foo 函数
返回值
一个函数可以通过 return 语句 返回一个值。该值被作为函数调用表达式的值,如果 以跨域方式调用函数,引用容器类型 的返回值会被复制回当前域。
示例
// 定义一个函数
int add(int a, int b) {
return a + b;
}
// 调用函数
int result = add(1, 2); // result 的值为 3