Erlang开发笔记之顺序型编程

开始之前先记录几个erlang的shell命令:

Ctrl+G 进入命令模式,输入q再按回车则退出shell命令行

pwd() 打印当前工作目录

cd(dir) 切换到某个目录

1、模块和函数

模块是erlang中代码的基本单元,一般一个文件就是一个模块,一个模块用于实现一个特定的功能。模块要在文件的第一行进行声明

-module(模块名).

函数名和原子一样,以小写字母开头,函数的定义如下

functionName(arg1,arg2,...,argn) -> setence1,sentence2.

箭头的意思也是模式匹配,用在函数定义,case,if等地方。erlang的函数也有重载,如果函数有多个定义,前面的定义以分号结束,最后一个定义才以句号结束表示函数定义结束。要特别注意的是,函数重载指的是函数名一样,参数个数也一样,只是参数的名称不完全一样,如果函数名一样,参数个数不一样,那这两个函数属于完成没关系的两个函数,各自都要以句号结束,这个与C++很不一样。例如这样

functionName(Arg,Arg2) -> setence1;

functionName(Arg,Arg3) -> setence2.

functionName(Arg,Arg,Arg) -> sentence3.

在一个模块内定义的函数作用域仅限于这个模块,如果其它模块想要使用这个函数,必须把这个函数导出。在文件的开始处,使用下面命令导出函数

-export([函数名/参数个数,函数名/参数个数]).

一个模块定义完之后,要想使用这个模块的函数,仅将函数导出还不行,首先得编译这个模块。在shell中输入下面命令进行编译,erlang的源文件后缀是.erl,编译后会生成.beam文件。

下面用一个实例来说明,创建一个几何模块geometry,模块里面定义了一个函数area用于求长方形、正方形、圆形和三角形的面积,使用重载的方式只需要定义area一个函数就够了。

-module(geometry).
-export([area/2]).

%求圆的面积
area(circle,T) -> 3.14*T*T;

%求三角形面积
area(triangle,{A,B,C}) ->
    P=(A+B+C)/2,
    math:sqrt(P*(P-A)*(P-B)*(P-C));

%求正方形面积
area(square,X) -> X*X;

%求长方形面积
area(square,{W,H}) -> W*H.

第1行定义模块geometry;

第2行把函数area导出,该函数需要2个参数,如果area的定义中有非两个参数的情况,导出函数也要增加,因为上面提到了参数个数不一样是两个完全没关系的函数。比如有一个分支是area(square,W,H),那导出列表应该是-export([area/2,area/3]).

下面是函数area的定义,共有四个重载分支,注意的是前面三个重载分支都是以分号结束的,表示函数的定义还没完成,最后的分支才是以句号结束,表示函数定义完成。函数体部分,语句和语句之前用逗号隔开。

2、匿名函数

可以使用关键字fun来定义一个匿名函数,定义方式如下

fun(参数列表) -> 函数体.

格式和普通函数一样,只是用fun来代替函数名而已。

匿名函数可与变量作模式匹配,匹配成功后就可以使用变量来调用函数了

X=fun(Arg1,Arg2) -> setence.

3、高阶函数

以fun以参数或返回值的函数就是高阶函数,高阶函数能完成很多功能,标准库里面的lists模块的很多函数就是高阶函数,比如过滤函数filter的原型如下

lists:filter(Fun,[L1,L2,L3,L4])

该函数会拿列表里面的每一个元素来运行函数Fun,将满足条件 元素保留下来,最后生成新的列表。我们可以手动来实现这个功能

fliter(Fun,L) ->
	filter_sub(Fun,L,[]).

filter_sub(Fun,[H|T],R) ->
	case Fun(H) of
	true -> filter_sub(Fun,T,[H|R]);
	_ -> filter_sub(Fun,T,R)
	end;

filter_sub(_Fun,[],R) -> lists:reverse(R).

然后再定义一个判断偶数的匿名函数,然后把这个函数匹配的变量传入到filter中

31> Even=fun(X) -> (X rem 2) =:= 0 end.
#Fun<erl_eval.6.80484245>
32> test:filter(Even,[1,2,3,4,5,6,7,8]).
[2,4,6,8]

4、列表解析

列表解析类似上面的filter函数,都是对列表里的每个元素进行操作,只不过列表解析不需要定义函数,格式更加简洁。列表解析格式如下

[sentence || X<-L]

其中L是一个列表

比如上面的过滤偶数可这样实现

36> [case X rem 2 =:= 0 of         
36> true -> X;
36> false -> null
36> end || X <- [1,2,3,4,5,6,7,8]].
[null,2,null,4,null,6,null,8]

