跳到主要内容
版本:master

函数指针(function)

1. 概述

函数指针是 GS 语言中一项强大而灵活的特性,它允许程序将函数作为数据进行处理。本章将系统性地讲解函数指针的基本概念声明方法使用技巧以及在实际项目中的应用场景。内容涵盖函数指针的基础语法、参数域机制、调用方式、闭包特性,以及在实际开发中的最佳实践。

本章节面向已经学习过 基础章节的同学GS 编程初学者或需要快速回顾函数指针相关内容的学习者。

完成本章节后,您将能够理解函数指针的核心概念和底层原理,声明和调用函数指针,避免常见错误,掌握参数域的概念和使用方法,理解闭包的捕获机制和使用场景。在实际项目中灵活运用函数指针解决复杂问题。

2. 函数指针基础

2.1 概念

函数指针是 GS 语言中一种特殊的变量类型,它允许你将函数本身像普通数据一样进行变量存储、传递和动态调用。与直接调用函数不同,函数指针提供了间接调用的能力,让程序能够在运行时决定执行哪个函数,从而实现回调机制、策略模式、事件处理等高级编程范式。

2.2 声明

可以通过(: function_name, function_args... :)的形式定义一个函数指针,其中function_args绑定参数可选。并以(*function_var)(...)function_var.call(...)的形式进行调用。基本语法与使用的示例如下:

// 计算两个数值的最大值
public int maxer(int x, int y)
{
printf("Max number of %d and %d is %d\n", x, y, x > y ? x : y);
return x > y ? x : y;
}

void test()
{
int x = 102;
int y = 32;
function fun = (: maxer :); // 声明函数指针指向 maxer 函数
function func1 = (: maxer , 99:); // 声明函数指针指向 maxer 函数,且函数的第一个参数固定为 99
// function func2 = (: maxer , 99, 100, 101:); // 报错: Argument num of 'maxer' expected 2, got 3.
(*fun)(x, y); // 通过 (*function_var)(...) 形式调用函数
func1.call(88); // 通过 function_var.call(...) 形式调用

// fun.call(x, y, 1000); // 报错: Too many arguments for function "maxer", expected 2, got 3.
// func1.call(x, y); // 报错: Too many arguments for function "maxer", expected 2, got 3
}

test();

示例2-1:函数指针的声明

示例的输出结果如下:

Max number of 102 and 32 is 102
Max number of 99 and 88 is 99

从上述示例可以看到函数指针在声明时可以绑定固定参数,在示例中函数指针func1绑定了第一个参数x99,这样当func1函数在进行调用时则只能指定第二个参数y。在函数指针声明时绑定的参数数量 + 函数指针调用时传入的参数数量应小于函数定义的参数数量否则会给出对应错误。除此之外,可以自行尝试为函数指针绑定错误类型的参数变量,查看报错信息。

需要注意的是funcfunc1实际存储的实际是不同的函数指针实例,尽管他们的实际调用的函数相同,但在函数外的绑定参数列表是不同的。

除了以(: function_name, function_args... :)形式定义外,函数指针类型的变量也可以用于接收函数章节中我们已经介绍过过的匿名函数,示例如下:

public void test()
{
function max_func = [](int x, int y){
printf("Max number of %d and %d is %d\n", x, y, x > y ? x : y);
return x > y? x: y
}; // 使用函数指针变量接收匿名函数
max_func.call(1,2); // 通过 function_var.call(...) 形式调用
(*max_func)(88, 99); // 通过 (*function_var)(...) 形式调用函数
}
test();

示例2-2:函数指针变量接收匿名函数示例

示例的输出结果如下:

Max number of 1 and 2 is 2
Max number of 88 and 99 is 99

可以看到使用匿名函数同样可以完成对函数指针变量的赋值,且同样实现了取最大值的功能。

2.3 参数绑定

现在我们知道函数指针变量在定义时可以显式的绑定参数,如(: function_name, function_args... :)中的function_args参数绑定。参数绑定与参数传递的区别有哪些?如 arrarymap等引用类型绑定又是如何处理的呢?示例如下:

int int_add_one(int x)
{
x++;
printf("x add one in `int_add_one`, result is %O\n", x);
return x;
}

array arr_push_one(array arr)
{
arr.push_back(1);
printf("arr push_back one in `arr_push_one`, result is %O\n", arr);
return arr;
}

