clojure语言

本文介绍了Clojure编程语言。 Clojure是Lisp的方言。 假定您还不了解Lisp。 而是假定您具有Java技术知识。 要编写Clojure程序,您需要Java Development Kit V5或更高版本以及Clojure库。 对于本文,使用了JDK V1.6.0_13和Clojure V1。 您还应该利用Eclipse的Clojure插件(clojure-dev),并且将需要Eclipse。 在本文中,使用了Eclipse V3.5以及clojure-dev 0.0.34。 请参阅相关主题的链接。

什么是Clojure?

不久之前,在Java虚拟机(JVM)上运行程序意味着使用Java编程语言编写程序。 那些日子已经一去不复返了,因为现在您有很多选择。 许多流行的选择,例如Groovy,Ruby(通过JRuby)和Python(通过Jython),都允许使用更具过程性,脚本化的编程风格,或者它们具有自己的面向对象编程风格。 这些都是Java程序员熟悉的范例。 有人可能会争辩说,使用这些语言,您编写的程序与使用Java语言编写的程序相似。 您只需要使用其他语法即可。

Clojure是JVM的另一种编程语言。 但是,它与Java技术或提到的任何其他JVM语言完全不同。 它是Lisp的方言。 Lisp编程语言家族已有很长的历史了-实际上自1950年代以来。 Lisp使用不同的S表达式或前缀表示法。 这种表示法可以概括为(function arguments...) 。 您始终以函数名称开头,并列出零个或多个参数以传递给该函数。 通过使用括号将函数及其参数组合在一起。 这导致Lisp的商标之一:很多括号。

您可能会猜到,Clojure是一种功能编程语言。 学术界可以争论它的“纯度”,但是它绝对包含了函数式编程的Struts:避免可变状态,递归,高阶函数等。Clojure还是一种动态类型化的语言,尽管您可以选择添加类型信息来提高性能。代码中的关键路径。 Clojure不仅在JVM上运行,而且在设计时考虑了Java的互操作性。 最后,Clojure是一种在设计时考虑了并发性的语言,并且具有与并发编程相关的一些独特功能。

通过示例Clojure

对于许多人来说,学习新语言的最佳方法是开始编写代码。 本着这种精神,我们将处理一些简单的编程问题,并使用Clojure解决它们。 我们将详细介绍解决方案,以更好地了解Clojure的工作原理,使用方式以及它做得如何。 但是,像其他任何语言一样,我们需要建立一个开发环境来使用它。 幸运的是,使用Clojure可以很容易地做到这一点。

最少的设置

使用Clojure所需的全部是JDK和Clojure库,它是单个JAR文件。 开发和运行Clojure程序有两种常用方法。 最常见的是使用其read-eval-print-loop(REPL)。

清单1. Clojure REPL
$ java -cp clojure-1.0.0.jar clojure.lang.Repl
Clojure 1.0.0-
user=>

该命令是从Clojure JAR所在的目录运行的。 根据需要调整JAR的路径。 您还可以创建脚本并执行脚本。 为此,您需要执行一个名为clojure.main的Java类。

清单2. Clojure主要
$ java -cp clojure-1.0.0.jar clojure.main /some/path/to/Euler1.clj
233168

同样,您需要调整Clojure JAR和脚本的路径。 最后,IDE支持Clojure。 Eclipse用户可以使用其Eclipse更新站点安装clojure-dev插件。 安装完成后,确保您在Java透视图中,然后可以创建一个新的Clojure项目和新的Clojure文件,如下所示。

图1.使用clojure-dev,Eclipse的Clojure插件

使用clojure-dev,您可以突出显示一些基本的语法,包括括号匹配(任何Lisp都必须具备)。 您还可以在直接嵌入Eclipse的REPL中启动任何脚本。 截至本文撰写时,该插件仍是一个非常新的插件,其功能正在Swift发展。 现在我们已经有了基本的设置,让我们通过编写一些Clojure程序来探索语言。

示例1:使用序列

Lisp的名称来自“列表处理”,通常说Lisp中的所有内容都是一个列表。 在Clojure中,这被概括为序列。 对于第一个示例,我们将处理以下编程问题。

如果我们列出所有低于10的自然数,这些自然数是3或5的倍数,则得到3、5、6和9。这些倍数的总和为23。找到1,000以下的3或5的所有倍数的总和。

