1.配置D:\Program Files\erl5.9.1\bin的时候,将bin目录下面的工具引入到环境变量里面,然后可以利用
打开命令行控制窗口,先进到test.erl的位置,然后执行erl工具,详细见截图:
2.记得执行的时候要末尾要加上一个局点".",代表执行。
如果你想退出Erlang终端窗口,那么请输入q()并加上句点
3.基元由小写字母开始或者是由单引号界定。当基元用小写字母开始的时候,字母、数字、"at"符号(@).和下划线(_)都是有效的字符,如果一个基元通过单引号
封装起来,则可以使用任意字符。 (注:大写字母不能当做基元来操作)
基元(atom)来表示文字常量。Erlang中的基元和其他语言中的枚举作用是一样的。
6> '1
6> 2
6> 3
6> 4'.
'1\n2\n3\n4'
上面的列子说明 单引号里面的字符串可以换行来执行
4.rlang中有各种各样的内置函数,通常在Erlang社区中称为BIF。
它们可以应用在程序和终端中。其中内置函数is_boolean用来测试一个Erlang值是否是布尔类型。
is_boolean(9+6).
5.布尔类型的逻辑运算符
((1>0) and (2>1)).
6.元组是用来保存一组数据元素的复合数据类型,其中数据元素要求是Erlang数据类型,但并不一定要求是相同的类型。
元组使用封闭的花括号{...}来表示,而其中的元素由逗号隔开。
Erlang提供了一些内置函数用来设置和检索元组元素的内容,并得到元组的大小。
当一个元组的第一个元素是一个基元时,称它为标记(tag)。
这一Erlang惯例用来表示不同类型的数据,同时通常在使用它的程序中有着特殊的意义。
例如,在元组{person, 'Joe', 'Armstrong'}中,
基元person是标记,这有可能也就表明第二个元素是这个人的名,然后第三个元素是这个人的姓。
1> tuple_size({abc, {def, 123}, ghi}).
3
2> element(2,{abc, {def, 123}, ghi}).
{def,123}
3> setelement(2,{abc, {def, 123}, ghi},def).
{abc,def,ghi}
4> {1,2}<{1,3}.
true
5> {2,3}<{2,3}.
false
6> {1,2}=={2,3}.
false
从第二条可以看出,下标是从1开始,而不是0
(找一份Erlang的api)
7.列表和元组都是用来存储元素集合的,在这两种情况中,它们的元素都可以是不同的。
列表用封闭的括号[...]来定义,而它们的元素由逗号隔开。
[january, february, march]
[123,def, abc]
[a,[b,[c,d,e],f],g]
[]
[{person,'Joe','Armstrong'},{person,'Robert','Virding'},
{person, 'Mike', 'Williams'}]
[72,101,108,108,111,32,87,111,114,108,100]
[$H,$e,$l,$l,$o,$,$W,$o,$r,$l,$d]
"Hello World"
字符和字符串:
字符(a,b,c类似形式) 字符串("abc","bbbn"类型形式)
字符由整数来表示,字符串(由字符组成)则由整数列表来表示。字符的整数值可以通过在字母前加上$符号来得到:
1> $A.
65
2> $A + 32.
97
3> $a.
97
Erlang中没有字符串数据类型。在Erlang中,字符串是一个由ASCII值组成的整数列表,并使用双引号("")来表示。
因此,字符串"Hello World"实际上就是列表[72,101,108,108,111,32,87,111,114,108,100] 。
如果你使用ASCII整数符号$Character来表示整数,那么你就会得到这样的列表[$H,$e,$l,$l,$o,$ ,$W,$o,$r,$l,$d]。
空字符串""则等价于空列表[]:
基元和字符串的区别
基元和字符串的区别在哪里?首先它们处理的方式不同,唯一可用于基元的操作是比较操作,而你可以使用很多不同的方式处理字符串。
例如,可以把字符串"Hello World"分解成["Hello","World"]的列表,而基元'Hello World'就不能这样处理。
双引号表示字符串。单引号表示基元
列表的组成和处理
正如我们前面所说,列表和元组的处理方式是非常不同的。元组的处理只能是提取的具体元素,而列表只要不为空,就可以把一个列表分成头部和尾部。
列表的头部是指列表的第一个元素,它的尾部是一个包含所有剩余元素的一个列表,而这个列表可以继续这样分解下去,如图2-1所示。
列表的头可以是任何东西,但是列表的尾通常还是一个列表。
正如列表可以如此这样分割下去一样,我们也可以用一个列表和一个元素组成一个新的列表。新的列表可以这样构造 —— [头部|尾部],这是一个典型的构造器(constructor,简写为cons)。
关于列表的很多操作都在lists库模块中定义,下面我们可以看到其中的一些例子。
这些函数不是内置函数,它们通过在函数前加上模块的名字来调用,并用冒号隔开,例如 lists:split。
列表函数和操作:
1> lists:max([1,2,3]).
3
2> lists:reverse([1,2,3]).
[3,2,1]
3> lists:sort([2,1,3]).
[1,2,3]
4> lists:split(2,[3,4,10,7,9]).
{[3,4],[10,7,9]}
5> lists:sum([3,4,10,7,9]).
33
6> lists:zip([1,2,3],[5,6,7]).
[{1,5},{2,6},{3,7}]
7> lists:delete(2,[1,2,3,2,4,2]).
[1,3,2,4,2]
8> lists:last([1,2,3]).
3
9> lists:member(5,[1,24]).
false
10> lists:member(24,[1,24]).
true
11> lists:nth(2,[3,4,10,7,9]).
4
12> lists:length([1,2,3]).
** exception error: undefined function lists:length/1
13> length([1,2,3]).
3
如果你想在一个列表前添加一个元素,那么有两种方法:
直接使用构造器,例如[1|[2,3,4]]。
使用++运算符,例如[1] + + [2,3,4]。
这两种方法产生同样的结果,但++运算符效率更低,并可能导致程序运行时速度大幅度减慢。
因此当你想添加一个元素到一个列表头部的时候,应该尽量使用高效率的构造器方法([...|...])。
当++运算符加入到Erlang语言之后,程序员就从使用append函数转变为滥用++运算符。
其实++运算符和append函数都是代价很高的操作,因为表达式的左边的列表都要遍历一次。
它们不仅仅是耗时的操作,而且常常是多余的,比如在Erlang中所有I/O函数(包括套接字操作)都接受未展开的字符串,比如["Hello ",["Concurrent "]|"World"]。
列表:
List = [Element|List] or []
[[3,2]|1]
[1|[2,3]]==[1,2,3] true [1,2|3]==[1,2,3] false
[1|[2|[3]] 应该少了一个]号确实少了个 正确的应该是 [1|[2|[3]]]
[1|[2|[3|[]]]]
[1,2|[3|[]]]
[3]==[3|[]]. true
[3]==[[]|[3]]. false
[3|[]]==[3]. true
[[]|[3]]==[3]. false
8.项元比较.
布尔类型:
==等于 /==不等于
=:= 精确等于 =/=精确不等于
>= 大于等于 > 大于 <= <
number < atom < reference < fun < port < pid < tuple < list < binary
列表按字典顺序排列,就像是字典里面的词一样。第一个元素先比较,小的那个表明这个列表也较小:如果它们是相同的,则第二个元素将继续进行比较,如此下去。当其中一个列表先比较完了,那么它就是较小的列表。因此:
5> [boo,hoo]<[adder,zebra,bee].
false
6> [boo,hoo]<[boo,hoo,adder,zebra,bee].
true
而另一方面,元组比较时,先会比较结构中的元素数目,然后再一个个比较各个元素的值:
7> {boo,hoo}<{adder,zebra,bee}.
true
8> {boo,hoo}<{boo,hoo,adder,zebra,bee}.
true
9.变量
变量用来存储简单和复合数据类型的值。在Erlang中,变量必须以大写字母开头(注2),后面的字符可以是大小写字母、整数和下划线。
它们不能包含任何其他的“特殊”字符。下面是变量的一些例子:
A_long_variable_name Flag Name2 DbgFlag
Erlang中的变量不同于大多数的传统编程语言。
在一个变量的生命周期内,包括在Erlang终端处理过程中,只容许给变量赋值一次,你就不能改变它了,这称为单次赋值。
因此,当你需要对一个变量的值进行计算和操作的时候,可以把结果存储在另外一个新的变量当中。
Erlang中变量的另一个有用的特性是我们不需要声明它们,只需要使用它们即可。
rlang的一个优点是,它不需要明确地分配和释放内存。对于C程序员来说,这意味着不会再有不眠之夜来查找指针错误或者内存泄漏。
用来存储复杂数据结构的内存在需要的时候会由系统自动分配,而当不再引用它的时候,就会由垃圾收集器自动回收释放掉。
Erlang内存管理:
现有的Erlang虚拟机的实现使用了通用继承性垃圾收集器。
垃圾收集针对每个并发进程各自独立进行:当一个处理器没有更多的内存用于存储的时候,就会自动触发垃圾收集器。
10.匹配模式: (重点测试下这种模式 测试 xs=[[]|XS])
模式匹配[A,B,C,D] = [1,2,3]则会失败。虽然它们两个都是列表类型,但是左边的列表有4个元素,而右边一个列表只有3个元素。
一个常见的误解是D可以设置为空列表,然后模式匹配应该成功。在这个例子中这是不可能的,因为C和D之间由逗号而不是构造器操作符分隔开。
[A,B,C|D]=[1,2,3]的模式匹配就会成功,变量A、B和C分别绑定到整数1、2和3,变量D则绑定到尾部,在这里就是空列表。
{A, _, [B|_], {B}} = {abc, 23, [22, 23], {22}}
会成功地提取元组的第一个元素,即基元abc,并把它绑定到变量A上。同时也将成功提取第三个元素元组的第一个元素并把它绑定到变量B上。
14> Var = {person, "Francesco", "Cesarini"}.
{person, "Francesco", "Cesarini"}
15> {person, Name, Surname} = Var.
{person, "Francesco", "Cesarini"}
在第一个语句中我们把一个类型为person的元组绑定到变量Var上了,然后在第二个语句中我们分别提取了名字和姓氏。
这将会成功地把字符串“Francesco”绑定到变量Name上并把字符串“Cesarini”绑定到变量Surname上。
11> {Element, Element, _} = {1,1,2}.
{1,1,2}
就如使用变量一样,在一个模式中我们也可以使用通配符,_,。这将匹配任何东西,并且不产生任何绑定。
我们刚才看到了变量可以用下划线开始,下划线是一个特殊的标记,它告诉编译器,这些变量是无关紧要的,它们只是作为程序中不需要的变量的占位符。
“无关紧要的”变量的功能跟普通变量一样,可以检查、使用和比较它们的值。唯一不同的是,普通变量的值如果从未使用过,编译器将会发出警告,而使用“无关紧要的”变量则不会。使用“无关紧要的”变量是一种很好的编程实践,这告诉阅读代码的人应该忽略掉这个值。
为了提高可读性和维护性,程序员经常以“无关紧要的”变量的形式引入值和类型。
下划线本身也是个“无关紧要的”变量,但不能访问其内容:因为它的值被忽略了而且从未绑定。
11.函数
Erlang程序包含相互调用的函数。函数组合在一起并在模块中定义。函数的名称是一个基元。
一个函数的头包括名字,随后是一对括号,在里面包含多个形式的参数或者没有参数。
在Erlang中,函数参数的数量叫做元数。使用箭头(->)来分隔函数头和函数主体。
在这两种操作系统下,你可以在Erlang终端中使用cd(Directory)进入到各个目录。
一旦进入该目录,你就可以在Erlang的终端中使用c(Module)并省略名称中的.erl后缀来进行编译。
如果代码中没有错误,编译就会成功。
factorial(0) -> 1;
factorial(N) ->
N * factorial(N-1).
正如我们前面提到过的,模式匹配发生在函数的头部,如何匹配成功,就会绑定变量N的一个实例。对每个语句来讲变量都是本地的,它们不需要分配或释放,Erlang运行时系统会自动处理这些。(方法定义的时候,前面先用;来表示,最后一个方法用.来表示)
12.模块:
函数组合在一起构成了模块。一个程序往往是分散在几个模块当中,每个模块包括按逻辑组合在一起的函数。
模块文件以.erl后缀来结尾,文件名称和模块名称必须是相同的。
若要执行从一个模块中导出的函数,你必须编译代码,这会在与模块相同的目录下面生成一个module.beam文件:
模块可以这样直接命名–module(Name),因此在例2-2中,demo模块将存储在一个名为demo.erl的文件中。
-module(demo).
-export([double/1]).
% This is a comment.
% Everything on a line after % is ignored.
double(Value) ->
times(Value, 2).
times(X, Y) ->
X*Y.
export指令以Function/Arity的格式(方法名/参数个数),包含了导出函数的一个列表。这些函数是全局性的,这意味着可以从模块的外部调用它们。
在Erlang中,注释以百分号(%)开始直到该行结束。请务必在代码中多多使用它们!
在Windows环境下,一种打开werl终端的方法是右击一个.beam文件,然后从弹出的窗口菜单中选择Open With option的“werl”选项。从现在起,双击任何源文件相同目录下的.beam文件,就会打开一个Erlang终端了。
在这两种操作系统下,你可以在Erlang终端中使用cd(Directory)进入到各个目录。一旦进入该目录,你就可以在Erlang的终端中使用c(Module)并省略名称中的.erl后缀来进行编译。如果代码中没有错误,编译就会成功。
1> cd("/home/francesco/examples").
/home/francesco/examples
ok
2> c(demo).
{ok,demo}
3> demo:double(10).
20
4> demo:times(1,2).
** exception error: undefined function demo:times/2
13.从复合数据类型中提取值
Person = {person, "Mike", "Williams", [1,2,3,4]}.
{person, Name, Surname, Phone} = Person.
Name.
小写字母开头表示基元,大写字母开头表示变量
14.case
防御性编程
假设程序把一个整数映射到一个表示一周中某天的基元。使用catch-all语句的防错性编程如下所示。
convert(Day) ->
case Day of
monday -> 1;
tuesday -> 2;
wednesday -> 3;
thursday -> 4;
friday -> 5;
saturday -> 6;
sunday -> 7;
Other -> {error, unknown_day}
end.
listlen(Y) ->
case Y of
[] -> 0;
[_|Xs] -> 1 + listlen(Xs)
end.
if case X rem 2 of
X rem 2 == 1 -> odd; 1 -> odd;
X rem 2 == 0 -> even 0 -> even
end end
15.保护元
保护元(guard)是一个额外的限制条件,它应用于函数的case或者receive语句中(我们会在第4章中具体讲述receive表达式)。
保护元应该放在“->”之前来分隔语句的主体和头部。
保护元由when关键字和紧跟其后的一个保护元表达式组成。只有在模式匹配和保护元表达式求值结果为基元true的情况下,这个语句才会执行。
max(X,Y) when X>Y- > X;
max(X,Y) - > Y.
接下来重写第2章中阶乘的例子:
factorial(0) -> 1;
factorial(N) ->
N * factorial(N-1).
这一次使用保护元:
factorial(N) when N > 0 ->
N * factorial(N - 1);
factorial(0) -> 1.
my_add(X,Y) when not(((X>Y) or not(is_atom(X))
) and (is_atom(Y) or (X==3.4))) ->
X+Y.
新的保护元函数是is_atom/1和is_integer/1等。
元编程:
一个函数在运行时才确定将调用哪些函数的特性叫做元编程,也就是程序创建程序并运行。
16.内置函数:
要从标准输入读取一行,请使用io:get_line/1,它需要提示符字符串(或基元)作为它的输入:
1> io:get_line("gissa line>").
gissa line>lkdsjfljasdkjflkajsdf.
"lkdsjfljasdkjflkajsdf.\n"
也可以这样读取指定数量的字符:
2> io:get_chars("tell me> ",2).
tell me> er
"er"
17.
keysearch(Key, N, TupleList) -> {value, Tuple} | false
这个函数和keyfind差不多,就是返回值的结构不一样
也是在TupleList中找一个Tuple,这个Tuple的第N个元素和Key一样。
例子:
List1 = [{name,"zhangjing"},{name,"zhangsan"}]
lists:keysearch("zhangjing",2,List1).
结果:
{value,{name,"zhangjing"}}
lists:keysearch(1,1,[{1,2},{2,4}]).
第一个元素的值和key值一样。
18.匹配模式,函数匹配模式
Page40 模块是Erlang中代码的基本单元,我们编写的所有函数都存于模块之中。模块文件通常以.erl为扩展名的文件中。
要运行一个模块,首先需要编译它,编译成功之后的模块文件其扩展名为.beam
模块名:方法名(). 来调用模块里面的方法
小写字母是基元,大写字母是变量
函数cost/1 :符号Name/N表示一个带有N个参数名为Name的函数,N称为函数的运算目。
函数cost/1必须从模块之中导出,如果你想从模块的外部调用它,这是必须的。即用-compile(export_all)来导出模块之中的所有函数
[x]就是[x|[]]的缩写
19.fun
P47。fun就是匿名函数,先定义一个Z,然后将fun赋值给一个变量Z (基元是小写字母开头的,就当做是一个常量,变量大写字母开头的,相当于一个变量)
1>Z=fun(X)-> 2*X end.
2>Z(2)
4
fun匹配多个函数 p48
20. 逗号(,)用来分隔函数调用、数据构造器以及模式中的参数
分号(;)用来分隔子句,在这几种情况相下都会用到子句:分段的函数定义、case语句、if语句、try....catch语句以及receive表达式。
无论何时,我们只看到一组后面跟有表达式的模式,都会使用分号进行分隔。
div 整数除法
rem 整数取余
21.返回fun的fun的值的时候,
Double=fun(x)->(2*x)end.
Double(5).
10
%定义两层fun
Mult= fun(Time)->(fun(X)->(Time*X)) end.
调用它的时候, Trip=Mult(5). Time的值为5
Trip(3). X的值为3
(1.)Mult=fun(time,A,B)->A*B end.
调用 Mult(time,2,3) 返回6
(2.)Mult(A,B)-> A*B end.
调用 Mult(3,4) 返回12
求和:
sum(H|T)-> H+sum(T);
sum([])->0.
将此文件储存为mylists.erl
c(mylists.erl). %%先将此模块编译
然后再调用
L=[1,2,3]
mylists.sum(L).
6
map(_,[]) ->[];
map(F,[H|T]) ->[F(H)|map(F,T)]. %[F[H]|F[T]|[]] 将依次的列表元素当做参数传进方法里面去 (F变量代表的是一个方法名)
L=[1,2,3,4]
mylists.map(fun(x)->2*x end,L). %% fun(1)+fun(2)+fun(3)+fun(4)
解析:
[2,4,6,8]
注:不能将自己的模块定义成lists ,因为有系统的模块名为这个名字
模块 -import(lists,[map/2,sum/1]).
如果有导入声明的时候,不需要写lists:map(...),可以直接使用map(....), 如果没有导入的函数,记得要加上模块名才能使用
23.
列表解析: (列表解析很有作用)
L=[1,2,3,4]
[2*X||X<-L]. %[F(X)||X<-L]代表"由F(X)组成的列表,其中X是取值于列表L"
使用列表解析实现过滤功能:
[X||{a,X}<-{a,1},{1,mn},{a,'m'}]
[1,'m']
使map方法更简洁:
map(F,L)->[F(X)||X<-L]. %<-不是方法体,而是说X的值是从L里面取的
列表解析很强大!!
list.seq(1,N)返回一个由1到N整数组成的列表,所以A<-lists:seq(1,N)意味着A的取值范围是1到N的所有整数。
max(X,Y) ->
case X>Y of
true->X;
false->Y;
end.
max(X,Y) ->
if X>Y
true - >X;
false ->Y;
end.
运用保护元:
max(X,Y) when X>Y -> X;
max(X,Y) ->Y.
什么情况下使用end结尾??
断言序列 ==保护元 Page58
24.记录
Page60.
-record(Name,{key1=Default1,
key2=Default2,
.....
}).
定义record
-record(todo,{status=remider,who=joe,text}
})
创建和更新记录
X=#todo{}.
X1=#todo{status=urgent,text='Fix errata in book'}.
X2=X1#todo{status=done}.
#用于创建类型为todo的新记录,key都是原子。
X2=X1#todo{status=done}. 这个表示创建了一个X1(X1必须为todo类型的)的副本,并将value的值改为了done,原记录本身并未改变
记录的匹配模式:
#todo{who=W,text=Txt} =X2.
W.
joe
Txt.
Fix errata in book
%%匹配操作符的左边,我们使用了自由变量W和Txt来定义一个记录模式
如果只是想提取某个记录之中的对应的值,X2#todo.text来取值
-module(shop).
-export(cost).
cost(oranges) ->5;
cost(apple) ->8;
cost(milk) ->16.
-module(shop1).
-export([total/1]).
total([{What,N}|T]) -> shop:cost(What)*N+total(T);
total([]) -> 0.
尝试在Erlang终端输入xd ,进入到相应的目录
在这两种操作系统下,你可以在Erlang终端中使用cd(Directory)进入到各个目录。一旦进入该目录,你就可以在Erlang的终端中使用c(Module)并省略名称中的.erl后缀来进行编译。如果代码中没有错误,编译就会成功。
-module(demo).
-export([double/1]).
% This is a comment.
% Everything on a line after % is ignored.
double(Value) ->
times(Value, 2).
times(X, Y) ->
X*Y.
1> cd("/home/francesco/examples").
/home/francesco/examples
ok
2> c(demo).
{ok,demo}
3> demo:double(10).
20
4> demo:times(1,2).
** exception error: undefined function demo:times/2
25.顺序型编程进阶
二进制数据:在Erlang中可以使用一种二进制数据结构来存储大量的原始数据。相对于列表或者元组,二进制类型更加节省内存,而且运行时系统也对此进行了优化。
在书写和打印时,二进制数据以一个整数或者字符序列的形式了来出现,两端分别用两个小于号和两个大于号括起来,例如:
<<5,10,20>>.
<<5,10,20>>
<<"hello">>.
<<"hello">>
列表构造:[1,2,[3,4,5],6|[8]] (记得测试下)
比特语法:
M=<<X:3,Y:6,Z:7>>.
atom_to_list([hello]).
"hello"
[2*X||X<-[1,2,3]]
fileName .hrl包含的文件。
-include("lerne/file.hrl").
短路布尔表达式:
Expr1 orelse Expr2 %%Expr1为true的时候,不会对Expr2求值, 只有当Expr1为false的时候,才会对Expr2求值。
Expr1 andalso Expr2 %%Expr1为true的时候,Expr2必须被求值, 只有当Expr1为false的时候,才不会对Expr2求值。
打开命令行控制窗口,先进到test.erl的位置,然后执行erl工具,详细见截图:
2.记得执行的时候要末尾要加上一个局点".",代表执行。
如果你想退出Erlang终端窗口,那么请输入q()并加上句点
3.基元由小写字母开始或者是由单引号界定。当基元用小写字母开始的时候,字母、数字、"at"符号(@).和下划线(_)都是有效的字符,如果一个基元通过单引号
封装起来,则可以使用任意字符。 (注:大写字母不能当做基元来操作)
基元(atom)来表示文字常量。Erlang中的基元和其他语言中的枚举作用是一样的。
6> '1
6> 2
6> 3
6> 4'.
'1\n2\n3\n4'
上面的列子说明 单引号里面的字符串可以换行来执行
4.rlang中有各种各样的内置函数,通常在Erlang社区中称为BIF。
它们可以应用在程序和终端中。其中内置函数is_boolean用来测试一个Erlang值是否是布尔类型。
is_boolean(9+6).
5.布尔类型的逻辑运算符
((1>0) and (2>1)).
6.元组是用来保存一组数据元素的复合数据类型,其中数据元素要求是Erlang数据类型,但并不一定要求是相同的类型。
元组使用封闭的花括号{...}来表示,而其中的元素由逗号隔开。
Erlang提供了一些内置函数用来设置和检索元组元素的内容,并得到元组的大小。
当一个元组的第一个元素是一个基元时,称它为标记(tag)。
这一Erlang惯例用来表示不同类型的数据,同时通常在使用它的程序中有着特殊的意义。
例如,在元组{person, 'Joe', 'Armstrong'}中,
基元person是标记,这有可能也就表明第二个元素是这个人的名,然后第三个元素是这个人的姓。
1> tuple_size({abc, {def, 123}, ghi}).
3
2> element(2,{abc, {def, 123}, ghi}).
{def,123}
3> setelement(2,{abc, {def, 123}, ghi},def).
{abc,def,ghi}
4> {1,2}<{1,3}.
true
5> {2,3}<{2,3}.
false
6> {1,2}=={2,3}.
false
从第二条可以看出,下标是从1开始,而不是0
(找一份Erlang的api)
7.列表和元组都是用来存储元素集合的,在这两种情况中,它们的元素都可以是不同的。
列表用封闭的括号[...]来定义,而它们的元素由逗号隔开。
[january, february, march]
[123,def, abc]
[a,[b,[c,d,e],f],g]
[]
[{person,'Joe','Armstrong'},{person,'Robert','Virding'},
{person, 'Mike', 'Williams'}]
[72,101,108,108,111,32,87,111,114,108,100]
[$H,$e,$l,$l,$o,$,$W,$o,$r,$l,$d]
"Hello World"
字符和字符串:
字符(a,b,c类似形式) 字符串("abc","bbbn"类型形式)
字符由整数来表示,字符串(由字符组成)则由整数列表来表示。字符的整数值可以通过在字母前加上$符号来得到:
1> $A.
65
2> $A + 32.
97
3> $a.
97
Erlang中没有字符串数据类型。在Erlang中,字符串是一个由ASCII值组成的整数列表,并使用双引号("")来表示。
因此,字符串"Hello World"实际上就是列表[72,101,108,108,111,32,87,111,114,108,100] 。
如果你使用ASCII整数符号$Character来表示整数,那么你就会得到这样的列表[$H,$e,$l,$l,$o,$ ,$W,$o,$r,$l,$d]。
空字符串""则等价于空列表[]:
基元和字符串的区别
基元和字符串的区别在哪里?首先它们处理的方式不同,唯一可用于基元的操作是比较操作,而你可以使用很多不同的方式处理字符串。
例如,可以把字符串"Hello World"分解成["Hello","World"]的列表,而基元'Hello World'就不能这样处理。
双引号表示字符串。单引号表示基元
列表的组成和处理
正如我们前面所说,列表和元组的处理方式是非常不同的。元组的处理只能是提取的具体元素,而列表只要不为空,就可以把一个列表分成头部和尾部。
列表的头部是指列表的第一个元素,它的尾部是一个包含所有剩余元素的一个列表,而这个列表可以继续这样分解下去,如图2-1所示。
列表的头可以是任何东西,但是列表的尾通常还是一个列表。
正如列表可以如此这样分割下去一样,我们也可以用一个列表和一个元素组成一个新的列表。新的列表可以这样构造 —— [头部|尾部],这是一个典型的构造器(constructor,简写为cons)。
关于列表的很多操作都在lists库模块中定义,下面我们可以看到其中的一些例子。
这些函数不是内置函数,它们通过在函数前加上模块的名字来调用,并用冒号隔开,例如 lists:split。
列表函数和操作:
1> lists:max([1,2,3]).
3
2> lists:reverse([1,2,3]).
[3,2,1]
3> lists:sort([2,1,3]).
[1,2,3]
4> lists:split(2,[3,4,10,7,9]).
{[3,4],[10,7,9]}
5> lists:sum([3,4,10,7,9]).
33
6> lists:zip([1,2,3],[5,6,7]).
[{1,5},{2,6},{3,7}]
7> lists:delete(2,[1,2,3,2,4,2]).
[1,3,2,4,2]
8> lists:last([1,2,3]).
3
9> lists:member(5,[1,24]).
false
10> lists:member(24,[1,24]).
true
11> lists:nth(2,[3,4,10,7,9]).
4
12> lists:length([1,2,3]).
** exception error: undefined function lists:length/1
13> length([1,2,3]).
3
如果你想在一个列表前添加一个元素,那么有两种方法:
直接使用构造器,例如[1|[2,3,4]]。
使用++运算符,例如[1] + + [2,3,4]。
这两种方法产生同样的结果,但++运算符效率更低,并可能导致程序运行时速度大幅度减慢。
因此当你想添加一个元素到一个列表头部的时候,应该尽量使用高效率的构造器方法([...|...])。
当++运算符加入到Erlang语言之后,程序员就从使用append函数转变为滥用++运算符。
其实++运算符和append函数都是代价很高的操作,因为表达式的左边的列表都要遍历一次。
它们不仅仅是耗时的操作,而且常常是多余的,比如在Erlang中所有I/O函数(包括套接字操作)都接受未展开的字符串,比如["Hello ",["Concurrent "]|"World"]。
列表:
List = [Element|List] or []
[[3,2]|1]
[1|[2,3]]==[1,2,3] true [1,2|3]==[1,2,3] false
[1|[2|[3]] 应该少了一个]号确实少了个 正确的应该是 [1|[2|[3]]]
[1|[2|[3|[]]]]
[1,2|[3|[]]]
[3]==[3|[]]. true
[3]==[[]|[3]]. false
[3|[]]==[3]. true
[[]|[3]]==[3]. false
8.项元比较.
布尔类型:
==等于 /==不等于
=:= 精确等于 =/=精确不等于
>= 大于等于 > 大于 <= <
number < atom < reference < fun < port < pid < tuple < list < binary
列表按字典顺序排列,就像是字典里面的词一样。第一个元素先比较,小的那个表明这个列表也较小:如果它们是相同的,则第二个元素将继续进行比较,如此下去。当其中一个列表先比较完了,那么它就是较小的列表。因此:
5> [boo,hoo]<[adder,zebra,bee].
false
6> [boo,hoo]<[boo,hoo,adder,zebra,bee].
true
而另一方面,元组比较时,先会比较结构中的元素数目,然后再一个个比较各个元素的值:
7> {boo,hoo}<{adder,zebra,bee}.
true
8> {boo,hoo}<{boo,hoo,adder,zebra,bee}.
true
9.变量
变量用来存储简单和复合数据类型的值。在Erlang中,变量必须以大写字母开头(注2),后面的字符可以是大小写字母、整数和下划线。
它们不能包含任何其他的“特殊”字符。下面是变量的一些例子:
A_long_variable_name Flag Name2 DbgFlag
Erlang中的变量不同于大多数的传统编程语言。
在一个变量的生命周期内,包括在Erlang终端处理过程中,只容许给变量赋值一次,你就不能改变它了,这称为单次赋值。
因此,当你需要对一个变量的值进行计算和操作的时候,可以把结果存储在另外一个新的变量当中。
Erlang中变量的另一个有用的特性是我们不需要声明它们,只需要使用它们即可。
rlang的一个优点是,它不需要明确地分配和释放内存。对于C程序员来说,这意味着不会再有不眠之夜来查找指针错误或者内存泄漏。
用来存储复杂数据结构的内存在需要的时候会由系统自动分配,而当不再引用它的时候,就会由垃圾收集器自动回收释放掉。
Erlang内存管理:
现有的Erlang虚拟机的实现使用了通用继承性垃圾收集器。
垃圾收集针对每个并发进程各自独立进行:当一个处理器没有更多的内存用于存储的时候,就会自动触发垃圾收集器。
10.匹配模式: (重点测试下这种模式 测试 xs=[[]|XS])
模式匹配[A,B,C,D] = [1,2,3]则会失败。虽然它们两个都是列表类型,但是左边的列表有4个元素,而右边一个列表只有3个元素。
一个常见的误解是D可以设置为空列表,然后模式匹配应该成功。在这个例子中这是不可能的,因为C和D之间由逗号而不是构造器操作符分隔开。
[A,B,C|D]=[1,2,3]的模式匹配就会成功,变量A、B和C分别绑定到整数1、2和3,变量D则绑定到尾部,在这里就是空列表。
{A, _, [B|_], {B}} = {abc, 23, [22, 23], {22}}
会成功地提取元组的第一个元素,即基元abc,并把它绑定到变量A上。同时也将成功提取第三个元素元组的第一个元素并把它绑定到变量B上。
14> Var = {person, "Francesco", "Cesarini"}.
{person, "Francesco", "Cesarini"}
15> {person, Name, Surname} = Var.
{person, "Francesco", "Cesarini"}
在第一个语句中我们把一个类型为person的元组绑定到变量Var上了,然后在第二个语句中我们分别提取了名字和姓氏。
这将会成功地把字符串“Francesco”绑定到变量Name上并把字符串“Cesarini”绑定到变量Surname上。
11> {Element, Element, _} = {1,1,2}.
{1,1,2}
就如使用变量一样,在一个模式中我们也可以使用通配符,_,。这将匹配任何东西,并且不产生任何绑定。
我们刚才看到了变量可以用下划线开始,下划线是一个特殊的标记,它告诉编译器,这些变量是无关紧要的,它们只是作为程序中不需要的变量的占位符。
“无关紧要的”变量的功能跟普通变量一样,可以检查、使用和比较它们的值。唯一不同的是,普通变量的值如果从未使用过,编译器将会发出警告,而使用“无关紧要的”变量则不会。使用“无关紧要的”变量是一种很好的编程实践,这告诉阅读代码的人应该忽略掉这个值。
为了提高可读性和维护性,程序员经常以“无关紧要的”变量的形式引入值和类型。
下划线本身也是个“无关紧要的”变量,但不能访问其内容:因为它的值被忽略了而且从未绑定。
11.函数
Erlang程序包含相互调用的函数。函数组合在一起并在模块中定义。函数的名称是一个基元。
一个函数的头包括名字,随后是一对括号,在里面包含多个形式的参数或者没有参数。
在Erlang中,函数参数的数量叫做元数。使用箭头(->)来分隔函数头和函数主体。
在这两种操作系统下,你可以在Erlang终端中使用cd(Directory)进入到各个目录。
一旦进入该目录,你就可以在Erlang的终端中使用c(Module)并省略名称中的.erl后缀来进行编译。
如果代码中没有错误,编译就会成功。
factorial(0) -> 1;
factorial(N) ->
N * factorial(N-1).
正如我们前面提到过的,模式匹配发生在函数的头部,如何匹配成功,就会绑定变量N的一个实例。对每个语句来讲变量都是本地的,它们不需要分配或释放,Erlang运行时系统会自动处理这些。(方法定义的时候,前面先用;来表示,最后一个方法用.来表示)
12.模块:
函数组合在一起构成了模块。一个程序往往是分散在几个模块当中,每个模块包括按逻辑组合在一起的函数。
模块文件以.erl后缀来结尾,文件名称和模块名称必须是相同的。
若要执行从一个模块中导出的函数,你必须编译代码,这会在与模块相同的目录下面生成一个module.beam文件:
模块可以这样直接命名–module(Name),因此在例2-2中,demo模块将存储在一个名为demo.erl的文件中。
-module(demo).
-export([double/1]).
% This is a comment.
% Everything on a line after % is ignored.
double(Value) ->
times(Value, 2).
times(X, Y) ->
X*Y.
export指令以Function/Arity的格式(方法名/参数个数),包含了导出函数的一个列表。这些函数是全局性的,这意味着可以从模块的外部调用它们。
在Erlang中,注释以百分号(%)开始直到该行结束。请务必在代码中多多使用它们!
在Windows环境下,一种打开werl终端的方法是右击一个.beam文件,然后从弹出的窗口菜单中选择Open With option的“werl”选项。从现在起,双击任何源文件相同目录下的.beam文件,就会打开一个Erlang终端了。
在这两种操作系统下,你可以在Erlang终端中使用cd(Directory)进入到各个目录。一旦进入该目录,你就可以在Erlang的终端中使用c(Module)并省略名称中的.erl后缀来进行编译。如果代码中没有错误,编译就会成功。
1> cd("/home/francesco/examples").
/home/francesco/examples
ok
2> c(demo).
{ok,demo}
3> demo:double(10).
20
4> demo:times(1,2).
** exception error: undefined function demo:times/2
13.从复合数据类型中提取值
Person = {person, "Mike", "Williams", [1,2,3,4]}.
{person, Name, Surname, Phone} = Person.
Name.
小写字母开头表示基元,大写字母开头表示变量
14.case
防御性编程
假设程序把一个整数映射到一个表示一周中某天的基元。使用catch-all语句的防错性编程如下所示。
convert(Day) ->
case Day of
monday -> 1;
tuesday -> 2;
wednesday -> 3;
thursday -> 4;
friday -> 5;
saturday -> 6;
sunday -> 7;
Other -> {error, unknown_day}
end.
listlen(Y) ->
case Y of
[] -> 0;
[_|Xs] -> 1 + listlen(Xs)
end.
if case X rem 2 of
X rem 2 == 1 -> odd; 1 -> odd;
X rem 2 == 0 -> even 0 -> even
end end
15.保护元
保护元(guard)是一个额外的限制条件,它应用于函数的case或者receive语句中(我们会在第4章中具体讲述receive表达式)。
保护元应该放在“->”之前来分隔语句的主体和头部。
保护元由when关键字和紧跟其后的一个保护元表达式组成。只有在模式匹配和保护元表达式求值结果为基元true的情况下,这个语句才会执行。
max(X,Y) when X>Y- > X;
max(X,Y) - > Y.
接下来重写第2章中阶乘的例子:
factorial(0) -> 1;
factorial(N) ->
N * factorial(N-1).
这一次使用保护元:
factorial(N) when N > 0 ->
N * factorial(N - 1);
factorial(0) -> 1.
my_add(X,Y) when not(((X>Y) or not(is_atom(X))
) and (is_atom(Y) or (X==3.4))) ->
X+Y.
新的保护元函数是is_atom/1和is_integer/1等。
元编程:
一个函数在运行时才确定将调用哪些函数的特性叫做元编程,也就是程序创建程序并运行。
16.内置函数:
要从标准输入读取一行,请使用io:get_line/1,它需要提示符字符串(或基元)作为它的输入:
1> io:get_line("gissa line>").
gissa line>lkdsjfljasdkjflkajsdf.
"lkdsjfljasdkjflkajsdf.\n"
也可以这样读取指定数量的字符:
2> io:get_chars("tell me> ",2).
tell me> er
"er"
17.
keysearch(Key, N, TupleList) -> {value, Tuple} | false
这个函数和keyfind差不多,就是返回值的结构不一样
也是在TupleList中找一个Tuple,这个Tuple的第N个元素和Key一样。
例子:
List1 = [{name,"zhangjing"},{name,"zhangsan"}]
lists:keysearch("zhangjing",2,List1).
结果:
{value,{name,"zhangjing"}}
lists:keysearch(1,1,[{1,2},{2,4}]).
第一个元素的值和key值一样。
18.匹配模式,函数匹配模式
Page40 模块是Erlang中代码的基本单元,我们编写的所有函数都存于模块之中。模块文件通常以.erl为扩展名的文件中。
要运行一个模块,首先需要编译它,编译成功之后的模块文件其扩展名为.beam
模块名:方法名(). 来调用模块里面的方法
小写字母是基元,大写字母是变量
函数cost/1 :符号Name/N表示一个带有N个参数名为Name的函数,N称为函数的运算目。
函数cost/1必须从模块之中导出,如果你想从模块的外部调用它,这是必须的。即用-compile(export_all)来导出模块之中的所有函数
[x]就是[x|[]]的缩写
19.fun
P47。fun就是匿名函数,先定义一个Z,然后将fun赋值给一个变量Z (基元是小写字母开头的,就当做是一个常量,变量大写字母开头的,相当于一个变量)
1>Z=fun(X)-> 2*X end.
2>Z(2)
4
fun匹配多个函数 p48
20. 逗号(,)用来分隔函数调用、数据构造器以及模式中的参数
分号(;)用来分隔子句,在这几种情况相下都会用到子句:分段的函数定义、case语句、if语句、try....catch语句以及receive表达式。
无论何时,我们只看到一组后面跟有表达式的模式,都会使用分号进行分隔。
div 整数除法
rem 整数取余
21.返回fun的fun的值的时候,
Double=fun(x)->(2*x)end.
Double(5).
10
%定义两层fun
Mult= fun(Time)->(fun(X)->(Time*X)) end.
调用它的时候, Trip=Mult(5). Time的值为5
Trip(3). X的值为3
(1.)Mult=fun(time,A,B)->A*B end.
调用 Mult(time,2,3) 返回6
(2.)Mult(A,B)-> A*B end.
调用 Mult(3,4) 返回12
求和:
sum(H|T)-> H+sum(T);
sum([])->0.
将此文件储存为mylists.erl
c(mylists.erl). %%先将此模块编译
然后再调用
L=[1,2,3]
mylists.sum(L).
6
map(_,[]) ->[];
map(F,[H|T]) ->[F(H)|map(F,T)]. %[F[H]|F[T]|[]] 将依次的列表元素当做参数传进方法里面去 (F变量代表的是一个方法名)
L=[1,2,3,4]
mylists.map(fun(x)->2*x end,L). %% fun(1)+fun(2)+fun(3)+fun(4)
解析:
[2,4,6,8]
注:不能将自己的模块定义成lists ,因为有系统的模块名为这个名字
模块 -import(lists,[map/2,sum/1]).
如果有导入声明的时候,不需要写lists:map(...),可以直接使用map(....), 如果没有导入的函数,记得要加上模块名才能使用
23.
列表解析: (列表解析很有作用)
L=[1,2,3,4]
[2*X||X<-L]. %[F(X)||X<-L]代表"由F(X)组成的列表,其中X是取值于列表L"
使用列表解析实现过滤功能:
[X||{a,X}<-{a,1},{1,mn},{a,'m'}]
[1,'m']
使map方法更简洁:
map(F,L)->[F(X)||X<-L]. %<-不是方法体,而是说X的值是从L里面取的
列表解析很强大!!
list.seq(1,N)返回一个由1到N整数组成的列表,所以A<-lists:seq(1,N)意味着A的取值范围是1到N的所有整数。
max(X,Y) ->
case X>Y of
true->X;
false->Y;
end.
max(X,Y) ->
if X>Y
true - >X;
false ->Y;
end.
运用保护元:
max(X,Y) when X>Y -> X;
max(X,Y) ->Y.
什么情况下使用end结尾??
断言序列 ==保护元 Page58
24.记录
Page60.
-record(Name,{key1=Default1,
key2=Default2,
.....
}).
定义record
-record(todo,{status=remider,who=joe,text}
})
创建和更新记录
X=#todo{}.
X1=#todo{status=urgent,text='Fix errata in book'}.
X2=X1#todo{status=done}.
#用于创建类型为todo的新记录,key都是原子。
X2=X1#todo{status=done}. 这个表示创建了一个X1(X1必须为todo类型的)的副本,并将value的值改为了done,原记录本身并未改变
记录的匹配模式:
#todo{who=W,text=Txt} =X2.
W.
joe
Txt.
Fix errata in book
%%匹配操作符的左边,我们使用了自由变量W和Txt来定义一个记录模式
如果只是想提取某个记录之中的对应的值,X2#todo.text来取值
-module(shop).
-export(cost).
cost(oranges) ->5;
cost(apple) ->8;
cost(milk) ->16.
-module(shop1).
-export([total/1]).
total([{What,N}|T]) -> shop:cost(What)*N+total(T);
total([]) -> 0.
尝试在Erlang终端输入xd ,进入到相应的目录
在这两种操作系统下,你可以在Erlang终端中使用cd(Directory)进入到各个目录。一旦进入该目录,你就可以在Erlang的终端中使用c(Module)并省略名称中的.erl后缀来进行编译。如果代码中没有错误,编译就会成功。
-module(demo).
-export([double/1]).
% This is a comment.
% Everything on a line after % is ignored.
double(Value) ->
times(Value, 2).
times(X, Y) ->
X*Y.
1> cd("/home/francesco/examples").
/home/francesco/examples
ok
2> c(demo).
{ok,demo}
3> demo:double(10).
20
4> demo:times(1,2).
** exception error: undefined function demo:times/2
25.顺序型编程进阶
二进制数据:在Erlang中可以使用一种二进制数据结构来存储大量的原始数据。相对于列表或者元组,二进制类型更加节省内存,而且运行时系统也对此进行了优化。
在书写和打印时,二进制数据以一个整数或者字符序列的形式了来出现,两端分别用两个小于号和两个大于号括起来,例如:
<<5,10,20>>.
<<5,10,20>>
<<"hello">>.
<<"hello">>
列表构造:[1,2,[3,4,5],6|[8]] (记得测试下)
比特语法:
M=<<X:3,Y:6,Z:7>>.
atom_to_list([hello]).
"hello"
[2*X||X<-[1,2,3]]
fileName .hrl包含的文件。
-include("lerne/file.hrl").
短路布尔表达式:
Expr1 orelse Expr2 %%Expr1为true的时候,不会对Expr2求值, 只有当Expr1为false的时候,才会对Expr2求值。
Expr1 andalso Expr2 %%Expr1为true的时候,Expr2必须被求值, 只有当Expr1为false的时候,才不会对Expr2求值。