值类型标签(Tag)能在编译时为表达式提供类型检查,或匹配运算符重载,进行文本替换。
标签对开发者而言能增强语义区分,帮助开发者更安全地编写代码。
不同标签的表达式做运算,通常会触发警告,除非与某个运算符重载匹配,或其一拥有any标签。
标签拥有uid,可通过tagof关键字获取。
编译前AMX Mod X会自动生成3个默认标签:
| 名称 | 作用 |
|---|---|
| any | 拥有此标签的表达式参与运算时,编译器不会进行类型检查 |
| bool | 拥有此标签的表达式若等于0,表示否定,若等于其他数值,表示肯定 |
| _ | 所有表达式的默认标签,说明表达式的值是整数 |
除了默认标签,其它标签是在编译时生成的,因此每个插件生成的同名标签uid不一定相同,并且编译后标签会被删除。
用户在声明标识符时,若不设置标签,则默认标识符拥有_标签。
用户在声明标识符时,若设置不存在的标签,编译器会自动创建该标签。因此,引用inc文件后会获得很多标签。
在任意表达式左边,可以使用标签覆盖表达式,临时更改表达式的标签。
重点1:标签无实际意义,会在编译时被删除。标签覆盖不会改变表达式的数值。
重点2:复合表达式的操作数无论带有什么标签,默认情况下只会进行整数之间的运算。但两操作数的标签不同可能触发警告。
重点3:amxx插件与AMX Mod X通过native或forward函数交换数据,数据(表达式)的标签不影响函数计算结果。
重点4:operator关键字可定义文本替换规则(运算符重载),将满足规则的复合表达式替换为函数与参数。
重点5:覆盖标签可以主动满足或避开运算符重载定义的文本替换规则,或屏蔽某些标签不匹配的警告。
重点6:一旦满足运算符重载的文本替换规则,替换成函数运算后,计算结果可能与预期不符。
// 若不覆盖标签,因常量和常量值标签不同,将触发警告:“标签不匹配”
const Float:FLOAT_NEGATIVE_INFINITY = 0b_01000000_01001001_00000000_00000000;
// 浮点型常量:负无穷
const Float:FLOAT_NEGATIVE_INFINITY = Float:0b_01000000_01001001_00000000_00000000;
// 此函数返回值是1和0,表示肯定和否定。
native is_user_alive(index);
public PlayerPreThink_Post(playerEntId)
{
// 我们通常使用带有bool标签的标识符表示肯定与否定,若直接储存1或0将在编译时被警告“标签不匹配”
// 因此,临时更改is_user_alive函数的标签,屏蔽警告(看函数的源码或注释,确认其绝对只会返回1或0)
new const bool:isAlive = bool:is_user_alive(playerEntId);
}
// 假如我们制作了一个默认风格的native函数,接受队伍索引作为参数,返回值是敌方队伍的索引
native GetEnemyTeam(CsTeams:team);
public CsTeams:@GetEnemyTeam()
{
// amxmodx.inc提供的get_param和get_param_f函数可根据号数获取非数组类型的传值参数值
// 它们返回值分别带有_和Float标签,而我们需要的是带有CsTeams标签的参数值
// 已知:标签无意义,在编译时被删除,标签不会改变数据,函数运行时不能得知参数原本的标签
// 因此我们直接使用其中任意一个函数,将其标签覆盖为CsTeams
new CsTeams:team = CsTeams:get_param(1);
// 由于我们无法确定参数值原本的数据类型,自然也无法保证其真的是队伍索引
// 因此,我们需要判断其是否在正确范围内(CS_TEAM_UNASSIGNED~CS_TEAM_SPECTATOR)
// 或者直接判断是否等于CS_TEAM_T(匪徒)或CS_TEAM_CT(警察)
// 或者使用switch编写各种判断分支
switch (team)
{
// 若参数值等于匪徒队伍索引,返回警察队伍索引
case CS_TEAM_T: return CS_TEAM_CT;
// 若参数值等于警察队伍索引,返回匪徒队伍索引
case CS_TEAM_CT:return CS_TEAM_T;
}
// 若参数值等于未分配队伍索引、观察者队伍索引、或非法数据,统一返回未分配队伍索引
return CS_TEAM_UNASSIGNED;
}
#include <amxmodx>
#include <cellarray> // 引用Array系列native函数声明。可省略,amxmodx.inc已经引用这个文件了
#include <float> // 引用浮点数相关运算符重载。可省略,amxmodx.inc已经引用这个文件了
public plugin_precache()
{
// 创建动态数组,将一个浮点数数据存入数组内
new Array:handle = ArrayCreate();
ArrayPushCell(handle, 5.0);
// 假如我们想直接判断数组内的数据是否等于5.0(执行两个浮点数之间的相等比较)
// 必须对ArrayGetCell函数使用标签覆盖,确保其满足Float: == Float:这个运算符重载的文本替换规则
// 这种情况下,括号内容会被替换为floatcmp(5.0, ArrayGetCell(handle, 0)) == 0并且计算结果为1,满足if条件
// 若不覆盖标签,将会满足Float: == any:的文本替换规则(实际没有这条规则,所以是Float: == _:)
// 这种情况下,括号内容会被替换为floatcmp(5.0, float(ArrayGetCell(handle, 0))) == 0并且计算结果为0,不满足if条件
// 因为数组内的浮点数数据5.0实际等于整数1084227584,会被float函数转换为1084227584.0
// 而5.0与1084227584.0不相等,不满足if条件
if (5.0 == Float:ArrayGetCell(handle, 0))
{
server_print("数组内的第一个元素等于浮点数5.0或整数1084227584");
}
}
// 将两个浮点数的二进制整数发给核心模块,执行浮点数加法,得到结果后返回二进制整数
native Float:floatadd(Float:oper1, Float:oper2);
// 若源码中存在两个带有Float标签的表达式相加,例如1.0 + 2.0,编译后将其替换成floatadd(1.0, 2.0)
native Float:operator+(Float:oper1, Float:oper2) = floatadd;
// 若源码中带有Float标签的变量使用了++自增,例如++v,将其替换为v = floatadd(v, 1.0)
stock Float:operator++(Float:oper)
return oper+1.0;
AMX Mod X的float.inc文件提供了Float与Float数据、Float与_数据、_与Float数据做运算的文本替换规则。
因此,当我们引用了float.inc或amxmodx.inc(它引用了float.inc)文件,须注意是否对复合表达式的操作数进行标签覆盖。
特别是其中一个操作数拥有Float标签而另一个没有的情况。