03-22-2026, 12:11 AM
(此帖子最后修改于: 03-22-2026, 12:14 AM 由 小鸟unsigned.)
枚举器
原文作者: iPLEOMAX
关于枚举,有些细节很多脚本作者并不清楚。
很多人都喜欢在脚本里用枚举来存玩家数据、车辆数据、房屋数据之类的,尤其是用户信息这块。
一个很典型的写法是这样的:
代码:
enum E_PLAYER_INFO
{
SCORE,
MONEY,
KILLS,
DEATHS
};
new pInfo[MAX_PLAYERS][E_PLAYER_INFO];用起来大概是这样:
代码:
public OnPlayerDeath(playerid, killerid, reason)
{
pInfo[playerid][DEATHS]++;
if(IsPlayerConnected(killerid) && killerid != playerid)
pInfo[killerid][KILLS]++;
return 1;
}这段应该不难理解。
现在我们看另一个例子:
代码:
enum E_PLAYER_INFO
{
SCORE,
MONEY = 9,
KILLS = 5,
DEATHS = 56
};
new pInfo[MAX_PLAYERS][E_PLAYER_INFO];
printf("%i | %i | %i | %i", pInfo[0][SCORE], pInfo[0][MONEY], pInfo[0][KILLS], pInfo[0][DEATHS]);你可能会觉得输出是
0 | 9 | 5 | 56,对吧?如果你这么想,那就错了。实际上输出是
0 | 0 | 0 | 0。你可能会以为枚举是用来存储数据的,但事实并非如此。我在枚举里写了
MONEY = 9,然后在 printf 里用了 pInfo[0][MONEY],但编译器其实把它当成 pInfo[0][9] 来处理,而不是什么 E_PLAYER_INFO:MONEY。也就是说,MONEY 在这里只是作为一个索引来用的。所以枚举本质上根本不是变量,它其实就是一组常量,只不过帮你的数组索引起了个好记的名字罢了。
好,我们再来深入一点,应该能让你理解得更清楚。
举个例子:
代码:
const e_CAR1 = 0;
const e_CAR2 = 1;
const e_CAR3 = 2;
new MyCars[3];
main()
{
MyCars[e_CAR1] = 520;
MyCars[e_CAR2] = 458;
MyCars[e_CAR3] = 411;
}现在我们用枚举重写一下:
代码:
enum e_TEST
{
e_CAR1,
e_CAR2,
e_CAR3
};
new MyCars[e_TEST];
main()
{
MyCars[e_CAR1] = 520;
MyCars[e_CAR2] = 458;
MyCars[e_CAR3] = 411;
}这两段代码都能正常编译,做的事情也完全一样。你看出区别了吗?其实就只是代码写法不同而已。
这个例子告诉我们,枚举干的事情和常量是一样的。不过枚举有些地方比常量或者宏定义(
const e_CAR1 = 0; 或者 #define e_CAR1 0)更好用,具体差异我们后面再说。首先你需要明白枚举的结构:
代码:
enum TEST
{
Abc,
Def,
Ghi,
};Abc 是一个值为 0 的常量(差不多等于 const Abc = 0; 或 #define Abc 0),Def 是值为 1 的常量,Ghi 是值为 2 的常量。预编译器会自动给这些常量赋值,从 0 开始一直往上排。而用常量的时候,你得自己手动赋值。枚举则是自动帮你排好的。
再看个例子:
代码:
enum TEST
{
e_ONE, // 自动得到 0
e_TWO, // 得到 1
e_THREE, // 得到 2
e_FOUR = 12,// 这里手动赋了 12,所以不再是 3 了
e_FIVE, // 变成 13,因为上一个值是 12
e_SIX // 变成 14,上一个值是 13
}看到了吧,如果你手动给某个枚举项赋了值,后面的项就会在此基础上依次加 1。
再来看看更复杂的情况:
代码:
enum DATA
{
INT, // 得到 0
STRING[10], // 得到 1,但因为是数组,一个位置不够,它会占 10 个位置
// 所以实际占用的索引是 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
INT2, // 得到 11,因为 STRING 最后一位是 10
STRING2[10] // 从 12 开始,一直到 22,需要 10 个位置
};运行下面的代码:
代码:
printf("%i", _:DATA);结果输出
22。因为整个枚举的总大小是 22:
INT 占 1 个 + STRING 占 10 个 + INT2 占 1 个 + STRING2 占 10 个 = 22。
你可能会问,为什么我写 INT 是 1 而不是 0?因为这里我数的是占用了多少个位置,不是从哪个位置开始。如果问起始位置,那 INT 确实是 0。
一个大小为 22 的块,索引范围是 0 到 21,总共 22 个位置。
再试一下:
代码:
printf("%i", _:STRING);输出
1。因为第一个位置(索引 0)被 INT 占了,所以 STRING 从索引 1 开始。每个枚举项占用的位置是这样的:
- INT:索引 0
- STRING:索引 1 到 10
- INT2:索引 11
- STRING2:索引 12 到 21
运行这段:
代码:
new Array[DATA];
printf("%i", sizeof Array);结果也是 22,因为
DATA 的大小就是 22。所以,这个数组实际上相当于:
代码:
Array[INT + STRING + INT2 + STRING2];也就是说,用枚举的时候,你其实是用这些枚举常量作为数组的索引。
另一个例子
代码:
enum TEST
{
SomeInteger = 124,
SomeString[12],
Float:SomeFloat
};
public OnFilterScriptInit()
{
new var_TEST[TEST];
var_TEST[SomeInteger] = 1337;
printf("1) 储存的值: %i, 常量: %i", var_TEST[SomeInteger], _:SomeInteger);
format(var_TEST[SomeString], 12, "Hey!");
printf("2) 储存的值: %s, 常量: %i", var_TEST[SomeString], _:SomeString);
var_TEST[SomeFloat] = 2054.124;
printf("3) 储存的值: %f, 常量: %i", var_TEST[SomeFloat], _:SomeFloat);
return true;
}输出结果:
储存的值: 1337, 常量: 124
储存的值: Hey!, 常量: 125
储存的值: 2054.124023, 常量: 137
第一行,
SomeInteger 的常量值是 124,我把 1337 存到了索引 124 的位置。第二行,
SomeString 虽然定义了长度为 12,但输出常量值时显示的是 125。因为 125 是这个字符串的起始索引,它占了 125 到 136 这 12 个位置。第三行,浮点数正常显示,但
SomeFloat 的常量值是 137。你可能会想,它应该在 SomeString(125)后面,应该是 126 才对?但别忘了 SomeString[12] 是个数组,占了 12 个位置,所以 SomeFloat 的起始索引是 125 + 12 = 137。一些额外的重要信息
枚举也可以作为标签使用:
代码:
enum E_MY_TAG
{
E_FIRST = 4,
E_SECOND = 2
}然后可以这样写:
代码:
new E_MY_TAG:SomeVar;代码:
new E_MY_TAG:MyVariable = E_FIRST; // 不会报错,因为 E_FIRST 本来就是 E_MY_TAG 的一部分但是:
代码:
new data[E_MY_TAG];
data[E_FIRST] = 7; // 编译错误,索引越界E_MY_TAG 的大小是 3(枚举里最大索引是 2),而 E_FIRST 的值是 4,已经超出范围了。匿名枚举:
代码:
enum // 没有名字
{
E_TEST[10] = 32,
E_VAR
};
main()
{
new data[E_TEST]; // E_TEST 的值是 32,不是 10
}强标签和弱标签:
代码:
enum E_STRONG // 名字以大写字母开头,生成的是强标签
{
E_VAR = 64
};
main()
{
new Test = E_STRONG:E_VAR;
// 会报“标签不匹配”的警告,因为 Test 没有 E_STRONG 标签
}代码:
enum e_WEAK // 名字以小写字母开头,生成的是弱标签
{
E_VAR = 64
};
main()
{
new Test = e_WEAK:E_VAR;
// 没有警告
}枚举的大概内容就是这些了,这也是为什么我们更喜欢用枚举而不是直接写常量的原因。
感谢 Y_Less 的讲解。

