查看源代码 预处理器
文件包含
可以使用以下方式包含文件
-include(File).
-include_lib(File).
File
,一个字符串,用于指向一个文件。此文件的内容将按原样包含在指令的位置。
包含文件通常用于多个模块共享的记录和宏定义。建议为包含文件使用文件扩展名 .hrl
。
File
可以以路径组件 $VAR
开头,其中 VAR
是一个字符串。在这种情况下,环境变量 VAR
的值(由 os:getenv(VAR)
返回)将替换 $VAR
。如果 os:getenv(VAR)
返回 false
,则 $VAR
将保持原样。
如果文件名 File
是绝对路径(可能在变量替换之后),则包含该名称的包含文件。否则,将在以下目录中按顺序搜索指定的文件
- 当前工作目录
- 正在编译的模块所在的目录
include
选项给出的目录
有关详细信息,请参阅 ERTS 中的 erlc 和 Compiler 中的 compile
。
示例
-include("my_records.hrl").
-include("incdir/my_records.hrl").
-include("/home/user/proj/my_records.hrl").
-include("$PROJ_ROOT/my_records.hrl").
include_lib
与 include
类似,但不是指向绝对文件。相反,第一个路径组件(可能在变量替换之后)被假定为应用程序的名称。
示例
-include_lib("kernel/include/file.hrl").
代码服务器使用 code:lib_dir(kernel)
来查找 Kernel 的当前(最新)版本的目录,然后搜索子目录 include
以查找文件 file.hrl
。
定义和使用宏
宏定义如下
-define(Const, Replacement).
-define(Func(Var1,...,VarN), Replacement).
宏定义可以放置在模块的属性和函数声明之间的任何位置,但定义必须在宏的任何使用之前。
如果一个宏在多个模块中使用,建议将宏定义放在包含文件中。
宏的使用方式如下
?Const
?Func(Arg1,...,ArgN)
宏在编译期间展开。简单的宏 ?Const
被替换为 Replacement
。
示例
-define(TIMEOUT, 200).
...
call(Request) ->
server:call(refserver, Request, ?TIMEOUT).
这将展开为
call(Request) ->
server:call(refserver, Request, 200).
宏 ?Func(Arg1,...,ArgN)
被替换为 Replacement
,其中宏定义中的所有变量 Var
的出现都被替换为相应的参数 Arg
。
示例
-define(MACRO1(X, Y), {a, X, b, Y}).
...
bar(X) ->
?MACRO1(a, b),
?MACRO1(X, 123)
这将展开为
bar(X) ->
{a,a,b,b},
{a,X,b,123}.
确保宏定义是有效的 Erlang 语法形式是一种良好的编程习惯,但不是强制性的。
要查看宏展开的结果,可以使用 'P'
选项编译模块。 compile:file(File, ['P'])
。这会在文件 File.P
中生成一个经过预处理和解析转换后的解析代码列表。
预定义宏
以下宏是预定义的
?MODULE
- 当前模块的名称,作为原子。?MODULE_STRING
- 当前模块的名称,作为字符串。?FILE
- 当前模块的文件名,作为字符串。?LINE
- 当前行号,作为整数。?MACHINE
- 机器名称,'BEAM'
。?FUNCTION_NAME
- 当前函数的名称,作为原子。?FUNCTION_ARITY
- 当前函数的元数(参数数量),作为整数。?OTP_RELEASE
- 运行编译器的运行时系统的 OTP 版本,作为整数。例如,当使用 Erlang/OTP 27 进行编译时,宏返回27
。注意
要在运行时查找版本,请调用
erlang:system_info(otp_release)
。请注意,它以字符串形式返回版本。例如,当版本为 Erlang/OTP 27 时,将返回字符串"27"
。变更
?OTP_RELEASE
宏是在 Erlang/OTP 21 中引入的。?FEATURE_AVAILABLE(Feature)
- 如果 特性Feature
可用,则展开为true
。该特性可能已启用,也可能未启用。变更
?FEATURE_AVAILABLE()
宏是在 Erlang/OTP 25 中引入的。?FEATURE_ENABLED(Feature)
- 如果 特性Feature
已启用,则展开为true
。变更
?FEATURE_ENABLED()
宏是在 Erlang/OTP 25 中引入的。
宏重载
可以重载宏,但预定义宏除外。重载的宏有多个定义,每个定义的参数数量不同。
变更
对宏重载的支持是在 Erlang 5.7.5/OTP R13B04 中添加的。
如果存在至少一个带有参数的 Func
定义,但没有一个带有 N 个参数的定义,则带有(可能为空的)参数列表的宏 ?Func(Arg1,...,ArgN)
将导致错误消息。
假设有以下定义
-define(F0(), c).
-define(F1(A), A).
-define(C, m:f).
则以下代码不起作用
f0() ->
?F0. % No, an empty list of arguments expected.
f1(A) ->
?F1(A, A). % No, exactly one argument expected.
另一方面,
f() ->
?C().
被展开为
f() ->
m:f().
删除宏定义
可以使用以下方式删除宏的定义
-undef(Macro).
条件编译
以下宏指令支持条件编译
-ifdef(Macro).
- 仅当定义了Macro
时才计算以下行。-ifndef(Macro).
- 仅当未定义Macro
时才计算以下行。-else.
- 仅允许在ifdef
、ifndef
、if
和elif
指令之后。如果前面的指令的计算结果为 false,则计算else
后面的行。-if(Condition).
- 仅当Condition
的计算结果为 true 时才计算以下行。-elif(Condition).
- 仅允许在if
或另一个elif
指令之后。如果前面的if
或elif
指令的计算结果不为 true,并且Condition
的计算结果为 true,则改为计算elif
后面的行。-endif.
- 指定一系列控制流指令的结束。
注意
宏指令不能在函数内部使用。
在语法上,if
和 elif
中的 Condition
必须是 保护表达式。其他构造(例如 case
表达式)将导致编译错误。
与标准的保护表达式相反,if
和 elif
中的表达式还支持调用伪函数 defined(Name)
,它测试 Name
参数是否是先前定义的宏的名称。如果宏已定义,则 defined(Name)
的计算结果为 true
,否则为 false
。尝试调用其他函数将导致编译错误。
示例
-module(m).
...
-ifdef(debug).
-define(LOG(X), io:format("{~p,~p}: ~p~n", [?MODULE,?LINE,X])).
-else.
-define(LOG(X), true).
-endif.
...
当需要跟踪输出时,在编译模块 m
时要定义 debug
% erlc -Ddebug m.erl
or
1> c(m, {d, debug}).
{ok,m}
然后 ?LOG(Arg)
展开为对 io:format/2
的调用,并为用户提供一些简单的跟踪输出。
示例
-module(m)
...
-if(?OTP_RELEASE >= 25).
%% Code that will work in OTP 25 or higher
-elif(?OTP_RELEASE >= 26).
%% Code that will work in OTP 26 or higher
-else.
%% Code that will work in OTP 24 or lower.
-endif.
...
此代码使用 OTP_RELEASE
宏来根据版本有条件地选择代码。
示例
-module(m)
...
-if(?OTP_RELEASE >= 26 andalso defined(debug)).
%% Debugging code that requires OTP 26 or later.
-else.
%% Non-debug code that works in any release.
-endif.
...
此代码使用 OTP_RELEASE
宏和 defined(debug)
仅针对 OTP 26 或更高版本编译调试代码。
-feature() 指令
指令 -feature(FeatureName, enable | disable)
可用于启用或禁用 特性 FeatureName
。这是启用(禁用)特性的首选方式,尽管也可以使用编译器的选项来完成。
请注意,-feature(..)
指令只能在任何语法使用之前出现。实际上,这意味着它应该出现在任何 -export(..)
或记录定义之前。
-error() 和 -warning() 指令
指令 -error(Term)
会导致编译错误。
示例
-module(t).
-export([version/0]).
-ifdef(VERSION).
version() -> ?VERSION.
-else.
-error("Macro VERSION must be defined.").
version() -> "".
-endif.
错误消息如下所示
% erlc t.erl
t.erl:7: -error("Macro VERSION must be defined.").
指令 -warning(Term)
会导致编译警告。
示例
-module(t).
-export([version/0]).
-ifndef(VERSION).
-warning("Macro VERSION not defined -- using default version.").
-define(VERSION, "0").
-endif.
version() -> ?VERSION.
警告消息如下所示
% erlc t.erl
t.erl:5: Warning: -warning("Macro VERSION not defined -- using default version.").
变更
-error()
和-warning()
指令是在 Erlang/OTP 19 中添加的。
字符串化宏参数
构造 ??Arg
,其中 Arg
是一个宏参数,它展开为包含参数标记的字符串。这类似于 C 中的 #arg
字符串化构造。
示例
-define(TESTCALL(Call), io:format("Call ~s: ~w~n", [??Call, Call])).
?TESTCALL(myfunction(1,2)),
?TESTCALL(you:function(2,1)).
结果为
io:format("Call ~s: ~w~n",["myfunction ( 1 , 2 )",myfunction(1,2)]),
io:format("Call ~s: ~w~n",["you : function ( 2 , 1 )",you:function(2,1)]).
也就是说,一个带有调用的函数和结果值的跟踪输出。