本文节选自《LPC语言基础教程:从零学习游戏开发》,版权归@mudren,欢迎转载,但必须注明来源(mud.ren)。
宏定义(#define)
LPC语言中的宏定义用法和C语言一样。宏定义的一般形式为:
#define 宏名 字符串
对宏定义的几点说明:
1) 宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单粗暴的替换。字符串中可以含任何字符,它可以是常数、表达式、if 语句、函数等,预处理程序对它不作任何检查,如有错误,只能在编译已被宏展开后的源程序时发现。
2) 宏定义不是说明或语句,在行末不必加分号,如加上分号则连分号也一起替换。
3)宏定义必须放在函数之外,作用域为当前对象文件。如要终止其作用域可使用#undef命令
请注意:宏定义的常量和全局变量的作用域不同,全局变量可以在继承对象中直接使用,而宏定义无法在继承对象中使用。
宏的名称不允许有空格,而且必须遵守 C 语言的变量命名规则,只能使用字母、数字与下划线(_),且首字符不能是数字。
宏是原样替换,指定什么内容,就一模一样替换成什么内容。
#define HELLO "Hello, world"
// 相当于 printf("%s", "Hello, world");
printf("%s", HELLO);
上面示例中,宏HELLO会被原样替换成"Hello, world"。
#define指令可以出现在源码文件的任何地方,从指令出现的地方到该文件末尾都有效。习惯上,会将#define放在源码文件的头部。它的主要好处是,会使得程序的可读性更好,也更容易修改。
#define指令从#开始,一直到换行符为止。如果整条指令过长,可以在折行处使用反斜杠\,延续到下一行。
#define OW "C programming language is invented \
in 1970s."
上面示例中,第一行结尾的反斜杠将#define指令拆成两行。
#define允许多重替换,即一个宏可以包含另一个宏。
#define TWO 2
#define FOUR TWO*TWO
上面示例中,FOUR会被替换成2*2。
注意,如果宏出现在字符串里面(即出现在双引号中),或者是其他标识符的一部分,就会失效,并不会发生替换。
#define TWO 2
int main()
{
int TWOs = 22;
// 输出 TWO
printf("TWO\n");
// 输出 22
printf("%d\n", TWOs);
return 1;
}
上面示例中,双引号里面的TWO,以及标识符TWOs,都不会被替换。
同名的宏可以重复定义,只要定义是相同的,就没有问题。如果定义不同,在LPC语言中会报重复定义的错误。
// 正确
#define FOO hello
#define FOO hello
// 报错
#define BAR hello
#define BAR world
上面示例中,宏FOO没有变化,所以可以重复定义,宏BAR发生了变化,就报Warning: redefinition of #define BAR before the end of line。
带参数的宏
基本用法
宏的强大之处在于,它的名称后面可以使用括号,指定接受一个或多个参数。
#define SQUARE(X) X*X
上面示例中,宏SQUARE可以接受一个参数X,替换成X*X。
注意,宏的名称与左边圆括号之间,不能有空格。
这个宏的用法如下。
// 替换成 z = 2*2;
z = SQUARE(2);
这种写法很像函数,但又不是函数,而是完全原样的替换,会跟函数有不一样的行为。
#define SQUARE(X) X*X
// 输出19
printf("%d\n", SQUARE(3 + 4));
上面示例中,SQUARE(3 + 4)如果是函数,输出的应该是49(7*7);宏是原样替换,所以替换成3 + 4*3 + 4,最后输出19。
可以看到,原样替换可能导致意料之外的行为。解决办法就是在定义宏的时候,尽量多使用圆括号,这样可以避免很多意外。
#define SQUARE(X) ((X) * (X))
上面示例中,SQUARE(X)替换后的形式,有两层圆括号,就可以避免很多错误的发生。
宏的参数也可以是空的。
#define getchar() getc(stdin)
上面示例中,宏getchar()的参数就是空的。这种情况其实可以省略圆括号,但是加上了,会让它看上去更像函数。
一般来说,带参数的宏都是一行的。下面是两个例子。
#define MAX(x, y) ((x)>(y)?(x):(y))
#define IS_EVEN(n) ((n)%2==0)
如果宏的长度过长,可以使用反斜杠(\)折行,将宏写成多行。
#define PRINT_NUMS_TO_PRODUCT(a, b) { \
int product = (a) * (b); \
for (int i = 0; i < product; i++) { \
printf("%d\n", i); \
} \
}
上面示例中,替换文本放在大括号里面,这是为了创造一个块作用域,避免宏内部的变量污染外部。
带参数的宏也可以嵌套,一个宏里面包含另一个宏。
#define QUADP(a, b, c) ((-(b) + sqrt((b) * (b) - 4 * (a) * (c))) / (2 * (a)))
#define QUADM(a, b, c) ((-(b) - sqrt((b) * (b) - 4 * (a) * (c))) / (2 * (a)))
#define QUAD(a, b, c) QUADP(a, b, c), QUADM(a, b, c)
上面示例是一元二次方程组求解的宏,由于存在正负两个解,所以宏QUAD先替换成另外两个宏QUADP和QUADM,后者再各自替换成一个解。
那么,什么时候使用带参数的宏,什么时候使用函数呢?
一般来说,应该首先使用函数,它的功能更强、更容易理解。宏有时候会产生意想不到的替换结果,而且往往只能写成一行,除非对换行符进行转义,但是可读性就变得很差。
宏的优点是相对简单,本质上是字符串替换,不涉及数据类型,不像函数必须定义数据类型。而且,宏将每一处都替换成实际的代码,省掉了函数调用的开销,所以性能会好一些。
#运算符,##运算符
由于宏不涉及数据类型,所以替换以后可能为各种类型的值。如果希望替换后的值为字符串,可以在替换文本的参数前面加上#。
// 示例:7.2.2
#define F(f) f
#define STR(s) #s
int main(object me, string arg)
{
debug(F("mud.ren"));
// 不能使用,debug(F(mud.ren)); 会报错!
debug(STR(mud.ren));
debug(STR("mud.ren"));
return 1;
}
示例中运行结果:
mud.ren
mud.ren
"mud.ren"
如果在宏定义时没有使用 #,STR(mud.ren) 这种使用是会报错的。# 用来将宏参数转换为字符串,也就是在宏参数的开头和末尾添加引号。即使给宏参数“传递”的数据中包含引号,使用#仍然会在两头添加新的引号,而原来的引号会被转义输出。
下面是另一个例子。
#define XNAME(n) "x"#n
// 输出 x4
printf("%s\n", XNAME(4));
上面示例中,#n指定参数输出为字符串,再跟前面的字符串结合,最终输出为"x4"。
如果替换后的文本里面,参数需要跟其他标识符连在一起,组成一个新的标识符,可以使用##运算符。它起到粘合作用,将参数“嵌入”一个标识符之中。
#define MK_ID(n) i##n
上面示例中,n是宏MK_ID的参数,这个参数需要跟标识符i粘合在一起,这时i和n之间就要使用##运算符。下面是这个宏的用法示例。
int MK_ID(1), MK_ID(2), MK_ID(3);
// 替换成
int i1, i2, i3;
上面示例中,替换后的文本i1、i2、i3是三个标识符,参数n是标识符的一部分。从这个例子可以看到,##运算符的一个主要用途是批量生成变量名和标识符。
另一个##的示例:
#define SCOPE(parent, fn) parent##::##fn
void some_function () {
// inserts: name::fn();
SCOPE(name, fn());
}
取消宏定义(#undef)
#undef指令用来取消已经使用#define定义的宏。
#define LIMIT 400
#undef LIMIT
上面示例的undef指令取消已经定义的宏LIMIT,后面就可以重新用 LIMIT 定义一个宏。
有时候想重新定义一个宏,但不确定是否以前定义过,就可以先用#undef取消,然后再定义。因为同名的宏如果两次定义不一样,会报错,而#undef的参数如果是不存在的宏,并不会报错。
除了自定义宏,游戏驱动也提供了一些预定义的宏,最常用的是 __DIR__、___FILE__ 和 __LINE__。
__LINE__:表示当前源代码的行号;
__FILE__:表示当前源文件的名称;
__DIR__:表示当前源文件所在目录;
需要特别注意的是:__FILE__ 和 file_name() 的不同,我们新建 /cmds/test/7.2.1.c 测试,代码如下:
// 示例:7.2.1
int main(object me, string arg)
{
debug("文件目录:" + __DIR__);
debug("文件名:" + __FILE__);
debug("当行前:" + __LINE__);
debug("文件名:" + file_name());
return 1;
}
一方面 __FILE__ 带有扩展名 .c,另一方面,在继承对象中的结果也不同,再新建 /cmds/test/7.2.1.1.c 使用以下代码测试:
// 示例:7.2.1
inherit __DIR__ "7.2.1";
输出结果为:
文件目录:/cmds/test/
文件名:/cmds/test/7.2.1.c
当行前:6
文件名:/cmds/test/7.2.1.1
另外,FLUFFOS中还提供了大量的预定义宏,不同版本和环境下编译的驱动可能稍有不同,具体参考如下:
==============================
==== LPC Predefines ====
#define FLUFFOS
#define HAS_DEBUG_LEVEL
#define HAS_ED
#define HAS_PRINTF
#define MAX_FLOAT 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.000000
#define MAX_INT 9223372036854775807
#define MIN_FLOAT 0.000000
#define MIN_INT -9223372036854775808
#define MUDOS
#define MUD_NAME "MiniMud"
#define SIZEOFINT 8
#define __ARCH__ "Linux/x86-64"
#define __ARGUMENTS_IN_TRACEBACK__
#define __ARRAY_STATS__
#define __AUTO_SETEUID__
#define __CACHE_STATS__
#define __CALLOUT_HANDLES__
#define __CFG_COMPILER_STACK_SIZE__ 600
#define __CFG_EVALUATOR_STACK_SIZE__ 65536
#define __CFG_MAX_CALL_DEPTH__ 150
#define __CFG_MAX_GLOBAL_VARIABLES__ 65536
#define __CLASS_STATS__
#define __COMMAND_BUF_SIZE__ 2000
#define __COMPILER__ "/usr/bin/c++"
#define __CXXFLAGS__ "Broken"
#define __DEBUG_MACRO__
#define __DEFAULT_DB__ 2
#define __DEFAULT_PRAGMAS__ PRAGMA_WARNINGS + PRAGMA_SAVE_TYPES + PRAGMA_ERROR_CONTEXT + PRAGMA_OPTIMIZE
#define __DSLIB__
#define __ED_INDENT_SPACES__ 4
#define __ED_TAB_WIDTH__ 8
#define __GET_CHAR_IS_BUFFERED__
#define __HAS_CONSOLE__
#define __HAVE_DIRENT_H__ 1
#define __HAVE_JEMALLOC__ 1
#define __HAVE_SIGNAL_H__ 1
#define __HAVE_SYS_RESOURCE_H__ 1
#define __HAVE_SYS_STAT_H__ 1
#define __HAVE_SYS_TIME_H__ 1
#define __HAVE_TIME_H__ 1
#define __LARGEST_PRINTABLE_STRING__ 65535
#define __LARGE_STRING_SIZE__ 1000
#define __LOCALS_IN_TRACEBACK__
#define __MAX_SAVE_SVALUE_DEPTH__ 100
#define __MUDLIB_ERROR_HANDLER__
#define __OLD_ED__
#define __PACKAGES_PACKAGES_H__
#define __PACKAGE_ASYNC__
#define __PACKAGE_COMPRESS__
#define __PACKAGE_CONTRIB__
#define __PACKAGE_CORE__
#define __PACKAGE_CRYPTO__
#define __PACKAGE_DB__
#define __PACKAGE_DEVELOP__
#define __PACKAGE_EXTERNAL__
#define __PACKAGE_MATH__
#define __PACKAGE_MATRIX__
#define __PACKAGE_MUDLIB_STATS__
#define __PACKAGE_OPS__
#define __PACKAGE_PARSER__
#define __PACKAGE_PCRE__
#define __PACKAGE_SHA1__
#define __PACKAGE_SOCKETS__
#define __PACKAGE_TRIM__
#define __PACKAGE_UIDS__
#define __PARSE_DEBUG__
#define __PORT__ 4002
#define __PRIVS__
#define __PROJECT_VERSION__ "fluffos v2019.20220507-9-g7ff8942e"
#define __RANDOMIZED_RESETS__
#define __RECEIVE_SNOOP__
#define __REF_RESERVED_WORD__
#define __RESTRICTED_ED__
#define __SANE_EXPLODE_STRING__
#define __SANE_SORTING__
#define __SAVE_EXTENSION__ ".o"
#define __SAVE_GZ_EXTENSION__ ".o.gz"
#define __SENSIBLE_MODIFIERS__
#define __SMALL_STRING_SIZE__ 100
#define __STRING_STATS__
#define __STRUCT_CLASS__
#define __STRUCT_STRUCT__
#define __SUPPRESS_ARGUMENT_WARNINGS__
#define __THIS_PLAYER_IN_CALL_OUT__
#define __TIME_WITH_SYS_TIME__ 1
#define __TRACE__
#define __TRAP_CRASHES__
#define __USE_32BIT_ADDRESSES__
#define __USE_MYSQL__ 1
#define __USE_SQLITE3__ 2
#define __VERSION__ "fluffos v2019.20220507-9-g7ff8942e"
#define __WARN_OLD_RANGE_BEHAVIOR__
========================