`
netcome
  • 浏览: 467898 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Erlang 编程简介,第 1 部分

 
阅读更多

什么是 Erlang?

Erlang 由 Ericsson 开发,用于帮助开发管理许多电信项目的软件。Erlang 的第一个版本发布于 1986 年,1998 年发布了它的第一个开放源码版本。可以从扩展的 Erlang 版本信息中了解到,Open Telecom Platform (OTP) 是适用于 Erlang 的应用程序开发平台,也是交付 Erlang 开发环境的主要方法。

Erlang 提供许多在其他语言中不存在或难以管理的标准特性。Erlang 中之所以存在这些功能,是因为它最初用于电信领域。

例如,Erlang 包含一个非常简单的并发模型,允许在同一主机上相对轻松地多次执行代码块。除了并发之外,Erlang 还使用一个错误模型,允许识别和处理这些进程中的错误(甚至可以用新进程处理),因此可以非常轻松地构建容错能力很强的应用程序。最后,Erlang 包含内置的分布式处理,允许在一台计算机上运行组件的同时从另一台计算机请求它们。

总之,Erlang 为构建分布式、可伸缩和高性能的离散应用程序提供了良好的环境,我们常常使用这种应用程序支持现代网络和基于 web 的应用程序。


函数编程与其他范例

Erlang 与其他流行的语言之间的主要差异是,Erlang 基本上是一种函数编程语言。函数编程与语言是否支持函数无关,而是指程序操作和组件的工作方式。

在函数编程中,按照与数学计算相似的方式设计语言的函数和操作,语言通过函数执行操作,函数接收输入并生成结果。函数编程范例 (paradigm) 意味着对于相同的输入值,代码块会产生相同的输出值。因此,预测函数或程序的输出容易得多,更容易调试和分析。

与之相对的编程范例是命令式编程语言,比如 Perl 或 Java,这类语言依赖于在执行期间应用程序状态的改变。在命令式编程语言中,状态的改变意味着:对于相同的输入值,程序的组件可以根据程序当时的状态而产生不同的结果。

函数编程方式很容易理解,但是如果您习惯了过程式和关注状态的命令式语言,可能不太容易适应它。


获得 Erlang

可以从 Erlang 网站直接获得 Erlang(参见 参考资料 )。许多 Linux 发行版的存储库中也包含它。例如,要想在 Gentoo 上安装它,可以使用 $ emerge dev-lang/erlang 。还可以使用 $ apt-get install erlang 在 Ubuntu 或 Debian 发行版上安装 Erlang。

对于其他 UNIX® 和 Linux 平台,可以下载源代码并手工构建它。从源代码构建 Erlang 需要 C 编译器和 make 工具(参见 参考资料 )。基本步骤如下:

  1. 解压源代码:$ tar zxf otp_src_R14B01.tar.gz
  2. 切换目录:$ cd otp_src_R14B
  3. 运行配置脚本:$ ./configure
  4. 运行 make 以构建代码:$ make

还可以从 Erlang 网站获得 Windows® 安装程序(参见 参考资料 )。


第一个 Erlang 程序,一个递归的 Fibonacci 函数

要想了解函数编程风格的好处以及它在 Erlang 中的实现方式,最好的方法是了解 Fibonacci 函数。Fibonacci 数列是一种整数序列,可以使用以下算式计算各个 Fibonacci 值:F(n) = F(n-1) + F(n-2)

第一个值 F(0) 的结果是 0F(1) 的结果是 1 。在此之后,通过把前两个值相加求出 F(n) 。例如,F(2) 的计算过程见 清单 1


清单 1. F(2) 的计算过程

F(2) = F(2-1) + F(2-2) F(2) = F(1) + F(0) F(2) = 1 + 0 F(2) = 1

 

Fibonacci 数列对于许多系统(包括分析金融数据)都很重要,它还是在树结构的主干和分支上安排叶节点的基础。如果您玩过使用 3D 树的视频游戏,就会知道,这类游戏很可能使用 Fibonacci 数列来确定分支和叶的位置。

在用编程语言编写 Fibonacci 计算时,可以使用递归来实现,即函数通过调用本身从 root(F(0)F(1) )开始计算数字。

在 Erlang 中,可以用变量和固定的值创建函数。这样可以简化 Fibonacci 数列的计算,因为 F(0)F(1) 返回的是固定的值,而不是计算出的值。

因此,基本函数有三种情况:提供的值是 01 和任何更高的值。在 Erlang 中,使用分号分隔语句,所以可以用 清单 2 所示的代码定义基本 Fibonacci 函数。


清单 2. 基本 Fibonacci 函数

fibo(0) -> 0 ; fibo(1) -> 1 ; fibo(N) when N > 0 -> fibo(N-1) + fibo(N-2) .

 

第一行定义调用 fibo(0) 的结果(-> 把定义与函数体分隔开),第二行定义调用 fibo(1) 的结果,第三行定义在提供正值 N 时执行的计算。可以这样做是因为在 Erlang 中有一个称为模式匹配的系统,后面会详细讨论这个系统。注意,最后一个语句(和 Erlang 中的所有语句)以句号结尾。实际的计算非常简单。

现在,我们来仔细查看一下 Erlang 语言的结构。


基础知识

如果您习惯了 Perl、Python 或 PHP 等语言,那么 Erlang 的结构和布局看起来可能有点儿怪,但它的某些方面会极大地简化应用程序的编写过程,让您不必为代码的许多方面操心。尤其是,Erlang 代码比其他语言少得多,某些操作、表达式和构造往往只有一行。

了解 Erlang 最简便的方法是使用 Erlang shell。安装 Erlang 之后,可以通过在命令行上执行 erl 运行 Erlang shell,参见 清单 3


清单 3. 使用 Erlang shell

$ erl Erlang R13B04 (erts-5.7.5) [source] [rq:1] [async-threads:0] Eshell V5.7.5 (abort with ^G) 1>

 

可以在提示符下输入语句(语句应该以句号结尾)。shell 会执行语句。因此,输入一个简单的求和语句会返回 清单 4 所示的结果。


清单 4. 输入简单的求和语句

1> 3+4. 7

 

下面使用 shell 研究一些数据类型和构造。


数据类型

Erlang 支持基本数据类型(比如整数和浮点数)和更复杂的结构(比如元组和列表)。

整数和大多数整数操作与其他语言相同。可以把两个数字相加,参见 清单 5


清单 5. 将两个数字相加

1> 3+4. 7

 

可以使用圆括号组织算式,参见 清单 6


清单 6. 使用圆括号组织算式

2> (4+5)*9 2> . 81

 

注意,在清单 6 中结束语句的句号在另一行上,输入句号之后方可执行前面的计算。

在 Erlang 中,会使用浮点数代表实数,并且可以自然地表达浮点数,参见 清单 7


清单 7. 自然地表示浮点数

3> 4.5 + 6.2 . 10.7

 

还可以使用指数表示浮点数,参见 清单 8


清单 8. 使用指数表示浮点数

4> 10.9E-2 +4.5 4> . 4.609

 

在整数和浮点数上,都支持使用标准的数学操作符(+、-、/ 和 *),可以在算式中混合使用浮点数和整数。但是,如果对浮点数使用取模和求余数操作符,则会产生错误,因为这些操作符只支持整数。


原子值

原子值是静态的(即不变的)字面值。清单 9 给出一个示例。


清单 9. 原子值

8> abc. abc 9> 'Quoted literal'. 'Quoted literal'

 

原子值的使用方式应该与 C 中的 #define 相同,也就是说,作为一种明确指定或标识值的方法。因此,对于原子值,惟一合法的操作是比较。还可以将原子值的这种使用方法扩展到布尔逻辑,利用原子值 true 和 false 来标识语句的布尔结果。原子值必须以小写字母开头,否则需要加上单引号。

例如,可以比较整数并获得布尔原子值结果,参见 清单 10


清单 10. 比较整数以获得布尔原子值

10> 1 == 1. true

 

还可以比较原子值,参见 清单 11


清单 11. 比较原子值

11> abc == def. false

 

原子值本身按字母表次序排序(即 z 的值大于 a ),参见 清单 12


清单 12. 原子值按字母表次序排序

13> a < z. true

 

可以使用标准的布尔操作符,比如 andorxornot 。还可以使用 is_boolean() 函数检查提供的值是 true 还是 false。


元组

元组是复合的数据类型,用于存储数据项的集合。元组要放在花括号中(参见 清单 13 )。


清单 13. 元组

14> {abc, def, {0, 1}, ghi}. {abc,def,{0,1},ghi}

 

一个元组的内容不必都是相同类型的。元组的构造很特殊,其中的第一个值为原子值。在这种情况下,第一个原子值称为标签,可以使用它来标识内容或对内容进行分类(参见 清单 14 )。


清单 14. 第一个值为原子值的元组

16> { email, 'example@example.org'}. {email,'example@example.org'}

 

在这里标签是 email,可以使用标签标识此元组中其余的内容。

元组对于包含定义的元素和描述各种复杂数据结构非常有用。Erlang 允许显式地设置和获取元组中的值(参见 清单 15 )。


清单 15. 显式地设置和获取元组中的值

17> element(3,{abc,def,ghi,jkl}). ghi 18> setelement(3,{abc,def,ghi,jkl},mno). {abc,def,mno,jkl}

 

注意,元组元素以 1 作为第一个值的索引,而不是像其他大多数语言中那样从 0 开始。还可以将元祖作为整体进行比较(参见 清单 16 )。


清单 16. 作为整体比较元组

19> {abc,def} == {abc,def}. true 20> {abc,def} == {abc,mno}. false

 


列表

最后一个数据类型是列表,列表用方括号表示。列表与元组相似,但是元组只能在比较中使用,而列表允许执行的操作更多。

基本的列表如 清单 17 所示。


清单 17. 基本的列表

22> [1,2,3,abc,def,ghi,[4,56,789]]. [1,2,3,abc,def,ghi,[4,56,789]]

 

字符串实际上是特殊类型的列表。Erlang 不直接支持字符串的概念,但是可以使用带双引号的值创建字符串值(参见 清单 18 )。


清单 18. 使用带双引号的值创建字符串值

23> "Hello". "Hello"

 

但是,字符串实际上只是由 ASCII 字符值组成的列表。因此,上面的字符串存储为由 ASCII 字符值组成的列表(参见 清单 19 )。


清单 19. 字符串存储为由 ASCII 字符值组成的列表

24> [72,101,108,108,111]. "Hello"

 

还可以使用 $Character 表示法指定字符(参见 清单 20 )。


清单 20. 使用 $Character 表示法指定字符

25> [$H,$e,$l,$l,$o]. "Hello"

 

列表(包括字符串,即字符的列表)支持许多操作。这是字符串与原子值之间的主要区别。原子值是静态的标识符,但是可以通过操作字符串的组成部分(各个字符)来操作字符串。例如,不能标识原子值(比如 'Quick brown fox' )中的各个单词,因为原子值是单一实体。但是,您可以把字符串分割为单词:["Quick","brown","fox"]

lists 模块中提供了许多用于操作列表的函数。例如,可以使用 sort 函数对列表中的数据项进行排序。因为这些是内置的函数,所以必须指定模块和函数名(参见 清单 21 )。


清单 21. 指定模块和函数名

34> lists:sort([4,5,3,2,6,1]). [1,2,3,4,5,6]

 


列表操作

可以使用构造函数构造包含多个元素的列表,它用一个元素和另一个列表构造列表。在其他语言中,这种构造操作由用于 push() 的函数或操作符处理。在 Erlang 中,使用 | (管道)操作符分隔头(列表的开头)和尾,表达方式为 [Head|Tail] 。头是单一元素,尾是列表的其余部分。

清单 22 展示了如何在列表的开头添加新的值。


清单 22. 在列表的开头添加新的值

29> [1|[2,3]]. [1,2,3]

 

可以重复执行这种操作以构造整个列表,参见 清单 23


清单 23. 重复执行这种操作以构造整个列表

31> [1|[2|[3|[]]]]. [1,2,3]

 

在这个示例中,末尾的空列表意味着您构造了一个结构良好(合适)的列表。注意,第一项必须是一个元素,不能是列表片段。如果以其他方式执行合并,则会构造一个嵌套式列表(参见 清单 24 )。


清单 24. 构造嵌套的列表

30> [[1,2]|[2,3]]. [[1,2],2,3]

 

最后,可以使用 ++ 操作符合并列表,参见 清单 25


清单 25. 使用 ++ 操作符合并列表

35> [1,2] ++ [3,4]. [1,2,3,4]

 

还可以从操作符左边的列表中删除右边列表中的所有元素(参见 清单 26 )。


清单 26. 删除列表中的元素

36> [1,2,3,4] -- [2,4]. [1,3]

 

因为字符串是列表,所以这些操作也适用于字符串(参见 清单 27 )。


清单 27. 这些操作也适用于字符串

37> "hello" ++ "world". "helloworld" 40> ("hello" -- "ello") ++ "i". "hi"

 

尽管这里只简要介绍了数据类型,但是我们希望让您对基本数据类型和操作有足够的了解。


表达式和模式匹配

在研究数据类型时,我们已经看到了许多表达式和构造。表达式的重要元素是变量。Erlang 中的变量必须以大写字母开头,后面是大写字母、小写字母和下划线的任意组合(参见 清单 28 )。


清单 28. Erlang 中的变量

41> Value = 99. 99

 

在 Erlang 中,在对变量赋值时,是一次性将值绑定到变量。绑定变量之后,就不能改变它的值(参见 清单 29 )。


清单 29. 将值绑定到变量

42> Value = 100. ** exception error: no match of right hand side value 100

 

这与大多数语言不一样 — 变量的定义通常意味着值是可变的。在 Erlang 中,只能赋值一次意味着:如果希望向计算某个值的结果,则必须将它赋值给新的变量(参见 清单 30 )。


清单 30. Erlang 中变量的限制

43> Sum = Value + 100 199

 

只能赋值一次的好处是,在计算过程中很难意外地设置或改变变量值,这让值的识别和调试变得更容易,也让代码更为清晰,有时候更简短(因为可以简化结构)。

注意,这种操作意味着先计算出值,然后把值绑定到变量。在其他语言中,可以根据函数或操作的引用设置值,这意味着值取决于访问它时引用的值。在 Erlang 中,在创建变量时它的值总是已知的。

可以使用 f(Varname) 显式地忽略一个变量的绑定,或使用 f() 忽略所有变量的绑定。

为变量赋值实际上就是一种特殊的模式匹配。Erlang 中的模式匹配还会处理各个语句的执行流,以及从复合数据类型(元组、数组)中提取出值。模式匹配的基本形式是:模式 = 表达式。

表达式由数据结构、绑定的变量(即具有值的变量)、数学操作符和函数调用组成。操作的两边必须匹配(也就是说,如果模式是包含两个元素的元组,那么表达式的计算结果也必须是包含两个元素的元组)。在执行表达式时,计算表达式并将结果赋值给模式。

例如,可以使用一个模式匹配同时为两个变量赋值(参见 清单 31 )。


清单 31. 同时给两个变量赋值

48> {A,B} = {(9+45),abc}. {54,abc}

 

注意,如果模式是绑定的变量,或者模式的元素是绑定的变量,那么模式匹配的结果就会变成比较。此操作支持实现强大的选择性赋值。例如,为了从元组中获取姓名和电子邮件地址,可以使用以下模式匹配:{contact, Name, Email} = {contact, "Me", "me@example.com"}

最后,可以按前面提到的构造表示方法,使用模式匹配从列表或元组中提取元素。例如,清单 32 展示了如何获得列表中的前两个元素,同时保留其余元素。


清单 32. 获得列表中的前两个元素,同时保留其余元素

53> [A,B|C] = [1,2,3,4,5,6]. [1,2,3,4,5,6]

 

A 赋值为 1B 赋值为 2C 赋值为列表的其余部分。


函数

与其他语言一样,Erlang 中的函数是所有程序的基本组成部分。函数由函数名(由一个原子值定义)和圆括号中的零个或更多函数参数组成:sum(N,M) -> N+M

函数必须在文件中的模块中定义(不能在 shell 中定义函数)。参数可以包含复合数据类型。例如,可以使用元组中的标签选择不同的操作(参见 清单 33 )。


清单 33. 可以使用元组中的标签选择不同的操作

mathexp({sum, N,M}) -> N+M ; mathexp({sub, N,M}) -> N-M ; mathexp({square, N}) -> N*N.

 

分号是每个函数定义之间的 “或” 操作符。使用模式匹配评估函数的参数,所以如果把包含三个元素的元组提供给 mathexp() 函数,模式匹配会失败。

Erlang 中的函数还可以接受不同数量的参数。Erlang 会执行模式匹配,直到找到有效的函数定义,从而选择适当的函数定义。函数的参数数量称为元数 (arity),用于帮助标识函数。

再看一下 Fibonacci 示例,现在您就会发现,当调用 fibo(0) 时,模式与函数的第一个定义匹配,fibo(1) 与第二个定义匹配,其他值与最后一个定义匹配。这也解释了函数执行的递归是如何实现的。例如,在调用 fibo(9) 时,可以使用相应的值调用 fibo(N) 函数定义,直到到达 fibo(0)fibo(1) 函数定义(它们返回固定的值)。

任何函数的返回值都是子句(在我们的示例中只有一行)中最后一个表达式的结果。注意,只有在找到了匹配项且变量是函数局部变量的情况下,才会为变量赋值。


模块

与其他语言中的模块一样,模块用于把相似的函数集中在一起。

在文件中指定模块名(必须与文件名匹配),然后指定模块中希望导出到装载此模块的其他程序的函数。例如,清单 34 给出了文件 fib.erl,其中包含 fib 模块的定义。


清单 34. fib.erl 文件

-module(fib). -export([fibo/1, printfibo/1]). %% print fibo arg. and result, with function as parameter printfibo(N) -> Res = fib:fibo(N), io:fwrite("~w ~w~n", [N, Res]). fibo(0) -> 0 ; fibo(1) -> 1 ; fibo(N) when N > 0 -> fibo(N-1) + fibo(N-2) .

 

模块声明位于 -module() 行中。-export() 行包含要导出的函数的列表。每个函数的定义都给出了函数名和函数的元数,以便您能导出函数的特定定义。

要使用模块,则需要编译并装载模块。可以在 shell 中使用 c() 语句完成这个步骤,参见 清单 35


清单 35. 使用 c() 语句编译并装载模块

1> c(fib). {ok,fib} 2> fib:printfibo(9). 9 34 ok

 

注意,函数调用包含模块名,从而确保调用的是 fib 模块中的 printfibo() 函数。


结束语

Erlang 的结构和格式与大多数其他语言有很大区别。尽管许多数据类型和基本的表达式是相同的,但是它们的用法和应用不太一样。变量只能赋值一次,通过模式匹配系统对不同的表达式进行运算,这些特性给典型的语言环境提供了一些强大的扩展。例如,可以为同一函数定义多种处理方式,还可以对递归调用应用模式匹配,这样做可以简化某些函数。

在下一篇文章中,我们将讨论 Erlang 的进程、消息解析和网络功能,并通过研究 MochiWeb HTTP 服务器了解这种语言的强大功能和灵活性。

 

本文转载于developerWorks,原文在这里

分享到:
评论

相关推荐

    ErlangOTP并发编程实战

     《erlang/otp并发编程实战》主要分为三大部分:第一部分讲解erlang 编程及otp 基础;第二部分讲解如何在实际开发中逐一添加otp 高级特性,从而完善应用,作者通过贯穿本书的主项目——加速web 访问的分布式缓存...

    erlang OTP并发编程实战(中文高清).part1

    erlang OTP并发编程实战,连城译著,清晰版的,学习erlang的必看书籍。 (积分权限不够,只能分两部分了)

    erlang OTP并发编程实战(中文高清).part2

    erlang OTP并发编程实战,连城译著,清晰版的,学习erlang的必看书籍。 第二部分(积分权限不够,只能分两部分了)

    Erlang中文手册

    小部分。比如,我将只会告诉你最简单的语法,而不是所有详细的结构原理。有很多极其简 单的东西我会写上*manual*,这就表示这里有很多信息,你可以在Erlang相关的书中找 到或是在&lt;Erlang参考手册&gt;中找得到。 我也...

    Erlang 教程中文版 - 极客学院

    Erlang 是一种多用途编程语言,主要用于开发并发和分布式系统。它最初是一种专有的编程语言,Ericsson 使 用它来开发电话和通信应用程序。 本教程目的是告诉你如何快速高效地学习 Erlang 语言。教程中只讲述了 ...

    Twisted系列教程.pdf

    第二部分:异步编程初探与reactor模式 第三部分:初步认识Twisted 第四部分:由Twisted支持的诗歌客户端 第一个twisted支持的诗歌服务器 第一滴心血 第五部分:由Twisted支持的诗歌客户端 第一滴心血 第六...

    Elixir程序设计语言.pdf

    2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9 2.10 2.11 2.12 2.13 2.14 3.1 3.2 3.3 3.4 4.1 4.2 4.3 和Erlang互操作 错误处理 可执⾏⽂件 并发OTP并发 OTP Supervisors OTP 分布式 元编程 Umbrella Projects ...

    适用于Erlang / OTP的小型,快速,现代HTTP服务器。-C/C++开发

    它针对低延迟和低内存使用进行了优化,部分原因是Cowboy Cowboy是适用于Erlang / OTP的小型,快速,现代的HTTP服务器。 Goals Cowboy的目标是在一个小的代码库中提供完整的HTTP堆栈。 它针对低延迟和低内存使用进行...

    erlings:小练习让您习惯于阅读和编写Erlang代码

    Erlang是一种编程语言,用于构建对高可用性有要求的大规模可扩展软实时系统。 它的一些用途是在电信,银行,电子商务,计算机电话和即时消息中。 Erlang的运行时系统具有对并发,分发和容错的内置支持。 它是任何...

    cpie-cn_r148.pdf

    第1章 Erlang教程 串行编程 数据类型 模式识别 内置函数 并发 第2章串行编程 项式 模式匹配 表达式求值 模块系统 函数定义 原语 算术表达式 变量作用域 第3章列表编程 用于列表处理的BIF 常用列表...

    ErlangQuest:通过这组挑战学习 Erlang。 一个认识 Erlang 的交互系统

    二郎任务Erlang Quest 是一个小游戏,您可以在其中解决 Erlang 编程任务,从非常简单的东西开始。 Erlang Quest 是一种练习 Erlang 的方式——在 Erlang 中写作和解决问题。 它不包括 Erlang 语法等方面的教程——...

    gml:Erlang 中的另一个生命游戏实现

    通用语言GML 是用 Erlang 编程语言编写的康威生命游戏。 它在无边无际的空间中运行,所以要小心——它会吃掉你的宇宙。 作者:米哈伊尔·库尔科夫用法克隆存储库后运行简单的make命令 - 它将启动 Erlang shell 并...

    Ceylan-WOOPER:Ceylan项目的一部分,该项目收集了所有与WOOPER相关的元素(Erlang中的OOP)

    塞兰·伍珀 该存储库对应于的一部分,该部分收集了所有WOOPER相关的元素,用于使用Erlang进行面向对象的编程。 请参阅,否则请参阅其。 “主”分支旨在对应于该层的当前稳定版本。

    CloudI:最低级别的云!

    当使用非Erlang编程语言进行软件扩展时,或在将软件系统(全部或部分)转换为Erlang编程语言期间,CloudI可以减轻软件开发风险(延迟或故障)。应该如何使用CloudI? CloudI API以任何受支持的语言(当前为C / C ++...

    fastinvsqrt:编程语言中的快速反平方根

    编程语言中的快速反平方根 这是我用多种语言编写快速反平方根算法所面临的挑战的资源库。 用多种语言编写一种算法很有趣。 我用从未有过的语言写过一些代码。 我了解了语言之间的差异和相似之处,以及其他语言如何...

    functional-programming:这是KTH提供的函数式和并发编程课程

    最初使用的编程语言是Erlang,但现在我们转向Elixir。讲课将有14堂讲座将讲解功能和并发编程的概念。 我们将使用Elixir作为示例语言,但讲座更侧重于全局。 学习Elixir是您在编程时最好学习的东西。练习题强烈建议...

    bitnami-docker-rabbitmq:RabbitMQ的Bitnami Docker映像

    RabbitMQ服务器使用Erlang编程语言编写,并基于Open Telecom Platform框架构建,用于集群和故障转移。 与代理接口的客户端库适用于所有主要编程语言。 TL; DR $ docker run --name rabbitmq bitnami/rabbitmq:...

    Programming-Paradigms-DD1361.Inet:实验室网络

    写在例如Go 或 Erlang 旨在处理并行编程范式。 另一方面,Erlang 不太擅长处理字符串,您可能需要用 Java 为用户界面编写一个小型前端。 (此实验不固定在 Kattis 上。) 背景 下载示例代码。 服务器部分有两个类 ...

    java版飞机大战源码-programbook:节目单

    【第一部分】 (豆瓣阅读,免费书籍) 其它 Android Google Material Design 正体中文版( ) Android 一些重要知识点解析整理 APP AWK C/C++ (欢迎大家参与在线翻译和校对) (宋劲杉, 北京亚嵌教育研究中心) (中英文版) ...

Global site tag (gtag.js) - Google Analytics