function test()
{
int x = 0;
array arr = [];
function func = (:int_add_one, x:); // 绑定非引用类型参数
function func1 = (:arr_push_one, arr:); // 绑定引用类型参数
arr = nil;

func.call();
func1.call();

writeln("after function call x is: ", x);
writeln("after function call arr is: ", arr);
return func1;
}

function func_ref = test();
func_ref.call();

示例2-3:参数绑定示例

示例的输出结果如下:

x add one in `int_add_one`, result is 1
arr push_back one in `arr_push_one`, result is [ /* sizeof() == 1 */
1,
]
after function call x is: 0
after function call arr is: [ /* sizeof() == 1 */
1,
]
arr push_back one in `arr_push_one`, result is [ /* sizeof() == 2 */
1,
1,
]

从示例可以看到函数指针的参数绑定表现与普通函数的参数传递类似。非引用类型的绑定类似值传递。如示例中变量 x ,调用函数 int_add_one 后上级的 x变量未改变,仍是0。引用类型的参数绑定类似引用参数传递。如实例中变量arr,调用函数arr_push_one后上级变量arr指向的数组被修改。

实际上函数指针类型内部有一个用于存储变量的arguments列表。绑定参数的实际操作是将绑定的变量经过参数数量检查及类型检查后推入该arguments列表。因此在示例的test函数返回后,尽管引用类型局部变量arrtest函数结束后不再存在,但返回的 function 类型的变量 func_refarguments列表中仍保留有arr变量对应的数组空间的引用。因此对应的数组空间实际不会被 GC 回收销毁。

2.4 参数域

函数指针的参数域可以理解为函数指针绑定的参数所在的域。在之前的学习中我们已经知晓函数指针绑定参数实际是将变量推入arguments列表中。如果该参数是引用类型,且在域A中创建,我们却想要在域B中调用该函数指针,明显是会有域冲突的,参数域的概念便是用于明晰并解决此类问题。函数指针的参数域的具体规则如下:

  • 函数指针不带有绑定参数时,参数域取决于函数原型是不是parallel

    • 原型为parallel的,参数域为nil

    • 原型为not parallel的,参数域为函数指针的owner(ob instance)所在的域

      • 可以使用function.info().object查看函数指针的owner
  • 函数指针带有绑定参数变量时,参数域取决于参数列表中的参数类型

    • 绑定参数中拥有not parallel的buffer/array/map/function参数时,参数域就是该参数所在的域
    • 绑定参数中不拥有not parallel的buffer/array/map/function参数时,参数域和该函数指针不带有参数变量时是一样的

让我们先来尝试一个无绑定参数的参数域示例:

xxx.gs
// xxx.gs
public parallel void parallel_func(array arr)
{
return;
}

public void not_parallel_func(array arr)
{
return;
}

public array get_bind_funcs()
{
// 均无绑定参数
return[(:parallel_func:), // 无绑定参数 && parallel -> 参数域为 nil
(:not_parallel_func:) // 无绑定参数 && not parallel -> 参数域为 xxx
];
}
test.gs
// test.gs
handle dom = domain.create("xxx"); // 新建一个域
object obj = load_static("/xxx.gs", dom); // 将obj绑定到新建的域xxx
array arr = obj=>get_bind_funcs();
printlnf("prototype parallel && no bind arg -> arg domain is '%O'", arr[0].get_arg_domain());
printlnf("prototype not parallel && no bind arg -> arg domain is '%O'", arr[1].get_arg_domain());

示例2-4:无绑定参数函数指针参数域示例

示例的输出结果如下:

prototype parallel && no bind arg -> arg domain is 'nil'
prototype not parallel && no bind arg -> arg domain is 'domain[2895636:v0]xxx'

从示例的输出结果可以看到,在不带有绑定的参数时,函数原型为parallelparallel_func的函数指针参数域为nil,函数原型为not_parallelnot_parallel_func的函数指针参数域为函数所属的对象实例的参数域xxx

在函数有绑定参数时,函数的参数域示例如下:

xxx.gs
// xxx.gs
public parallel void parallel_func(array arr)
{
return;
}

public void not_parallel_func(array arr)
{
return;
}