这个问题来自于Euler项目,它是一组数学问题,可以使用聪明的(或有时不太聪明的)计算机编程来解决。 实际上,这是问题1。清单3显示了使用Clojure的解决方案。

清单3.来自项目Euler的示例1
(defn divisible-by-3-or-5? [num] (or (== (mod num 3) 0)(== (mod num 5) 0))) (println (reduce + (filter divisible-by-3-or-5? (range 1000))))

第一行定义一个函数。 请记住:函数是Clojure中程序的主要构建块。 大多数Java程序员习惯于将对象作为其程序的构建块,因此使用函数可能需要一些时间来适应。 您可能会认为defn是该语言的关键字,但实际上它是一个宏。 宏允许您扩展Clojure编译器,以从本质上向该语言添加新的关键字。 因此, defn并不是语言规范的一部分,而是由语言的核心库添加的。

在这种情况下,它正在创建一个称为divisible-by-3-or-5?的函数divisible-by-3-or-5? 。 这遵循Clojure命名约定。 单词之间用连字符隔开,函数名称以问号结尾,表明它是谓词,它返回true或false。 该函数采用一个名为num参数。 如果有更多输入参数,它们将出现在方括号内,并用空格分隔。

接下来是函数的主体。 首先,我们调用or函数。 这是正常的逻辑or习惯; 它只是一个函数,而不是一个运算符。 我们将其传递给参数。 这些也是表达式。 第一个表达式以==函数开头。 这会对传递给它的参数进行基于值的比较。 有两个参数传递给它。 首先是另一种表达方式; 此表达式调用mod函数。 这是数学的模运算符,或者是Java语言中的%运算符。 它返回余数,因此,在这种情况下,将num除以3后得到余数。将该余数与0进行比较(它是余数0,因此num被3整除)。 同样,我们检查num除以5后的余数是0。如果这些余数之一为0,则函数返回true。

在下一行,我们将创建一个表达式并将其打印出来。 让我们从最里面的括号开始。 在这里,我们调用范围函数并传入数字1000。 这将创建一个从0开始的所有小于1000的序列。 这正是我们要检查的数字集,以查看它们是否可以被3或5整除。移出后,我们调用filter函数。 这有两个参数:第一个是另一个函数,必须是谓词,因为它必须返回true或false; 第二个参数是一个序列-在这种情况下为序列(0, 1, 2, ... 999)filter函数应用谓词,如果返回true,则序列中的元素将添加到结果中。 谓词只是divisible-by-3-or-5? 在上面的行中定义的功能。

因此,过滤器表达式将产生一个整数序列,每个整数均小于1,000,并且可以被3或5整除。这正是我们感兴趣的整数集合,因此现在我们只需要添加它们即可。 为此,我们使用reduce函数。 该函数有两个参数:一个函数和一个序列。 它将功能应用到序列中的前两个元素。 然后,它将函数应用于序列中的上一个结果和下一个元素。 在这种情况下,函数是+函数或加法。 因此,它将添加序列中的所有元素。

看一下清单3,少量代码中发生了很多事情。 这是Clojure的吸引力之一。 发生了很多事情,但是一旦您习惯了这种记法,代码就变得不言自明了。 当然,要做同样的事情将需要更多的Java代码。 让我们继续另一个例子。

示例2:懒惰是一种美德

对于此示例,我们将研究Clojure中的递归和惰性。 对于许多Java程序员来说,这是另一个新概念。 Clojure允许您定义“惰性”序列,因为直到需要它们时才计算它们的元素。 这允许您定义无限序列,并且您肯定看不到Java语言中的序列。 要查看一个何时特别有用的示例,让我们看一个涉及函数式编程另一个重要方面的示例:递归。 再次,我们使用来自Euler项目的编程问题,但这一次是问题2。

斐波那契数列中的每个新项都是通过将前两个项相加而生成的。 以1和2开头,前10个项将是:1、2、3、5、8、13、21、34、55、89,...

找出序列中所有不超过400万的偶数项之和。 为了解决这个问题,Java程序员可能会想定义一个给您第n个斐波那契数的函数。 一个简单的实现如下所示。

清单4.天真的斐波那契函数
(defn fib [n] (if (= n 0) 0(if (= n 1) 1(+ (fib (- n 1)) (fib (- n 2))))))

