• 0 票 - 平均分 0
  • 1
  • 2
  • 3
  • 4
  • 5
[wiki系列] openmp/samp 关键字:指令
#1

[wiki系列] openmp/samp 关键字:指令



来自 SA-MP Wiki



目录





#assert



此指令会检查常量表达式是否为真,如果不为真则停止编译。

代码:
#define MOO 10
#assert MOO > 5

以上代码可以正常编译。

代码:
#define MOO 1
#assert MOO > 5

以上代码无法编译,会产生致命错误。

这类似于:

代码:
#define MOO 1
#if MOO <= 5
    #error Moo check failed
#endif

但是 #assert 会给出以下错误信息:

代码:
"Assertation failed: 1 > 5"

而第二种写法会给出:

代码:
"User error: Moo check failed"

哪种错误信息更实用,则视情况而定。



#define



#define 是一个文本替换指令。只要在代码中找到被定义的符号(宏名),就会将其替换为定义的内容。

代码:
#define MOO 7
printf("%d", MOO);

会被替换为:

代码:
printf("%d", 7);

这也是为什么反编译后的代码中看不到任何 #define,因为所有指令都在预处理器阶段处理完毕。定义的内容不一定是数字:

代码:
#define PL new i = 0; i < MAX_PLAYERS; i++) if (IsPlayerConnected(i)
for (PL)
{
    printf("%d connected", i);
}

会编译成大家熟悉(或讨厌)的玩家循环。注意括号的使用:一部分来自 for 语句,一部分来自宏本身。

另一个鲜为人知的特性是:定义可以跨行(通过在行尾加上反斜杠 \ 来转义换行)。一般来说换行会结束定义,但下面这样是有效的:

代码:
#define PL \
    new i = 0; i < MAX_PLAYERS; i++) \
        if (IsPlayerConnected(i)
for (PL)
{
    printf("%d connected", i);
}

\ 字符表示定义继续到下一行。

#define 的第三个重要特性是支持参数(宏参数)。参数用 %0%9 表示,用法与普通函数参数类似:

代码:
#define MOO(%0) \
    ((%0) * 7)
printf("%d", MOO(6));

输出结果为 42(不是随意选的)。注意宏里多余的括号——这是因为宏是纯文本替换,所以实际编译成:

代码:
printf("%d", ((6) * 7));

这样是安全的。但看下面这个例子:

代码:
printf("%d", MOO(5 + 6));

你会期望结果是 77((5 + 6) * 7)。加上括号后确实如此;如果去掉括号:

代码:
#define MOO(%0) \
    %0 * 7
printf("%d", MOO(5 + 6));

实际会变成:

代码:
printf("%d", 5 + 6 * 7);

由于运算优先级(BODMAS),结果变为 47(5 + (6 * 7)),这就完全错误了。

关于参数还有一个有趣的事实:如果传入的参数数量超过定义的数量,最后一个参数会包含所有多余的部分。例如:

代码:
#define PP(%0,%1) \
    printf(%0, %1)
PP("%s %s %s", "hi", "hello", "hi");

实际会打印:

代码:
hi hello hi

因为 %1 包含了 "hi", "hello", "hi"。你可能还注意到 # 可以把字面量转换成字符串,这是 openmp(samp) 特有的功能,这里只是为了区分参数。



#else



#else 相当于普通 else,但用于 #if 条件。



#elseif



#elseif 相当于 else if,但用于 #if 条件。

代码:
#define MOO 10
#if MOO == 9
    printf("if");
#elseif MOO == 8
    printf("else if");
#else
    printf("else");
#endif



#emit



此指令在 pawn-lang.pdf 的表格中并未列出,但实际存在。它相当于内联汇编器,如果你熟悉 AMX 字节码,可以用它直接插入 AMX 操作码。唯一限制是每次只能带一个参数。

语法:#emit <opcode> <argument>

<argument> 可以是小数、整数或(局部/全局)符号(变量、函数、标签)。

操作码列表及其含义可在 Pawn Toolkit ver. 3664 中找到。



#endif



#endif 相当于 #if 的结束括号。#if 不使用大括号,所有条件内容一直持续到对应的 #endif



#endinput , #endscript



此指令停止当前文件的包含(不再继续读取该文件)。



#error



此指令立即停止编译,并输出自定义错误信息。示例见 #assert



#if



#if 是预处理器版本的 if。你可以精确控制哪些代码被编译、哪些不被编译。例如:

代码:
#define LIMIT 10
if (LIMIT < 10)
{
    printf("Limit too low");
}

会编译成:

代码:
if (10 < 10)
{
    printf("Limit too low");
}

编译器知道这个条件永远为假,因此会给出“常量表达式”警告。但既然永远不会成立,为什么还要保留这段代码呢?你可以直接删掉,但之后别人修改 LIMIT 重新编译时就无法检查了。这就是 #if 的作用。

普通 if 在常量表达式时会警告,而 #if 必须 是常量表达式:

代码:
#define LIMIT 10
#if LIMIT < 10
    #error Limit too low
#endif

这样会在编译时就检查限制是否过小,如果是则直接报错,而不用运行脚本测试。同时也不会生成多余的代码。注意 #if 不强制使用括号(虽然复杂表达式可能需要)。

另一个例子:

代码:
#define LIMIT 10
if (LIMIT < 10)
{
    printf("Limit less than 10");
}
else
{
    printf("Limit equal to or above 10");
}

同样是常量检查,会产生警告,而且两个 printf 都会被编译(尽管我们知道只会执行一个)。用 #if 改写后:

代码:
#define LIMIT 10
#if LIMIT < 10
    printf("Limit less than 10");
#else
    printf("Limit equal to or above 10");
#endif

这样只有需要的 printf 会被编译,另一个仍保留在源代码中(方便以后改 LIMIT),但不会占用最终编译后的代码空间,也不会每次运行都执行无用的判断。



#include



此指令会把指定文件的所有代码插入到 #include 所在的位置。有两种包含方式:相对路径(用双引号)和系统路径(用尖括号,我自己起的名称,如果你有更好的叫法请告诉我)。

相对包含使用双引号,路径相对于当前文件:

代码:
#include "me.pwn"

会包含与当前文件同目录下的 me.pwn

系统包含使用尖括号,从 Pawn 编译器所在目录下的 include 文件夹(或其父目录的 include 文件夹)中查找:

代码:
#include <me>

会包含 qawno/include/me.inc(注意没有扩展名;如果文件不是 .p.inc,可以指定扩展名)。

两种方式都支持子目录:

代码:
#include "folder/me.pwn"
#include <folder/me>

如果文件不存在,编译会立即失败。



#pragma



说明



这是最复杂的指令之一。它提供了一系列选项来控制脚本的编译行为。例如:

代码:
#pragma ctrlchar '$'

会把转义字符从 \ 改成 $,所以换行符不再是 "\r\n",而是 "$r$n"

很多选项原本是为嵌入式系统设计的,用来限制 PC 上几乎无限制的资源。这里只列出与 openmp(samp) 相关的选项(完整列表见 pawn-lang.pdf)。

列表



                                                                                                                                               
名称说明
codepage名称/值设置字符串使用的 Unicode 码页
compress1/0openmp(samp) 不支持,请勿使用
deprecated符号名称如果使用该符号会产生警告,用于提示有更好的替代版本
dynamic值(通常是 2 的幂)设置栈和堆内存大小(单位:cells)。出现“excessive memory usage”警告时需要设置
librarydll 名称在 openmp(samp) 中经常被误用。它指定本文件中的原生函数来自哪个 DLL,并非把文件定义为库
pack1/0交换 !"""" 的含义(详见 pawn-lang.pdf 中的打包字符串说明)
tabsize经常被误用。用于设置制表符宽度,避免因空格和制表符混用产生的警告。openmp(samp) 默认设为 4(qawno 编辑器中制表符宽度)。设为 0 会关闭所有缩进警告,但强烈不推荐(会导致代码难以阅读)
unused符号名称类似 deprecated,用于抑制“symbol is never used”警告。推荐使用 stock,但某些情况(如函数参数)无法使用 stock 时才用此方式

示例



已弃用(Deprecated)


代码:
new
    gOldVariable = 5;
#pragma deprecated gOldVariable
main()
{
    printf("%d", gOldVariable);
}

使用 gOldVariable 时会产生警告,提示不应再使用它。主要用于函数更新 API 时保留向下兼容性。



#tryinclude



类似于 #include,但如果文件不存在,编译不会失败。这非常适合根据用户是否安装了特定插件(或插件的 include 文件)来选择性包含功能。

myinc.inc:

代码:
#if defined _MY_INC_INC
    #endinput
#endif
#define _MY_INC_INC
stock MyIncFunc()
{
    printf("Hello");
}

主脚本:

代码:
#tryinclude <myinc>
main()
{
    #if defined _MY_INC_INC
        MyIncFunc();
    #endif
}

只有当 myinc.inc 存在并被成功包含时,才会调用 MyIncFunc()。这对 IRC 插件等需要检测插件是否安装的情况非常有用。



#undef



移除之前定义的宏或常量符号。

代码:
#define MOO 10
printf("%d", MOO);
#undef MOO
printf("%d", MOO);

第二个 printf 会编译失败,因为 MOO 已被取消定义。

代码:
enum
{
    e_example = 300
};
printf("%d", e_example);
#undef e_example
printf("%d", e_example); // 致命错误


#GTA# #圣安地列斯# #侠盗猎车手# #圣安地列斯联机# #samp# #gta联机# #gtasa联机# #openmp# #omp# #open.mp# #gtasa#

社区交流群: 673335567

论坛: https://open-mp.cn/
  回复


论坛跳转:


浏览此主题的用户: 1 位客人