public array get_bind_funcs()
{
// 均无绑定参数
return[(:parallel_func, []:), // 函数原型 parallel && 绑定 not parallel 参数-> 参数域为 xxx
(:not_parallel_func, []:), // 函数原型 not parallel && 绑定 not parallel 参数 -> 参数域为 xxx
(:parallel_func, make_parallel([]):), // 函数原型 parallel && 绑定 parallel 参数-> 参数域为 nil
(:not_parallel_func, make_parallel([]):), // 函数原型 not parallel && 绑定 parallel 参数 -> 参数域为 xxx
];
}

test.gs
// test.gs
handle dom = domain.create("xxx"); // 新建一个域
object obj = load_static("/xxx.gs", dom); // 将obj绑定到新建的域xxx
array arr = obj=>get_bind_funcs();
printlnf("prototype parallel && bind not parallel reference arg -> arg domain is '%O'", arr[0].get_arg_domain());
printlnf("prototype not parallel && bind not parallel reference arg -> arg domain is '%O'", arr[1].get_arg_domain());
printlnf("prototype parallel && bind parallel reference arg -> arg domain is '%O'", arr[2].get_arg_domain());
printlnf("prototype not parallel && bind parallel reference arg -> arg domain is '%O'", arr[3].get_arg_domain());

示例2-5:有绑定参数函数指针参数域示例

示例的输出结果如下:

prototype parallel && bind not parallel reference arg -> arg domain is 'domain[2895636:v0]xxx'
prototype not parallel && bind not parallel reference arg -> arg domain is 'domain[2895636:v0]xxx'
prototype parallel && bind parallel reference arg -> arg domain is 'nil'
prototype not parallel && bind parallel reference arg -> arg domain is 'domain[2895636:v0]xxx'

可以看到在绑定了not parallel的引用类型的参数变量或者函数原型为not parallel的情况下,函数指针对应的参数域为绑定参数或函数所属对象实例所在域xxx。而只有没有绑定not_parallel的引用类型变量且函数原型也为parallel的函数指针参数域才为nil

参数域用来明确并解决函数指针的绑定参数与函数指针的执行域冲突的问题。假如我们有两个域A,B。我们在域A中创建一个函数原型为not parallel函数指针func,并为他绑定一个域Anot parallel的引用类型变量。然后将该函数指针func传入域B中的函数尝试调用,会发生什么?实际在非跨域调用的情况下,域B中既不能调用函数原型为not parallel的的函数指针,也不能访问域A下的not parallel的引用类型变量。

现在让我们简单试一下(注意为多脚本示例):

xxx.gs
// xxx.gs
// 这是一个带有引用类型参数的函数
public void a_ref_type_arg_func(array arr)
{
writeln("Function pointer is called successfully.\n");
return;
}

public function get_bind_func()
{
// 创建并返回一个带有绑定的引用类型参数的函数指针
array arr = [];
return (:a_ref_type_arg_func, arr:);
}
test.gs
// test.gs
handle dom = domain.create("xxx"); // 新建一个域
object obj = load_static("/xxx.gs", dom); // 将obj绑定到新建的域xxx
function func = obj=>get_bind_func(); // 获取一个带有绑定引用类型参数的函数指针,且该引用类型在域 `xxx` 中
(*func)(); // 尝试在当前域 `zero` 调用该函数指针

示例2-6:参数域概念错误示例

Error(-1): Can not call function local since not in expected domain, current domain is 'domain[2048:v0]zero' expected 'domain[2985236:v0]xxx

编译器意识到了非法操作。因为函数原型a_ref_type_arg_funcnot parallel的,在当前域zero下不可以直接非跨域调用。现在我们为函数a_ref_type_arg_func添加parallel前缀,这样理论上能够解决函数的跨域问题,现在我们将xxx.gs代码修改如下:

// xxx.gs
// 这是一个带有引用类型参数的函数
public parallel void a_ref_type_arg_func(array arr) // 修改为带有 parallel 前缀
{
writeln("Function pointer is called successfully.\n");
return;
}

public function get_bind_func()
{
// 创建并返回一个带有绑定的引用类型参数的函数指针
array arr = [];
return (:a_ref_type_arg_func, arr:);
}

示例2-7:参数域概念错误示例

运行结果如下:

Error(-1): Can not call function local since not in expected domain, current domain is 'domain[2048:v0]zero' expected 'domain[2984724:v0]xxx'

