AMXXPawn 值类型标签

1. 简介

值类型标签(Tag)能在编译时为表达式提供类型检查,或匹配运算符重载,进行文本替换。

标签对开发者而言能增强语义区分,帮助开发者更安全地编写代码。

不同标签的表达式做运算,通常会触发警告,除非与某个运算符重载匹配,或其一拥有any标签。

标签拥有uid,可通过tagof关键字获取。

编译前AMX Mod X会自动生成3个默认标签:

名称 作用
any 拥有此标签的表达式参与运算时,编译器不会进行类型检查
bool 拥有此标签的表达式若等于0,表示否定,若等于其他数值,表示肯定
_ 所有表达式的默认标签,说明表达式的值是整数

除了默认标签,其它标签是在编译时生成的,因此每个插件生成的同名标签uid不一定相同,并且编译后标签会被删除。

注意:

不同版本AMX Mod X的默认标签也不拥有相同的uid,不要依靠uid辨别其它插件传递的数据类型,不可靠。

Float并不是默认标签,需使用#pragma rational指令创建,或引用float.inc文件,才能使用。

float.inc已被amxmodx.inc文件引用,所以引用amxmodx.inc也是可以的。

使用#pragma rational指令创建浮点数标签之前,编译器不会允许用户使用浮点数字面量。

2. 声明标识符时设定标签

用户在声明标识符时,若不设置标签,则默认标识符拥有_标签。

用户在声明标识符时,若设置不存在的标签,编译器会自动创建该标签。因此,引用inc文件后会获得很多标签。

常量标签:

结构体属性标签:

变量标签:

数组索引器标签:

函数标签:

函数索引器标签:

3. 标签覆盖

在任意表达式左边,可以使用标签覆盖表达式,临时更改表达式的标签。

重点1:标签无实际意义,会在编译时被删除。标签覆盖不会改变表达式的数值

重点2:复合表达式的操作数无论带有什么标签,默认情况下只会进行整数之间的运算。但两操作数的标签不同可能触发警告。

重点3:amxx插件与AMX Mod X通过native或forward函数交换数据,数据(表达式)的标签不影响函数计算结果

重点4:operator关键字可定义文本替换规则(运算符重载),将满足规则的复合表达式替换为函数与参数

重点5:覆盖标签可以主动满足或避开运算符重载定义的文本替换规则,或屏蔽某些标签不匹配的警告。

重点6:一旦满足运算符重载的文本替换规则,替换成函数运算后,计算结果可能与预期不符

整数字面量默认带有_标签,若使用带有Float标签的常量或变量储存,将触发警告。

此时可对字面量使用标签覆盖,屏蔽这个赋值表达式的“标签不匹配”警告:

// 若不覆盖标签,因常量和常量值标签不同,将触发警告:“标签不匹配” 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; }

某些模块提供了储存、获取数据的功能,假如我们储存的是浮点数的数据,但获取数据的函数不带有Float的标签。

若该函数带有any标签,可直接用浮点型变量储存其返回值。

但若想直接将其返回值用于参与浮点数运算,只要其标签不是Float,必须使用标签覆盖,使其正确匹配运算符重载。

#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标签而另一个没有的情况。