这检查n是否为0;否则为0。 如果是,则返回0。然后检查n是否为1。如果是,则返回1。否则,计算第(n-1)个斐波那契数和第(n-2)个斐波那契数并将它们加在一起。 这当然是正确的,但是如果您做了很多Java编程,就会发现问题所在。 这样的递归定义将Swift填充堆栈并导致堆栈溢出。 斐波那契数形成一个无限序列,因此应使用Clojure的无限惰性序列进行描述。 这是清单5.注意,虽然Clojure的具有更高效的斐波那契实现,它是标准库(Clojure的-的contrib)的一部分,它是更为复杂所示,所以这里显示的斐波那契序列来自斯图尔特哈洛韦的书(见相关主题想要查询更多的信息)。

清单5.斐波那契数的惰性序列
(defn lazy-seq-fibo ([] (concat [0 1] (lazy-seq-fibo 0 1))) ([a b] (let [n (+ a b)] (lazy-seq (cons n (lazy-seq-fibo b n))))))

在清单5中, lazy-seq-fibo函数具有两个定义。 第一个定义没有参数,因此为空方括号。 第二个定义有两个参数[ab] 。 对于无参数的情况,我们采用序列[0 1]并将其连接到一个表达式。 该表达式是对lazy-seq-fibo的递归调用,但是这一次,它正在调用两个参数的情况,将0和1传递给它。

两个参数的情况以let表达式开始。 这是Clojure中的变量分配。 表达式[n (+ ab)]定义变量n并将其设置为等于a+b 。 然后,它使用了lazy-seq宏。 顾名思义, lazy-seq宏用于创建惰性序列。 它的身体是一种表达。 在这种情况下,它使用了cons函数。 这是Lisp中的经典功能。 它接受一个元素和一个序列,并通过在该元素之前添加一个新序列来返回该序列。 在这种情况下,该序列是再次调用lazy-seq-fibo函数的结果。 如果此序列不是惰性的,就会一次又一次调用lazy-seq-fibo函数。 但是, lazy-seq宏确保仅在访问元素时才调用该函数。 要查看此序列的实际效果,可以使用REPL,如清单6所示。

清单6.生成斐波那契数
1:1 user=> (defn lazy-seq-fibo ([] (concat [0 1] (lazy-seq-fibo 0 1))) ([a b] (let [n (+ a b)] (lazy-seq (cons n (lazy-seq-fibo b n))))))
#'user/lazy-seq-fibo
1:8 user=> (take 10 (lazy-seq-fibo))
(0 1 1 2 3 5 8 13 21 34)

take函数用于从序列中获取一定数量(在这种情况下为10个)元素。 现在我们有了生成斐波那契数的好方法,让我们解决这个问题。

清单7.示例2
(defn less-than-four-million? [n] (< n 4000000))(println (reduce + (filter even? (take-while less-than-four-million? (lazy-seq-fibo)))))

在清单7中,我们定义了一个函数,该函数less-than-four-million? 。 这只是测试其输入是否少于400万。 在下一个表达式中,从最内部的表达式开始很有用。 我们首先得到无限斐波那契数列。 然后,我们使用take-while功能。 这就像take函数,但是需要一个谓词。 一旦谓词返回false,它将停止从序列中提取。 因此,在这种情况下,只要我们得到的斐波那契数大于400万,我们就会停止采用。 我们得到这个结果并应用一个过滤器。 过滤器使用内置的even? 功能。 该函数可以实现您的预​​期:测试数字是否为偶数。 结果是所有的斐波那契数小于400万甚至更少。 现在,我们像第一个示例一样使用reduce对其求和。

清单7解决了手头的问题,但并不完全令人满意。 要使用take-while函数,我们必须定义一个非常简单的函数,称为less-than-four-million? 。 事实证明,这不是必需的。 Clojure支持闭包也就不足为奇了。 这样可以简化清单8中的代码。

Clojure中的闭包

闭包在许多编程语言中都很常见,尤其是在函数语言(例如Clojure)中。 函数不仅是一等公民,而且可以作为参数传递给其他函数,而且可以内联或匿名定义。 清单8使用闭包显示了清单7的简化形式。

清单8.更简单的解决方案
(println (reduce + (filter even? (take-while (fn [n] (< n 4000000)) (lazy-seq-fibo)))))

在清单8中,我们使用了fn宏。 这将创建一个匿名函数并返回它。 谓词函数通常非常简单,最好使用闭包进行定义。 事实证明,Clojure具有一种更简化的定义闭包的方式。