是的,还是报错。因为**函数指针绑定的引用类型参数arr在域xxx而不是当前域zero**中,此时函数指针的参数域仍是xxx。我们可以通过make_parallel将绑定的引用类型参数修改为 parallel变量,此时函数指针的参数域就会是nil这样即使不跨域我们也可以访问此引用类型数据了。修改xxx.gs如下:

// xxx.gs
// 这是一个带有引用类型参数的函数
public parallel void a_ref_type_arg_func(array arr)
{
writeln("Function pointer is called successfully.\n");
return;
}

public function get_bind_func()
{
// 创建并返回一个带有绑定的引用类型参数的函数指针
array arr = [];
return (:a_ref_type_arg_func, make_parallel(arr):); //make_parallel 将绑定参数置为 parallel 的
}

示例2-8:修改函数指针参数域示例

示例的输出结果如下:

Function pointer is called successfully.

函数指针终于可以以非跨域的方式调用了,此时函数原型为parallel函数指针绑定的引用类型参数也为parallel。得到的函数的参数域实际为 nil 。我们就可以在当前域调用该函数指针了。在最初的示例2-6的基础上,我们也可以使用跨域调用,我以从zero域跨入到xxx域中以正确的调用函数指针并访问绑定的引用类型参数。只需要修改test.gs如下:

// test.gs
handle dom = domain.create("xxx"); // 新建一个域
object obj = load_static("/xxx.gs", dom); // 将obj绑定到新建的域xxx
function func = obj=>get_bind_func(); // 获取一个带有绑定引用类型参数的函数指针,且该引用类型在域 `xxx` 中
func.call_domain(); // 尝试在当前域 `zero` 跨域调用该函数指针

示例2-7:函数指针跨域调用示例

和普通的跨域函数调用相同,若要访问参数域xxx中的not_parallel的引用类型变量,只需要以func_var.call_domain(...)的方式跨入对应的参数域中即可正常的执行该函数指针。

2.5 调用方式

函数指针变量有多种调用方式,在此简单说明下函数指针各个调用方式间的区别。函数指针的调用方式主要分为不跨域、总是跨域、选择性跨域、指定临时owner后调用四种情况。

2.5.1 当前域下调用

​ 在当前域调用函数指针的方式主要有以下两种:

  • (*func_var)(...)

  • func_var.call_local(...)

使用此种类型的调用方式时需满足函数的参数域为nil或者参数域与当前域相同,其他情况均会报错,示例如下:

#pragma parallel
public void test()
{
function fn = (int i) {
printf("hello: %d\n", i);
};
// 参数域为nil,可以被调用
fn.call_local(1);

// 函数绑定参数域为当前域
fn.bind_arg_domain(this_domain());

// 调用域和函数的参数域一致,可以被调用
fn.call_local(2);

function fn2 = (int i) {
printf("hello: %d\n", i);
};

// 函数绑定参数域为非当前域
fn2.bind_arg_domain(domain.create());

// 调用域和函数的参数域不一致,调用报错
fn2.call_local(2);
}
test();

示例2-9:非跨域调用示例

示例输出如下:

hello: 1
hello: 2
Error(-1): Can not call function local since not in expected domain, current domain is 'domain[2048:v0]zero' expected 'domain[2891540:v0]~d2891540:v0@(test@/test.gs)'

可以看到当我们手动调用bind_arg_domain修改函数指针的参数域使其不同于当前域时,就不能以fun_var.call_local(...)(*fun_var)(...)的形式进行非跨域调用了。

2.5.2 选择型跨域调用

func_var.call(...):在函数指针的参数域中调用该函数指针

  • 如果函数指针的参数域为nil,则在当前调用域中调用该函数指针
  • 如果函数指针的参数域不为nil,则自动切域至函数指针的参数域后再调用该函数指针
    • 如果函数指针的参数域和当前调用域相同时,不需要切域
    • 如果函数指针的参数域和当前调用域不同时,则会自动切域

示例如下:

f1.gs
void create()
{
}

public void say(array arr)
{
arr.push_back(1);
printf("this domain@say: %M\n", this_domain());
}

public parallel void yell(array arr)
{
arr.push_back(1);
printf("this domain@yell: %M\n", this_domain());
}
test.gs
#pragma parallel
import .f1;

