记录和宏

记录

1.记录是一个有着固定数目字段的数据结构,这些字段可以通过名称来访问。记录不同于元组,元组的字段可以通过位置信息来访问。可以定义一个记录类型如下:

-record(person,{name,age,phone}).

这就引入了记录类型person,每个记录实例包含如下三个名字的字段:name,age和phone。字段名称被定义为基元。下面是一个关于这种类型的记录实例:

#person{

name="Joe",

age=21,

phone="999-999"

}

也可以在记录的定义中为字段提供默认值:

-record(person,{name,age=0,phone=""}).

如果没有给出默认值,"默认的默认"是基元undefined。

2.一个包含field1到fieldn字段的name记录的一般定义采取如下格式:

-record(name,filed1[=default1],

        filed2[=default2],

 ...

 filedn[=defaultn])

方括号中的部分是可选字段默认值的声明,相同的字段名称可以用于一个以上的记录类型,事实上,两个记录可以共享相同的名字列表。记录的名称只可用于一个定义,不管怎样它是用来识别记录的。

操作记录

1.Person=#person{name="Fred"}

可以这样读取记录的字段:Person#person.name,Person#person.age等等。

2.访问字段的一般形式是:

RecordExp#name.fieldName

其中name和fieldName不能是变量,而RecordExp是用来表明这是一个记录的表达式,通常这是一个变量,但它也可能是一个应用程序函数的结果或对另一个记录类型的字段访问。

3.假设需要修改一个记录的一个字段,可以直接这样写:

NewPerson=Person#person{age=37}.

在这种情况下记录语法的真正优势就体现出来了,只需提到需要修改值的字段,那些从Person到NewPerson没有改变的字段值就不需要在定义中说明了。事实上,记录机制允许更新任何选择字段,例如:

NewPerson=Person#person{phone="999-999",age=37}

4.修改记录字段的一般格式是:

RecordExp#name{...,fieldNamei=valuei,...}

字段更新可以以任何顺序出现,但每个字段名称最多只能出现一次。

基于记录的函数和模式匹配

1.基于记录的模式匹配可以提取字段值和影响计算的控制流程,假设想定义一个birthday函数,给这个人的年龄增加1,可以使用字段选择,然后更新定义这个函数:

birthday(P)->

P#person{age=P#person.age+1}

如果使用模式匹配显然会更简单明了:

birthday(#person{age=Age}=P)->

P#person{age=Age+1}

2.记录字段可以包含任何有效的Erlang的数据类型,因为记录是有效的数据类型,所以字段也可以包含其他的记录,这就产生了嵌套记录。例如,在一个person记录中,name字段的内容本身也可以是一个记录:

-record(name,{first,surname})

P=#person{name=#name{first="Rebort",surname="Virding"}}

First=(P#person.name)#name.first

终端中的记录

1.Erlang中的记录只在编译器有效,在虚拟机中没有它们的类型。正因为如此,终端对它们的处理与其它构造的处理不同。

2.在终端中可以使用命令rr(moduleName)来加载模块moduleName中的所有记录定义,或者,在终端中可以使用命令rd(name,{field1,field2,...})来直接定义包含有field1,field2等的name记录。这个对测试和调试,或者当无权访问记录定义所在的某个模块的时候,是非常有用的。最后,使用命令rl()可以列出目前终端中所有可见的记录定义。

3.最终,终端命令rf(RecordName)和rf()删除当前终端中可见的一个或所有的记录定义。

记录实现

1.Erlang编译器在程序运行之前使记录生效实现,把记录转换成元组,并且把籍于记录的函数转换为籍于对应元组的函数和内置函数。

2.不应该使用记录的元组的表现形式:因为使用这种数据形式会破坏数据抽象,你对记录类型的任何修改在使用元组的时候都不会反映到代码中。如果在记录中添加一个字段,编译器创造的元组的大小将会改变,在尝试模式匹配记录到元组时会导致badmatch错误。如果正在使用记录,交换记录中的字段顺序不会影响代码,因为通过名字访问字段,然而在某些地方使用了元组,却忘了交换所有的具体数值,那么程序可能会运行错误或者更糟糕,程序可能表现出意想不到的行为。

3.想查看在代码转换时有关记录的生成代码,编译模块的时候请使用"E"选项,结果会产生一个以E为后缀的文件,让我们以编译records1为例:使用compile:file(records1,['E'])或终端命令c(records1,['E'])会产生一个名为records1.E的文件,包含目标代码的beam文件没有被生成。

记录内置函数

1.内置函数record_info给我们提供一个记录类型及其代表的信息。函数调用record_info(fields,recType)返回recType中的字段名称列表,而函数调用record_info(size,recType)返回元组的大小,即字段的数目加上1。一个字段在元组中的位置由#recType.fieldName来确定,而recType和fieldName都是基元。

2.内置函数record_info/2和#RecordType.Field计算可以不包含变量,但必须包含文字基元,这是因为它们是在代码运行和变量被绑定之前由编译器来处理,它们会先各自转换为所表达的值。

3.一个可以在保护元中使用的内置函数是is_record(Term,RecordTerm)。这个内置函数将证实项元是一个元组,它的第一个元素是RecordTag,而且其元组的大小是正确的,这个内置函数返回基元true或false。

1.宏允许你在Erlang中使用结构缩写,然后Erlang的预处理程序(EPP)在编译的时候会扩展它们,宏可以使程序更具有可读性和实现语言本身以外的特性。使用有条件的宏,就有可能写出可以在不同的方式下定制的程序,在调试和生产方式中或者在不同的系统架构中相互切换。

简单宏

1.最简单的宏可以用来定义一个常量,如:

-define(TIMEOUT,1000)

宏是通过在宏的名字前放置一个?来使用的,如:

receive

after ?TIMEOUT->ok

end.

2.一个简单宏定义的一般形式为:

-define(Name,Replacement).

大写Name是一种习惯,但不是必须的。事实上,它可以是任何Erlang的标记序列,也就是说,它可以是一系列单词,比如变量,基元,符号或者标点符号。其结果不一定是完整的Erlang表达式或顶级的形式(即函数定义或编译指令)。通过宏扩展生成新的关键字是不可能的。

宏参数化

1.宏可以带有变量名标识的参数。带参数的宏的一般形式为:

-define(Name(Var1,Var2,...,Varn),Replacement)

在这里,作为正常的Erlang变量,变量Var1,Var2,...,Varn需要大写第一个字母。

2.宏参数化的另一个例子是用于诊断打印输出,在代码中出现两个已经定义的宏,而注释掉其中一个并不罕见。在开发系统的时候,在代码中调试打印所有的信息。当想将它关闭的时候,需要做的只是在重新编译代码之前注释掉一部分定义,并去掉另一部分定义的注释。

调试和宏

1.Erlang中的宏的主要用途之一是允许代码可以以不同的方式引入,宏方法的优点是可以使用条件宏,它可以生成不同版本的代码,如调试版本和生产版本。

2.第一个方面,宏有能力将宏的参数作为字符串保留,它由参数标记组成,可以在变量前加前缀??来达到这一目的,例如:

-define(VALUE(Call),io:format("~p=~p~n",[??Call,Call]))

test1()->VALUE(length([1,2,3])).

第一次使用Call参数的是??Call,它将作为一个字符串参数扩展到文本,第二次使用将在终端中继续扩展。

3.第二个方面,有一组常用在调试代码中的预定义的宏。

?MODULE:扩展为它所在模块的名称

?MODULE_STRING:扩展为它所在模块的名称组成的字符串

?FILE:扩展为它所在的文件名

?LINE:扩展为它所在的行号

?MACHINE:扩展使用到的虚拟机,到目前为止,唯一可能的值是BEAM。

4.最后,定义条件宏是可能的,它根据传递给编译器的不同标记,以不同的方式来扩展。

-undef(Flag):复位Flag

-ifdef(Flag):如果设置了Flag,下面的语句被执行。

-ifndef(Flag):如果没有设置Flag,接下来的语句就会被执行。

-else:这提供了另外一种可能,如果这个条件满足了,接下来的语句将会被执行。

-elseif:终止条件构造。

下面是一个使用它们的实例:

-ifdef(debug)

-define(DBG(Str,Args),io:format(Str,Args))

-else

-define(DBG(Str,Args),ok)

-endif

在代码中可以使用如下:

?DBG("~p:call(~p) called~n",[?MODULE,Request])

如果想要打开系统的调试,需要设置调试标签,可以在终端中使用命令:

c(Module,[{d,debug}])

或者可以使用compile:file/2以编程的方式来做到这一点,可以使用c(Module,[{u,debug}])重置标签。

5.要调试宏定义,可以让编译器把epp处理完文件的结果保存到一个文件中,可以在终端中使用c(Module,['P']),而在程序中可以使用函数compile:file/2。这些命令保存结果到文件Module.P中。'P'标签不同于'E'标签,因为记录类型必要的代码转换不是由'P'进行的。

Include文件

1.习惯上把记录和宏定义到一个include文件中,使它们能够在整个项目的多模块中共享,而不仅仅是在一个模块中,为了使现有的定义可以在多个模块中使用,可以把它们放在一个单独的文件中,然后在模块中使用-include指令,其通常放置在module和export指令之后:

-include("File.hrl")

在前面的指令中,文件名两边的双引号"..."是强制性的。包含的文件带有后缀.hrl,但这不是强制性的。

2.编译器拥有一个路径列表来搜索包含文件,其中第一个路径是当前的目录,其次是包含要被编译的源代码的目录。可以在编译的时候使用i选项把其他的路径列表包含进来:c(Module,[{i,Dir}])。可以指定多个目录,最后指定的目录会被首先搜索。

猜你喜欢

转载自yansxjl.iteye.com/blog/2359334