《Erlang程序设计》学习笔记-第3章 顺序型编程

本文详细介绍了Erlang的顺序型编程概念,包括函数定义、模式匹配、shell交互、目录切换方法,以及标点符号的使用。讲解了函数的目、匿名函数(fun)的使用、高阶函数、列表操作如map和filter,还涉及到了自定义for循环、导入和导出函数、列表解析等特性。此外,讨论了算术表达式、断言、记录的创建和使用,以及case和if表达式在控制流程中的作用。最后提到了列表处理的最佳实践和累加器的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第三章 顺序型编程

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)}.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值