public void test()
{
// 当前调用域
printf("this domain: %M\n", this_domain());

object ob = new_object(f1, domain.create());
// 初始化数组参数
array arr = [];

printf("---1\n");

// fn的参数域不为空
function fn = (: ob.say :);

// 切域至fn的参数域后调用函数指针
fn.call(arr);
printf("array variable is not changed, result: %O\n", arr);
printf("---2\n");

// fn2的参数域为空
function fn2 = (: ob.yell :);

// 直接在当前调用域里调用函数指针
arr = [];
fn2.call(arr);
printf("array variable is changed to %O\n", arr);
}
test();

示例2-10:选择性跨域调用示例

示例的输出结果如下:

this domain: domain[2048:v0]zero
---1
this domain@say: domain[2891540:v0]~d2891540:v0@(test@/test.gs)
---2
this domain@yell: domain[2048:v0]zero
array variable is changed to [ /* sizeof() == 1 */
1,
]

可以看到,当目标函数指针参数域为 nil时进行非跨域调用,数组arr作为参数传递时并未被深复制。当函数指针参数域与当前域不同时,进行跨域调用,数组arr作为参数传递时被深复制。

2.5.3 跨域调用函数指针

func_var.call_domain(...):无论参数域与当前域是否相同都会进行跨域调用,示例如下:

f1.gs

void create()
{
}

public void say(array arr)
{
arr.push_back(1);
printf("this domain@say: %M\n", this_domain());
}

public parallel void yell(array arr)
{
arr.push_back(1);
printf("this domain@yell: %M\n", this_domain());
}
test.gs
#pragma parallel
import .f1;

public void test()
{
// 当前调用域
printf("this domain: %M\n", this_domain());

object ob = new_object(f1, domain.create());
// 初始化数组参数
array arr = [];

printf("---1\n");

// fn的参数域不为空
function fn = (: ob.say :);

// 切域至fn的参数域后调用函数指针
fn.call_domain(arr);
printf("array variable is not changed, result: %O\n", arr);
printf("---2\n");

// fn2的参数域为空
function fn2 = (: ob.yell :);

// 直接在当前调用域里调用函数指针
arr = [];
fn2.call_domain(arr);
printf("array variable is changed to %O\n", arr);
}
test();

示例2-11:函数指针跨域调用示例

输出结果如下:

this domain: domain[2048:v0]zero
---1
this domain@say: domain[2983700:v0]~d2983700:v0@(test@/test.gs)
array variable is not changed, result: [ ]
---2
this domain@yell: domain[2048:v0]zero
array variable is changed to [ ]

从输出结果可以看到,无论函数指针的参数域是否为 nil或参数域是否域当前域相同,func_var.call_domain(...)总是跨域调用的,传递的函数参数arr总是被深复制到参数域。而原本的zero域中数组arr未被修改。

2.5.4 临时指定owner后函数调用

func_var.call_by_instance(...):为函数指针临时指定owner后再进行call调用

  • 函数指针执行call_by_instance调用时,需要保证函数指针的参数域为nil或者和新owner的域相同,否则调用错误
  • function.remove_ob_instance的作用是移除函数指针的owner
  • 参数域不为nil的函数指针,不拥有not parallelbuffer/array/map/function的参数时,可以通过function.remove_ob_instance移除owner从而设置参数域为nil

示例如下:

f1.gs
void create()
{
}

public void say(mixed v)
{
printf("this domain@say: %M\n", this_domain());
printf("this object@say: %M\n", this);
}

public parallel void yell(mixed v)
{
printf("this domain@yell: %M\n", this_domain());
printf("this domain@yell: %M\n", this);
}
test.gs

#pragma parallel

import .f1;

public void test()
{
object ob = new_object(f1, domain.create());
defer ob.close();
object ob2 = new_object(f1, domain.create());
defer ob2.close();

// 有参数域,绑定参数中没有非read only的buffer/array/map/function
function f1 = (: ob.say, 1 :);

// 有参数域,绑定参数带有非read only的buffer/array/map/function
function f2 = (: ob.say :);
f2.append_dup_arg([]);

// 无参数域: 绑定参数中没有非read only的buffer/array/map/function
function f3 = (: ob.yell, 1 :);

// 有参数域,绑定参数带有非read only的buffer/array/map/function
function f4 = (: ob.yell :);
f4.append_dup_arg([]);

debug.enable_exception_log(ErrorCode.UNKNOWN_ERROR, false);
defer debug.enable_exception_log(ErrorCode.UNKNOWN_ERROR, true);

// f1参数域可以通过remove_ob_instance解除
printf("CALL f1:\n");
f1.remove_ob_instance();
f1.call_by_instance(ob2);

// f2参数域无法通过remove_ob_instance解除
printf("CALL f2:\n");
try {
f2.remove_ob_instance();
// 这个会报错
f2.call_by_instance(ob2);
}
catch {
printf("f2 error occur.\n");
};

// f3无参数域,不需要remove_ob_instance
printf("CALL f3:\n");
f3.call_by_instance(ob2);

// f4有参数域,无法通过remove_ob_instance解除参数域,调用报错
printf("CALL f4:\n");
try {
f4.remove_ob_instance();
f4.call_by_instance(ob2);
}
catch {
printf("f4 error occur\n");
};
}
test();