清单9.速记闭合
(println (reduce + (filter even? (take-while #(< % 4000000) (lazy-seq-fibo)))))

我们使用#来创建闭包,而不是fn宏。 我们还对传递给函数的第一个参数使用了%符号。 如果函数接受多个参数,则也可以将%1用作第一个参数,类似地使用%2%3等。

仅通过这两个简单的示例,我们就看到了Clojure的许多功能。 Clojure的另一个重要方面是它与Java语言的紧密集成。 让我们看另一个示例,其中利用Clojure的Java会有所帮助。

示例3:使用Java技术

Java平台可以提供很多东西。 JVM的性能以及核心API的丰富性以及用Java语言编写的大量第三方库都是强大的工具,可以使您免于重造过多的负担。 Clojure围绕这些想法而构建。 调用Java方法,创建Java对象,实现Java接口以及扩展Java类很容易。 要查看一些示例,让我们看一下另一个Euler项目问题​​。

清单10. Euler项目的第8个问题
Find the greatest product of five consecutive digits in the 1000-digit number.73167176531330624919225119674426574742355349194934
96983520312774506326239578318016984801869478851843
85861560789112949495459501737958331952853208805511
12540698747158523863050715693290963295227443043557
66896648950445244523161731856403098711121722383113
62229893423380308135336276614282806444486645238749
30358907296290491560440772390713810515859307960866
70172427121883998797908792274921901699720888093776
65727333001053367881220235421809751254540594752243
52584907711670556013604839586446706324415722155397
53697817977846174064955149290862569321978468622482
83972241375657056057490261407972968652414535100474
82166370484403199890008895243450658541227588666881
16427171479924442928230863465674813919123162824586
17866458359124566529476545682848912883142607690042
24219022671055626321111109370544217506941658960408
07198403850962455444362981230987879927244284909188
84580156166097919133875499200524063689912560717606
05886116467109405077541002256983155200055935729725
71636269561882670428252483600823257530420752963450

在这个问题中,我们有一个1000位数字。 这可以使用BigInteger在Java技术中用数字表示。 但是,我们不需要对整数进行计算-一次只计算五位数字。 因此,将其视为字符串更容易。 但是,要进行计算,我们需要将数字视为整数。 幸运的是,有Java语言的API,可以在字符串和整数之间来回切换。 首先,我们需要从上方处理大量不合规定的文字。

清单11.解析文本
(def big-num-str (str "73167176531330624919225119674426574742355349194934
96983520312774506326239578318016984801869478851843
85861560789112949495459501737958331952853208805511
12540698747158523863050715693290963295227443043557
66896648950445244523161731856403098711121722383113
62229893423380308135336276614282806444486645238749
30358907296290491560440772390713810515859307960866
70172427121883998797908792274921901699720888093776
65727333001053367881220235421809751254540594752243
52584907711670556013604839586446706324415722155397
53697817977846174064955149290862569321978468622482
83972241375657056057490261407972968652414535100474
82166370484403199890008895243450658541227588666881
16427171479924442928230863465674813919123162824586
17866458359124566529476545682848912883142607690042
24219022671055626321111109370544217506941658960408
07198403850962455444362981230987879927244284909188
84580156166097919133875499200524063689912560717606
05886116467109405077541002256983155200055935729725
71636269561882670428252483600823257530420752963450"))

在这里,我们利用Clojure对多行字符串的支持。 我们使用str函数来解析多行字符串文字。 然后,我们使用def宏定义一个名为big-num-str的常量。 但是,将其转换为整数序列最有用。 这在清单12中完成。

清单12.创建一个数字序列
(def the-digits(map #(Integer. (str %)) (filter #(Character/isDigit %) (seq big-num-str))))

再次,让我们从最里面的表达式开始。 我们使用seq函数将big-num-str变成一个序列。 但是,事实证明,此顺序并非我们想要的。 您可以在REPL的帮助下看到它,如下所示。

清单13.检查big-num-str序列
user=> (seq big-num-str)
(\7 \3 \1 \6 \7 \1 \7 \6 \5 \3 \1 \3 \3 \0 \6 \2 \4 \9 \1 \9 \2 \2 \5 \1 \1 \9\6 \7 \4 \4 \2 \6 \5 \7 \4 \7 \4 \2 \3 \5 \5 \3 \4 \9 \1 \9 \4 \9 \3 \4\newline...

REPL将字符(Java字符)显示为\c 。 因此\7是char 7,而\newline是char \ n( \newline行)。 这就是我们直接解析文本所得到的。 显然,在进行任何有用的计算之前,我们需要摆脱换行符并转换为整数。 清单11中就是这样做的。在这里,我们使用了一个过滤器来删除换行符。 再次注意,对于传递给filter函数的谓词函数,我们使用了简写形式的闭合。 闭包使用Character/isDigit 。 这是java.lang.Character的静态方法isDigit 。 因此,过滤器仅允许输入数字字符,放弃换行符。

现在我们已经摆脱了换行符,因此我们需要转换为整数。 在清单12中由内向外移动,请注意,我们使用map函数,该函数具有两个参数:函数和序列。 它返回一个新序列,其中序列的第n个元素是将该函数应用于原始序列的第n个元素的结果。 对于函数,我们再次使用简写的闭合符号。 首先,我们使用Clojure中的str函数将char转换为字符串。 我们为什么要做这个? 因为接下来,我们使用java.lang.Integer的构造函数创建一个整数。 这由Integer表示。 您可以将此表达式视为新的java.lang.Integer(str(%)) 。 将其与map函数配合使用,就可以得到所需的整数序列。 现在我们可以解决问题了。

清单14.示例3
(println (apply max (map #(reduce * %)(for [idx (range (count the-digits))] (take 5 (drop idx the-digits))))))

为了理解这段代码,让我们从for宏开始。 这不像Java语言中的for循环。 相反,它是一个序列理解。 首先,我们使用方括号创建绑定。 在这种情况下,我们将变量idx绑定到一个从0 ... N-1的序列,其中N是the-digits序列中the-digits元素the-digits (N = 1,000,因为原始数字有1,000位)。 接下来, for宏采用一个表达式来生成新序列。 它将遍历idx序列的每个元素,评估表达式,并将结果添加到返回序列中。 您可以看到它在某些方面如何像for循环。 理解中使用的表达式将首先使用drop函数删除序列的前M个元素,然后使用take函数获取缩短序列的前五个元素。 请记住, M将为0,然后为1,然后为2,依此类推,因此结果将是一系列序列,其中第一个元素为(e1,e2,e3,e4,e5),下一个元素为( e2,e3,e4,e5,e6)等,其中e1,e2等是the-digits中的元素。

现在我们有了这个序列序列,我们使用map函数。 通过使用reduce函数,我们将五个数字的每个序列转换为这五个数字的乘积。 现在我们有一个整数序列,其中第一个元素是元素1-5的乘积,第二个元素是元素2-6的乘积,依此类推。我们想要最大的此类乘积。 为此,我们使用max函数。 但是, max希望将多个元素传递给它,而不是单个序列。 要将序列转换为多个元素以传递给max ,我们使用apply函数。 这产生了我们想要解决的最大问题,当然可以打印出答案。 现在您已经解决了几个问题,同时学习了如何使用Clojure。

摘要

在本文中,我们介绍了Clojure编程语言,并受益于使用Eclipse的Clojure插件。 我们简要介绍了它的一些原理和功能,但仅关注代码示例。 在这些简单的示例中,我们已经看到了该语言的许多核心功能:函数,宏,绑定,递归,惰性序列,闭包,理解以及与Java技术的集成。 Clojure还有许多其他方面。 希望该语言引起您的注意,并且您将看一看一些资源并对其进行更多了解。


翻译自: https://www.ibm.com/developerworks/opensource/library/os-eclipse-clojure/index.html

clojure语言

clojure语言_Clojure编程语言相关推荐

  1. Clojure 语言在 2020 年的现状

    十年前,Chas Emerick 进行了首次 Clojure 语言使用状况调查.在 2010 年,大多数用户仅使用 Clojure 数周或数月,很少有人将其用作正式的工作项目的编程语言.今年,我们看到 ...

  2. 脚本语言和编程语言的区别

    脚本语言和编程语言的区别 计算机语言的种类非常的多,总的来说可以分成机器语言,汇编语言,高级语言三大类. 1 . 计算机所能识别的语言只有机器语言,即由0和1构成的代码.但通常人们编程时,不采用机器语 ...

  3. 计算机语言分类:机器语言、汇编语言、标记语言、脚本语言、编程语言

    一.计算机语言分类: 1. 机器语言 机器语言是计算机最原始的语言,是由0和1的代码构成,cpu在工作的时候只认识机器语言,即0和1的代码. 2. 汇编语言 汇编语言,即为一种低级语言,它用人类容易记 ...

  4. 脚本语言、编程语言、中间件

    1 静态 动态语言区别 答: 动态语言:服务端和客户端代码不一致(如html) 静态语言:服务端和客户端代码一致(如: asp,php,aspx,jsp) 2 常见的脚本语言有那些 答: asp .p ...

  5. C语言、编程语言发展史

    前言 了解和学习一门语言.一个系统乃至方方面面的任何东西时,如果不知道其历史和现状而只是一上来就一味地闷头苦学,你就很容易"一叶障目不见泰山". 如此这般火急火燎的就上手苦干,私以 ...

  6. 脚本语言和编程语言的区别和定义

    脚本语言和编程语言的比较 先说点大白话,没读懂得,看一下面官方的说法可能会理解更深入 我们经常用到的脚本语言有shell,js,python等,编程语言有java,c,c++,Go语言等 脚本语言比较 ...

  7. 标记语言、脚本语言和编程语言的含义和比较

    一.含义 标记语言(Markup Language),是一种将文本(Text)以及文本相关的其他信息结合起来,展现出关于文档结构和数据处理细节的电脑文字编码.与文本相关的其他信息(包括例如文本的结构和 ...

  8. 脚本语言和编程语言的区别_编程语言和脚本语言之间的区别

    脚本语言和编程语言的区别 A programming language is a language used to write set of instructions to perform a tas ...

  9. python编程 语言-python编程语言有什么用途

    Python是一门简单易学的编程语言.阅读好的Python程序感觉就像阅读英语,尽管是非常严格的英语.Python的这种伪代码特性是其最大强项之一,它可让你专注于解决问题的办法而不是语言本身.pyth ...

  10. 程序语言(编程语言)汇总大全

    链接至:维基百科 .NET Framework平台语言 C++/CLI · C# · F# · IronPython · J# · Visual C# · Visual Basic .NET · Sm ...

最新文章

  1. torch学习笔记(二) nn类结构-Linear
  2. gbase 导出_GBase 8a导出本地文件时多了目录,gbase_export_directory参数用处
  3. 互联网协议 — Ethernet — 冲突域、广播域
  4. junit5_JUnit 5和Selenium –使用Selenium内置的`PageFactory`实现页面对象模式
  5. 在struts2中訪问servletAPI
  6. HP officejet、PageWide打印机任意代码执行漏洞cve-2017-2741 Tenable发布漏洞检测插件...
  7. Hive旺旺讨论(关于mapjoin)
  8. AGC012D - Colorful Balls(并查集)
  9. java 缓存接口,java项目中,针对缓存问题的处理方式【接口中的处理方式】
  10. Golang 五种原子性操作的用法详解
  11. x86平台堆栈优化问题
  12. 一次性尿袋行业调研报告 - 市场现状分析与发展前景预测(2021-2027年)
  13. 关于微信小程序开发环境苹果IOS真机预览报SSL协议错误问题解决方案
  14. 计算机网络与应用在线作业,北航《计算机网络与应用》在线作业一15秋满分答案...
  15. sqlserver200864位下载_sql2008r2企业版下载-sql2008r2安装包64位 最新版 - 极光下载站...
  16. ​【原创】基于SSM的酒店预约管理系统(酒店管理系统毕业设计)
  17. 国际金融互联报文规范SWIFT之银行识别代码BIC Code介绍
  18. SSM9==SSM项目启动过程、xml配置SSM项目及需要的3大配置文件、原生SSM未前后端分离的电商网站项目(角色管理员、购买者)只使用了最基础的注解,Model传参
  19. php实现 合唱队形(算法想清楚在动)
  20. 51单片机基本工作引脚

热门文章

  1. 学习方法——TRIZ创新理论中的八大法则
  2. 自己动手写iPhone wap浏览器之界面架构篇
  3. 十大模拟炒黄金白银的软件
  4. PHP 读写 COOKIE
  5. 3000个最常用的英语单词
  6. 计算机办公模式是什么,华为Mate 10“电脑模式”告诉你什么是真正的“移动办公”...
  7. 基于 IPO 经济理论模型对目前 ICO 的理论研究和分析
  8. Cookie的SameSite属性
  9. excel+if函数+android,Excel中if函数多重条件的使用
  10. Science | 再野化植物微生物组——作物祖先微生物群可能为提高可持续的粮食生产提供了一种方法...