参数(Parameter)是函数与外部交互的接口,决定了函数如何接受输入数据。
1. 形参(Formal Parameter)
函数声明、定义语句中列出的变量,用于描述需要哪些数据。
static testfunc(param) { } // param是形参
2. 实参(Actual Argument)
调用函数时传入的表达式(字面量、常量、变量或函数返回值等)。
public plugin_init()
{
const constant = 1; // 常量
new variable; // 变量
// 调用server_print函数,传入字符串字面量、常量、变量、函数返回值作为实参
server_print("[AMXX]字面量! c:%d v:%d f:%d", constant, variable, random(5));
}
1. 位置参数(Positional Arguments)
调用函数时,可以按声明顺序输入实参,称为位置参数(位置实参):
// 按顺序,param1是1号参数,param2是2号参数
static testfunc(param1, param2)
return param1 + param2;
public plugin_init()
{
// 调用testfunc函数,按顺序填写param1和param2的值,此时10和20是位置参数
testfunc(10, 20);
}
2. 命名参数(Named Arguments)
调用函数时,可指定实参对应的形参名称,称为命名参数(命名实参):
// 按顺序,param1是1号参数,param2是2号参数
static testfunc(param1, param2)
return param1 + param2;
public plugin_init()
{
// 调用testfunc函数,通过参数名显式赋值,顺序可打乱,此时20和10是命名参数
testfunc(.param2 = 20, .param1 = 10);
}
注意:所有命名实参都必须后置,位置参数必须前置。
// 报错:位置实参只能出现在命名实参前
testfunc(.param2 = 20, 10);
3. 传值参数(Pass by Value)
// 参数param是按值传递
static testfunc(param)
{
// 更改param的值,不会影响实参
param = 20;
}
public plugin_init()
{
// 不用担心字面量10被更改
testfunc(10);
// 不用担心变量arg被更改
new arg = 10;
testfunc(arg);
}
4. 传引用参数(Pass by Reference)
// 参数param1、param2、...是按引用传递
static testfunc(¶m1, param2[1], ...)
{
// 更改param1和对应实参的值
param1 = 20;
// 更改param1和对应实参的值
param2[0] = 20;
// 更改可变参数(第3个实参)的值
setarg(.arg = 2, .index = 0, .value = 20);
// 更改可变参数(第4个实参)的值
setarg(.arg = 3, .index = 0, .value = 20);
}
public plugin_init()
{
new arg1 = 10;
new arg2[1] = 10;
new arg3 = 10;
testfunc(arg1, arg2, arg3, 10);
// arg1、arg2、arg3会被改为20
// 第4个实参(常量表达式10)不会被更改,因为暗中有一个临时变量代替它被传入,被更改了
}
5. 数组参数
// 参数param1和param2是数组参数
static testfunc(param1[], param2[3][2])
{
}
6. 可变参数(Variadic Parameters)
// ...表示数量不限,结构不限;any:表示标签不限
static testfunc(any:...)
{
// 获取实参总数
new totalArgCount = numargs();
// 获取索引号为0的实参[0]偏移量
new arg0@0_addr = getarg(.arg = 0, .index = 0) / 4 + 0;
// 获取索引号为0的实参[0][0](假设索引号为0的实参是二维数组)
new arg0@0@0 = getarg(.arg = 0, .index = arg0@0_addr + 0);
// 将索引号为0的实参[0]设为 { arg0@0@0 * 10, 20, 30 }
setarg(.arg = 0, .index = arg0@0_addr + 0, .value = arg0@0@0 * 10);
setarg(.arg = 0, .index = arg0@0_addr + 1, .value = 20);
setarg(.arg = 0, .index = arg0@0_addr + 2, .value = 30);
// 获取索引号为0的实参[1]偏移量
new arg0@1_addr = getarg(.arg = 0, .index = 1) / 4 + 1;
// 获取索引号为0的实参[1][0](假设索引号为0的实参是二维数组)
new arg0@1@0 = getarg(.arg = 0, .index = arg0@1_addr + 0);
// 将索引号为0的实参[1]设为 { arg0@1@0 * 10, 50, 60 }
setarg(.arg = 0, .index = arg0@1_addr + 0, .value = arg0@1@0 * 10);
setarg(.arg = 0, .index = arg0@1_addr + 1, .value = 50);
setarg(.arg = 0, .index = arg0@1_addr + 2, .value = 60);
// 获取索引号为0的实参[2]偏移量
new arg0@2_addr = getarg(.arg = 0, .index = 2) / 4 + 2;
// 获取索引号为0的实参[2][0](假设索引号为0的实参是二维数组)
new arg0@2@0 = getarg(.arg = 0, .index = arg0@2_addr + 0);
// 将索引号为0的实参[2]设为 { arg0@2@0 * 10, 80, 90 }
setarg(.arg = 0, .index = arg0@2_addr + 0, .value = arg0@2@0 * 10);
setarg(.arg = 0, .index = arg0@2_addr + 1, .value = 80);
setarg(.arg = 0, .index = arg0@2_addr + 2, .value = 90);
}
public plugin_init()
{
new array[3][3] =
{
{ 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 }
};
test(array); // array内部所有数值都被改为10倍
for (new i; i < sizeof array; i++)
for (new j; j < sizeof array[]; j++)
server_print("array[%d][%d] = %d", i, j, array[i][j]);
}
7. 可选参数
static testfunc(param1, param2 = 10, ...)
{
// param2的默认值是10,可变参数没有默认值,一旦省略,就不应该访问可变参数
}
public plugin_init()
{
// param1是必需填的参数,param2和...是可选参数,可省略
testfunc(.param1 = 10);
}
参数声明必须写在函数声明语句中的参数列表内。
注意:下文中,()表示必填项目,【】表示可选项目。
(参数列表)语法拆解:
【参数声明】语法1拆解:
【参数声明】语法2拆解:
【const】注解:
让参数变为只读,常用于修饰数组参数和可变参数,明确表示函数内不会更改实参,提高代码可读性。
用户调用函数前,可通过此修饰符判断函数是否有更改实参的意图。
对传值参数使用此修饰是没有意义的。
【&】注解:
让参数按引用传递,只允许用于修饰单值参数,明确表示函数将要更改实参,提高代码可读性。
约束实参必须是变量。
用户调用函数前,可通过此修饰符判断函数是否有更改实参的意图。
数组参数和可变参数默认按引用传递,禁止使用此修饰符。
【值类型标签】注解:
设置形参的标签,若形参与拥有不同标签的表达式做运算,将触发警告(除非设为any:标签)。
约束形参默认值的标签,若默认值标签与此不符,将触发警告(除非设为any:标签)。
约束实参的标签,若实参标签与此不符,将触发警告(除非设为any:标签)。
若填写的标签不存在,编译器会自动创建。若不设定标签,则默认使用_:标签。
编译器默认提供any:、bool:、_:三种标签,其余皆为inc文件提供或用户自定义。
标签在一定程度上暗示了用户应该填写什么类型的实参,提高了代码的可读性。
默认inc文件很多参数要求填写枚举成员,但并未设置标签,导致用户得不到警告提示、感到迷茫,应引以为戒。
点击查看更多内容:值类型标签指南
【维度声明】注解:
设置形参的维度、各个维度的尺寸,或者与枚举类名组合,设置形参为结构体。详情查阅:变量维度声明语法
约束实参必须是数组(数组字面量也是可以,因为其属于数据段匿名变量)。
若未填写维度声明,则形参是单值参数,而非数组参数。
若未填写某个维度的尺寸,表示不约束实参对应维度的尺寸。
由于数组参数是强制按引用传递,若函数无更改实参意图,应该为形参添加const修饰。
【初始化表达式】注解:
设置形参的默认值,让形参变为可选参数。详情查阅:变量初始化语法
forward函数和公共函数不允许参数拥有默认值。
【,】注解:
一旦填写此逗号,表示一个参数声明结束,用户必须填写下一个参数声明。
native函数限制最多16个实参,其它函数最多127个实参。
【...】注解:
作为参数名称,表示可变参数:不约束接下来的实参数量、维度、尺寸。
native函数限制最多16个实参,其它函数最多127个实参。
可变参数通常需使用any:作为值类型标签,表示不限制实参的标签类型。
可变参数是强制按引用传递的,通常应添加const修饰,除非确实需要在函数内部更改实参。
默认inc文件中完全忘记给只读可变参数添加const修饰,让新手误以为实参会被更改,增加学习成本,应引以为戒。
调用函数,输入多个实参时,若有需要运行时取值的表达式,实参计算顺序受到编译器优化影响,并不固定。
示例:
new b = 1, c = 2;
server_print("[AMXX] %d %d %d %d", ++b, random_num(b, b), ++c, random_num(c, c));
根据直觉,计算顺序应该从左至右计算,打印结果应为:
[AMXX] 2 2 3 3
实际打印:
[AMXX] 2 1 3 2
因此,建议提前计算各个参数的值,然后再调用函数,以保障可移植性和可维护性:
new b = 1, c = 2;
b++;
c++;
server_print("[AMXX] %d %d %d %d", b, random_num(b, b), c, random_num(c, c));