示例2-12:指定函数指针owner调用示例

示例的输出结果如下:

CALL f1:
this domain@say: domain[2891550:v0]~d2891550:v0@(test@/test.gs)
this object@say: object[2816343:v0]/f1.gs
CALL f2:
Error(-1) ignored.
f2 error occur.
CALL f3:
this domain@yell: domain[2048:v0]zero
this domain@yell: object[2816343:v0]/f1.gs
CALL f4:
Error(-1) ignored.
f4 error occur

从示例及示例的输出结果可以看到

  • 函数指针f2通过append_dup_arg绑定了not parallel引用类型变量,导致无法被remove_ob_instance移除参数域,最终函数指针调用报错。
  • 函数指针f4同样通过append_dup_arg绑定了not parallel引用类型变量,导致无法被remove_ob_instance移除参数域,最终函数指针调用报错。

2.6 改变参数域

参数域为nil的函数指针可以通过调用bind_arg_domain的方式手动指定参数域或通过append_arg的方式绑定参数来简介达成绑定参数域的效果。

  • function.bind_arg_domain: 直接将函数指针绑定到指定的参数域中

    test.gs
    #pragma parallel

    void create()
    {
    }

    public void test()
    {
    // 此时没有绑定参数域
    function f = () {};
    printf("f arg domain 1: %O\n", f.get_arg_domain());

    // 绑定到指定的参数域上
    domain d = domain.create();
    f.bind_arg_domain(d);
    printf("f arg domain 2: %O\n", f.get_arg_domain());
    }
    import test;
    test.test();

    示例2-13:手动绑定参数域示例

    示例的输出如下:

    输出在此:
    f arg domain 1: nil
    f arg domain 2: domain[11:v1]d1
  • function.append_arg_arr/function.append_arg:函数指针通过绑定not read onlybuffer/array/map/function参数绑定参数域

    test.gs
    #pragma parallel

    void create()
    {
    }

    public void test()
    {
    // 此时没有绑定参数域
    function f = () {};
    printf("f arg domain 1: %O\n", f.get_arg_domain());

    // 为函数指针增加一个buffer/array/map/function类型参数绑定参数域
    array a1 = [];
    f.append_arg(a1);
    printf("f arg domain 2: %O\n", f.get_arg_domain());

    // 此时没有绑定参数域
    function f2 = () {};
    printf("f2 arg domain 1: %O\n", f2.get_arg_domain());

    // 为函数指针增加一组buffer/array/map/function类型参数绑定参数域
    array arr = [ {}, [] ];
    f2.append_arg_arr(arr);
    printf("f2 arg domain 2: %O\n", f2.get_arg_domain());
    }
    import test;
    test.test();

    示例2-14:通过绑定参数间接实现绑定参数域示例

    示例的输出如下:

    f arg domain 1: nil
    f arg domain 2: domain[0:v1]zero
    f2 arg domain 1: nil
    f2 arg domain 2: domain[0:v1]zero
  • 一般来讲已经绑定参数域的函数指针无法改变参数域。但特别的,未绑定not_parallel的引用类型参数的有参数域的函数指针可以尝试function.remove_ob_instance()移除函数指针的owner(ob instance),从而解绑定参数域。

    test.gs
    public void test()
    {
    // 此时已绑定参数域
    function f = () {};
    printf("f arg domain 1: %O\n", f.get_arg_domain());

    // 通过function.remove_ob_instance()移除owner,从而解除绑定参数域
    f.remove_ob_instance();
    printf("f arg domain 2: %O\n", f.get_arg_domain());
    }
    import test;
    test.test();

    示例2-15:移除已绑定参数域示例

    输出在此:
    f arg domain 1: domain[0:v1]zero
    f arg domain 2: nil

