第三章 顺序型编程
1.
一个函数以一些以分号隔开的子句组成,最后一条子句以句号结束,表示函数结束。每个子句都有一个函数头和函数体。函数头由函数名和随后的以括号括起来的模
式组成,函数体则由一系列表达式(表达式以逗号分隔)组成。调用时,如果函数头中的模式与调用参数匹配成功的话,其对应的表达式就会进行运算。模式将按照
它们在函数定义中的先后顺序进行匹配,匹配完一个子句后,不再匹配下一下了。
2. 模块可以在shell中用c(模块名)的方式进行编译,调用模块中的函数的方法是:模块名:函数(参数)。
3. 当对函数的调用不能匹配时,会抛出一个运行时错误。
4. 切换当前目录的方法:
(1)在erlang的shell中:cd("相对目录名或绝对目录名"). pwd()会返回当前目录。
(2)在erlang的安装目录下写一个名为.erlang的文件,erl的shell在启动时会前执行这个文件。文件中的内容如下:
io:format("consulting .erlang in ~p~n", [element(2, file:get_cwd())]). %% element(2, 元组)取元组的第2个元素。file:get_cwd() 获取当前目录名
c:cd("c:/work"). %% c:的意思应该是在shell中执行一个命令。
io:format("Now in:~p~n", [element(2, file:get_cwd())]). %% 显示切换后的当前目录。
5. erlang中的标点符号:
逗号(,)用来分隔函数调用、数据构造器以及模式中的参数。
句号(.)(后跟一个空白符号,否则可能是浮点数的小数点)用来在shell中分隔完整的函数和表达式。
分号(;)用来分隔子句,用到子句的情况:分段的函数定义、case语句、if语句、try...catch语句以及receive表达式。
无论何时,我们只要看到一组后面跟有表达式的模式,都会使用分号进行分隔。
6. 函数的目就是它所拥有的参数的数量。同名不同目的函数是两个完全不同的函数,除了它们的名字恰巧相同外。同名不同目的函数一般可以用做辅助函数。
7. fun是匿名函数。Z = fun(X) -> 2*X end. 这里Z相当于这个匿名函数的指针。这个指针可以付给另外一个变量,如:Double = Z. 调用这个函数可以Double(4).。
8. fun与正常的函数一样,可以有多个参数,也可以有多个子句。只是要在最后加一个end.。多个子句的fun函数只在第一个子名处写fun,以下的子句的fun省略,最后一个子句不写分号(;),如:
fun(X,Y) -> X*Y;
(a,Y) -> a
end.
9. fun函数可以做为参数传递给其它的函数(当然也包括fun函数本身),也可以做为函数的返回值。使用fun函数做为参数或返回fun函数的函数称为高阶函数。
10. lists:map(F, L) 返回一个列表,将列表L的每个元素应用函数F。lists:filter(P,L)返回一个列表,列表中的元素是列表L中满足P(l)为true的元素的集合。lists:member(X, L)判断X是否在列表L中。
11. 返回fun的例子:
Fruit = [apple, pear, orange].
MakeTest = fun(L) -> (fun(X) -> lists:member(X, L) end ) end. %%注意前一个end后面不有句号。
IsFruit = MakeTest(Fruit).
IsFruit(pear). %%返回true
IsFruit(dog). %%返回false
lists:filter(IsFruit, [dog, pear, apple, click]). %% 返回[pear, apple]
12. 返回fun的函数有点像c++中的函数模范,用它可以构造出很多个版本的函数来。
13. 自定义的for循环结构:
for(Max, Max, F) -> [F(Max)];
for(I, Max, F) -> [F(I) | for(I+1, Max)].
14. -import(lists, [map/2, sum/1]. 导入lists模块中的map/2和sum/1函数,这样再应用时可以直接写map(...)和sum(...)了。
15. -export([total/1]). 将本模块中的total/1函数导出,这样total可以在本模块外部被调用。
16. 列表解析: [F(X) || X <- L]。例子:
[ 2*X || X <- L] %% 将列表L的元素*2,生成新的列表。
[{Name, 2*Number} || {Name, Number} <- Buy]
[shop:cost(A)*B || {A, B} <- Buy]
map(F, L) -> [F(X) || X <- L].
17. 列表解析的常见形式:[X || Qualifier1, Qualifier2, ...]
X是一个任意的表达式,每一个限定词可以是一个生成器,也可以是一个过滤器。
生成器: Pattern <- ListExpr, ListExpr必须是一个对列表项求值的表达式。
过滤器: 可以是一个返回true或false的函数,也可以是一个布尔表达式。
其实,生成器的Pattern也可以起到过滤器的作用,如:
[X || {a, X} <- [{a, 1}, {b, 2}, {c, 3}, {a, 4}, hello, "wow"]]. %% 返回值:[1,4]
相当于[X || {A, X} <- [{a, 1}, {b, 2}, {c, 3}, {a, 4}, hello, "wow"], A=:=a].
map18. A++B,是将列表B附加到列表A上生成一个新的列表,但效率不高。
A--B,是从列表A中删除与B中元素相同的所有元素,如果元素X在B中出现K次,则会从A中依次删除K个元素X。
19. lists:seq(M,N) 返回一个从M到N的整数组成的列表,包含M和N在内。 seq应该是sequence的缩写。
20. 算术表达式
1 +X
1 -X
2 X*Y
2 X/Y
2 bnot X 对X按位取反
2 X div Y X整除Y
2 X rem Y X除Y取余数
2 X band Y 对X和Y按位取与
3 X+Y
3 X-Y
3 X bor Y 对X和Y按位取或
3 X bxor Y 对X和Y按位进行异或
3 X bsl N 对X按位左移N位
3 X bsr N 对X按位右移N位
21. 断言(guard)是一种用于强化模式匹配功能的结构。
22. 在函数定义的头部使用断言时,必须以关键字when开头。如:
max(X, Y) when X > Y -> X;
man(X, Y) -> Y.
23. 可以在任何允许使用表达式的地方使用断言,当断言用于表达式时,它要么返回原子true(认为是求值成功),要么返回原子false(求值失败)。
24. 断言序列:
一组用逗号分隔的断言表达式表示的是and关系,即所以的断言为true,整个断言序列才为true。
一组用分号分隔的断言表达式表示的是or关系。
25. 不是所有合法的erlang表达式都可以做为断言表达式,只有下面的表达式才可以:
原子true 为什么要有true断言呢?这是因为可以在if表达式中做为catchall使用,相当于C语言中的if控制中的else,或switch中的default
其他常量(条件或绑定变量), 这些在断言表达式中都会被求值为false。
表3-2中的断言谓词或表3-3中的BIF(built-in function)
比较表达式
算术表达式
布尔表达式
短路布尔表达式 oralso和andalso(有点像C中的||,&&运算,先求前一个表达式的值,如果有必要再求后一个表达式的值)
26. 断言谓词:
is_atom(X)
is_binary(X)
is_constant(X)
is_float(X)
is_function(X)
is_function(X, N)
is_integer(X)
is_list(X)
is_number(X)
is_pid(X)
is_port(X)
is_reference(X)
is_tuple(X)
is_record(X, Tag)
is_record(X, Tag, N)
27. 断言BIF
abs(X) X的绝对值
element(N, X) 元组X的第N个元素
float(X) 将数字N转换为浮点数
hd(X) 列表X的头部
length(X) 列表X的长度
node() 当前节点
node(X) 进程X的节点
round(X) 将数字X转换为整数(四舍五入)
self() 当前进程的标识符
size(X) X的大小,X为元组或二进制数据
trunc(X) 将数字X转换为整数(截取)
tl(X) 列表X的尾部
28. 记录:将一个名称与元组中的元素一一对应起来的方法。
29. 记录的定义:
-record(Name, {key1=Default1, key2=Default2, key3, ...}). %% 其中Name是记录的名字,Default1和Default2分别是key1和key2的默认值。
记录的名字和键名必须是原子。
30. 记录一般放在erlang源代码文件或.hrl文件中,这些文件可以被其它的erlang源代码引用。
31. 在shell中引用记录用命令rr("filename.htl"). rr是read record的缩写。
释放记录用命令rf(记录名)。
32. 创建记录:X=#todo{} 创建一个记录,其中记录的键值是默认值。没有默认值的键默认会付值为原子undefined
X1=#todo{status=urgent, text="Fix errata in book"}
33. 复制记录:X2=X1#todo{status=done}.
34. 提取记录字段的值(两种方法):
#todo{who=W, text=Txt} = X2. %% 用模式匹配的方式
X2#todo.text.
35. 记录做为函数的参数进行模式匹配:
clear_status(#todo{status=S, who=W}=R) -> %% S和W分别匹配记录的status, who,而R匹配整个记录。
R#todo{status=finished} %% 注意:这里并不是修改了R,而是生成了一个新的记录返回。新的记录复制了R,同时将status修改为finished。
如果想匹配指定类型的记录,可以用断言的方式
clear_status(#todo{status=S, who=W}=R) when is_record(R, todo) -> R#todo{status=finished}.
36. 记录被释放后,原来为记录的变量就会变是一个普通的元组了。看来记录只是一个元组的显示界面而已。
37. 如果为每件事情都定义一个独立的函数子句不方便,可以使用case或if表达式。
38. case表达式:
case Expression of
Pattern1 [when Guard1] -> Expr_seq1;
Pattern2 [when Guard2] -> Expr_seq2;
... %% 最后一个不能加分号
end
首先计算Expression的值,然后将其值与Pattern1/Pattern2...进行模式匹配,如果没有匹配上,则抛出异常。
例子, filter(P, L)
filter(P, [H|T]) - >
case P(H) of
true -> [H|filter(P, T)];
false -> filter(P, T)
end;
filter(P, []) -> [].
39. if表达式:
if
Guard1 -> Expr_seq1;
Guard2 -> Expr_seq2;
... %% 最后一个不能加分号
end
一般情况下,if表达式的最后一个断言会是原子true,否则,如果所以断言都为false,则会抛出异常。
40. 尽量在一个列表的头部进行操作,尽量避免用到List++[H]这样的代码,除非List很短。通常要以自然顺序创建列表。规则如下:
(1)总是在列表头部添加元素。
(2)从一个输入列表的头部提取元素,然后把它们加在一个输出列表的头部。输出列表中的结果与输入列表的顺序相反。
(3)如果顺序至关重要,那么调用经过高度优化的函数list:reverse/1。 (这个函数是在erlang虚拟机中实现的,经过了高度的优化)。
(4)避免违反这些原则。
41. 如何在函数之外返回多于一个的列表呢?可以返回一个由多个列表组成的元组。
42. 累加器
odds_and_evens_acc(L) -> odds_and_evens_acc(L, [], []);
odds_and_evens_acc([H|T], Odds, Evens) ->
case H rem 2 of
0 -> odds_and_evens_acc(T, [H|Odds], Evens);
1 -> odds_and_evens_acc(T, Odds, [H|Evens)
end;
odds_and_evens_acc([], Odds, Evens) -> {lists:reverse(Odds), lists:reverse(Evens)}.
《Erlang程序设计》学习笔记-第3章 顺序型编程
最新推荐文章于 2022-06-22 08:15:56 发布