跳到主要内容
版本:release

函数调用表达式

语法

  • 可调用表达式1 ( 实参列表2? )

注释:

  1. 可调用表达式支持如下形式:
形式语法
1标识符
2类型 . 标识符
3表达式 ?? . | => ?? 标识符
4表达式 . call
5( * 表达式 )
  1. 实参列表的语法为:

表达式 (, 表达式)*

描述

在 GS 中,通过函数调用表达式使用指定的参数调用一个函数,可以获取该函数的返回值。

函数调用表达式的可调用表达式部分用于指定被调用的函数,可以是一个函数名(形式1), 也可以是一个类型拓展函数或对象的成员函数(形式2)。如果需要调用一个 function 实例,可以使用 形式3 或形式4,或者使用 function 的其他方法(例如 call_domaincall_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 实例的 方法 或 表达式的 非静态 类型拓展函数。具体调用的目标取决于表达式的类型;

    1. 对象(如果表达式是一个 object,或者是一个指向某个脚本的 string 字面量)的 成员函数
    2. class_map 实例的 方法
    3. 表达式的 非静态 类型拓展函数

    当尝试调用一个 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 成员函数,则不需要跨域;
  • 如果调用的成员函数所属的对象与当前协程所在的域相同,则不需要跨域;

如果确实需要跨域,按照跨域规则使得当前协程进入目标域。

无论是否需要跨域,只要以 => 方式调用,非 parallelreadonly引用容器类型 实参都会被如同跨域调用时一样复制。

调用其他脚本中的函数

可以通过脚本路径的字符串常量调用对应脚本中的函数:

// 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