2.6 闭包的参数域

对于匿名函数,其参数域由其捕获的变量的域决定

  • 闭包没有引用捕获也没有值捕获引用类型变量时,参数域的情形等同于没有绑定参数的函数
  • 闭包引用捕获任一类型变量时,闭包的参数域就是当前域
  • 闭包值捕获引用类型的变量时,闭包的参数域就是当前调用域

示例如下:

test.gs
public void test()
{
int n = 1;
array a = [];

domain d = domain.create();

printf("this domain: %M\n", this_domain());

// 引用捕获了任一变量,参数域为当前域
function f1 = [&n]() {
};
printf("f1 arg domain: %M\n", f1.get_arg_domain());

// 值捕获了buffer/array/map/function(引用类型)变量,参数域为当前域
function f2 = [] () {
a.push_back(1);
};
printf("f2 arg domain: %M\n", f2.get_arg_domain());

// 没有引用捕获也没有值捕获buffer/array/map/function(引用类型)变量,
// 声明为非paralell的闭包,参数域为到当前域
function f3 = () {
writeln(n);
};
printf("f3 arg domain: %M\n", f3.get_arg_domain());

// 没有引用捕获也没有值捕获buffer/array/map/function(引用类型)变量,
// 声明为parallel的闭包,参数域为nil
function f4 = parallel () {
writeln(n);
};
printf("f4 arg domain: %M\n", f4.get_arg_domain());
}
test();

示例2-16:闭包的参数域

输出在此:
this domain: domain[0:v1]zero
f1 arg domain: domain[0:v1]zero
f2 arg domain: domain[0:v1]zero
f3 arg domain: domain[0:v1]zero
f4 arg domain: nil

从示例及其输出结果可以看到闭包函数指针的参数域规则。函数指针f1引用捕获了变量,参数域为当前域。f2值捕获了引用类型变量,参数域为当前域。f3仅值捕获了非引用类型的变量,等同于无绑定参数的函数指针,由于函数原型为not parallel的,f3参数域为当前域。f4同样仅值捕获了非引用类型的变量,由于闭包函数原型为parallel的,f4的参数域为nil

3. 拓展阅读

更多有关函数指针的相关内容请查阅以下文档:

4. 总结

函数指针是 GS 语言中一项强大而灵活的核心特性,它极大地增强了代码的动态性和表达能力。函数指针的本质是将允许像操作普通数据一样对函数进行存储、传递和动态调用。这使得程序能够在运行时决定执行逻辑,极大的提升了代码的灵活性、复用性域动态性。

在本章节我们介绍了函数指针的基础内容,包括:

声明与调用:使用 (: function_name, function_args... :)语法声明,可以绑定部分或全部参数。通过 (*func_var)(...)func_var.call(...)等形式进行调用。绑定的参数会存储在函数指针内部的 arguments列表中,其行为(值传递/引用传递)与普通函数参数传递一致。

参数域(Arg Domain):这是函数指针安全机制的核心。它取决于函数指针绑定的参数或者所有者(owner) 所在的域,并严格规定了函数指针能在何处被安全调用。规则:参数域主要由绑定的参数类型(特别是非 readonly的引用类型)和函数原型(parallel或非 parallel)共同决定。重要性:确保了跨域访问数据的安全性,防止了非法访问域外数据的行为。

调用方式:根据参数域与当前域的关系,提供了多种调用策略。call_local:要求参数域为 nil或与当前域相同,用于高效的同域调用。call:智能选择是否跨域。若参数域非 nil且与当前域不同,则自动切域调用;否则在当前域调用。是最常用的安全调用方式。call_domain:总是进行跨域调用,无论参数域是否与当前域相同,参数会被深复制。call_by_instance:临时指定新的 owner后进行调用,适用于需要动态改变函数执行上下文的情景。

闭包(Closure):即匿名函数,是函数指针的一种形式。其核心特性是能够捕获定义其的外部作用域中的变量。值捕获:创建变量的副本,闭包内的操作不影响原始变量。(默认行为)引用捕获:直接捕获变量本身,闭包内的操作会直接影响原始变量。(使用 [&][&var]语法)闭包的参数域由其捕获的变量类型决定,遵循与普通函数指针相似的规则。