这里有个问题,我们看到返回的并不是[2,4,6,8],而是多了几个null。这是因为列表解析会把列表的每个元素进行操作会返回,因此它的返回值还是跟原来大小的列表。列表解析有局限性,并不是可以完成所有跟列表相关的操作,如果只是想对列表的所有元素进行操作,那么使用列表解析实现起来会非常简单。如果是想对列表的元素进行增删,那可以还得结合函数来实现。

5、断言

断言是一种强化模式匹配的结构,使用断言可以拓展函数重载,如果我们想给函数定义两个完全一样的分支是不允许的,但有时我们又想这么做,这两个分支分别用于处理不同情况下的逻辑,这里就可以使用断言了,我们在一个分支加上断言,另一个不加,这样就使这两个分支变成重载的了。

到目前为止,我们并没有接触到if,switch等分支结构,条件判断主要依赖于模式匹配,还有就是函数重载了,使用断言可以更好地做条件判断。

断言的格式是

when assert

断言加到函数头后面,当然除了函数之外,还有很多地方也会用到断言,后面再讲。

判断两个数之间的最大数

max(A,B) when A>B -> A;
max(A,B) -> B.

断言表达式除了原子(除true之外的所有原子都会被当成false)之外,还有很多断言函数可用

is_atom(X) 判断X是否是原子

is_binary(X) 判断X是否是二进制数据

is_constant(X) 判断X是否是常数

is_function(X) 判断X是否是函数

....

abs(X)

float(X) 转成浮点数

element(X,N) 取元组X的第N个元素

hd(X) 取列表X的头部

tl(X) 取列表的尾部

length(X) 取列表的长度

另外,断言可以多个一起使用,用逗号隔开表示与的关系,比如

is_tuple(X),size(X)>6 

等价于

is_tuple(X) and size(X)>6

等价于

is_tuple(X) andalso size(X)>6

用分别隔开表示或的关系,比如

is_integer(X);is_float(X)

等价于

is_integer(X) or is_float(X)

等价于

is_integer(X) orelse is_float(X)

and和or是非短路的,andalso和orelse是短路的。

6、记录

记录是元组的拓展,其底层也是一个元组。元组只是把几个字段封装到一起,每个字段并没有名字,所以元组更像一个数组。记录可以给每个字段一个名字,更加一个字典。

记录通常存放在.hrl文件中,用于保存数据,其定义格式如下

-record(recordName,{var1=value1,var2=value2}).

使用记录时要先将记录读进来模块中来,就像使用其它模块之前要编译该模块一样

rr("recordName.hrl").

6.1、创建记录

X=#recordName{}.

这是最简单的创建方式,即所有字段都没有赋值,这时所有字段的值都是记录定义时的默认值,如果没有默认值,那它的值就是原子undefined。

可以在创建时给某些字段赋值

X=#recordName{var1=1,var4=4}.

也可以使用另一个记录来创建记录

X2=X#recordName{var4=5}.

这时X2的值是#recordeName{var1=1,var2=undefined,var3=undefined,var4=5}

6.2、提取记录的字段

提取字段同列表和元组一样,都是使用模式匹配,表达式左边指定要提取的字段名,字段值用自由变量表示,对于不想提取的字段忽略即可

实例

定义一条记录保存到records.hrl

-record(todo,{status=reminder,who=lin,text}).

创建记录和操作记录

42> rr("records.hrl").
[todo]
43> R1=#todo{}.
#todo{status = reminder,who = lin,text = undefined}
44> R2=#todo{status=wait,who=yin}.
#todo{status = wait,who = yin,text = undefined}
45> R3=R2#todo{status=done,text=good}.
#todo{status = done,who = yin,text = good}
46> #todo{status=S}=R3.
#todo{status = done,who = yin,text = good}
47> S.
done

7、条件分支

上面提到了erlang没有if,switch这些条件语句,条件判断主要都是靠模式匹配。但是erlang还有case/if两个表达式来拓展模式匹配

case的语法

case Expression of

Pattern1 [when Guard1] -> Expr_seq1;

Pattern2 [when Guard2] -> Expr_seq2

end

if的语法

if

Guard1 -> Expr_seq1;

Guard2 -> Expr_seq2

end

为了保证严谨性,一般case的最后一个分支使用_,if的最后一个分支使用true,用于匹配任意其它情况,类似C语言的switch中的default。

这个表达式其它对应的就是C语言中的if和switch。

grade(A) when is_integer(A)	->
		if A>=100 -> "very good";
			A>=80 -> "good";
			A>=60 -> "not bad";
			true -> "bad"
		end.

even(A) ->
	case A rem 2 of
		0 -> true;
		1 -> false;
		_ -> false
	end.


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值