函数指针(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绑定了第一个参数x为 99,这样当func1函数在进行调用时则只能指定第二个参数y。在函数指针声明时绑定的参数数量 + 函数指针调用时传入的参数数量应小于函数定义的参数数量否则会给出对应错误。除此之外,可以自行尝试为函数指针绑定错误类型的参数变量,查看报错信息。
需要注意的是func与func1实际存储的实际是不同的函数指针实例,尽管他们的实际调用的函数相同,但在函数外的绑定参数列表是不同的。
除了以(: 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参数绑定。参数绑定与参数传递的区别有哪些?如 arrary或map等引用类型绑定又是如何处理的呢?示例如下:
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函数返回后,尽管引用类型局部变量arr在test函数结束后不再存在,但返回的 function 类型的变量 func_ref的arguments列表中仍保留有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
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
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'
从示例的输出结果可以看到,在不带有绑定的参数时,函数原型为parallel的parallel_func的函数指针参数域为nil,函数原型为not_parallel的not_parallel_func的函数指针参数域为函数所属的对象实例的参数域xxx。
在函数有绑定参数时,函数的参数域示例如下:
// 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
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,并为他绑定一个域A的not parallel