这一节让我们来了解一下那些“使用Clojure编程必须要掌握的东西”。

其实Clojure(以及其他lisp系语言)的学习曲线还是比较陡峭的。学习Clojure之前必须要掌握一些基础的数据结构和算法知识。

同像性

其实Lisp系语言的语法并没有什么可说的,因为Lisp系语言的代码实际上是由Lisp数据构成的。换句话说,当我们编写Lisp代码时,我们事实上是在描述一个数据结构。这种特性被称作同像性。例如,当我们在Lisp中调用函数时,实际上我们是写下了一个列表结构:

(+1 1) ; 函数调用

;=> 2

(class‘(+1 1)) ; 查看(+ 1 1)的类型

;=> clojure.lang.PersistentList

可以看到,(+ 1 1)的类型实际上是一个PersistentList。

因此,学习Lisp的语法其实就是在学习Lisp的数据表示。

同向性带来的好处是我们可以像操作数据结构一样操作代码,这为lisp系语言带来了其他语言不可比拟的元编程能力。

形式(Form)

lisp的语法单元被称为形式。形式指那些能够被读取器读入的代码片段。诸如数值、字符串、列表以及其他复合结构都是形式。相对的,一个没有闭合的列表不是一个形式。

在Clojure中,有一类特殊形式,比如用于定义变量的def,它们是语言的基础组成部分。

数值类型

和绝大多数语言一样,Clojure中自然也有数值类型,对数值类型求值,当然也会返回这个数值本身(废话!)

除了常见的整型和浮点数外,Clojure还支持任意精度实数和任意精度整数:

(class0.0000000000001M) ; 任意精度实数

;=> java.math.BigDecimal ; 它的实际类型

(class999999999999999999N) ; 任意精度整数

;=> java.math.Bigint

除此之外,Clojure还支持“有理数”类型:

(/1 3)

;=> 1/3 ; 除法的结果以分数形式表示

相较于浮点数,有理数类型不会损失精度,但会带来一定的性能损失。

数值计算

Clojure的数值计算与普通的函数调用并无不同:

(+1 2) ;=> 3

(-2 1) ;=> 1

(*2 2) ;=> 4

(/4 2) ;=> 2

(*(+1 2) 3) ;=> 9

有趣的是,这些函数都是可以接受任意数量的参数的:

(+1 2 3 4) ;=> 10

(apply *[1 2 3 4]) ;=> 24

(-1) ;=> -1

(+) ;=> 0

(*) ;=> 1

数值比较

数值比较在Clojure中也是函数:

(>2 1) ; => true

(=1 1) ;=> true

这些函数也可以接受任意参数,因此,我们可以用>=函数判断一个数列是否为降序:

(defndesc? [nums] (apply >=nums)) ; 定义函数desc?

;=> #'user/desc?

(desc? [4 3 2 1])

;=> true

注:在Clojure中有一个命名约定:返回值为布尔类型的函数应该以问号结尾。

布尔值与nil

布尔值只包含true和false两个值。在Clojure中,nil相当于java中的null。Clojure的真值判断遵循一个简单的规则:false、nil为假,其余值都为真。

!注意:在Clojure中,空列表不为假

条件判断

Clojure中自然也有分支跳转,需要用到一个叫 if的特殊形式:

(iftestthen else?)

if十分简单,当test为真时返回then,为假时返回else(如果有的话)。注意,当test为假时,else是不会被求值的。

(if(=(+1 1) 2)

"Math still works today!"

(println"Never happens"))

;=> "Math still works today!"

与Java、C语言的if不同,Clojure的if是具有返回值的。因此其实它相当于其他语言的三元运算符。

if中的then块只能包含一个形式,如果需要执行多条语句可以使用when:

(when(=(+1 1) 2)

(println"print something")

"Math still works today!")

;; print something

;=> "Math still works today!"

注意:when没有else块,如果判断条件不满足,when会返回nil。

许多语言都有if-else if-else的语言结构,在Clojure中可以使用cond实现同样的效果:

(defnspeak [x]

(cond

(=x :dog) "Woof!Woof!Woof!"

(=x :cat) "Mew~"

(=x :repeater) *1 ; *1是REPL的特殊变量,表示上一个在REPL中求得的值

:else nil)) ; :else会被求值为真,因此当上述条件都不满足时就会返回nil

(speak :dog) ; => "Woof!Woof!Woof!"

(speak :repeater) ; => "Woof!Woof!Woof!"

(speak :cat) ; => “Mew~”

(speak :repeater) ; => “Mew~”

(speak :monkey) ; => nil

Clojure中也有类似switch的结构case,上述例子可以用case进行改写:

(defnspeak2 [x]

(case x

:dog "Woof! Woof! Woof!"

:cat "Mew~"

:repeater *1

nil)) ; 最后一行表示默认情况

符号与变量

Clojure中的符号类似其他语言中的标识符。除了能够使用字母、数字、下划线(注:符号不能以数字开头)以外,Clojure符号还能够使用一些特殊符号,如+、-、*、/、、.等等。注意,除号(/)和句点(.)常被用于命名空间。

Clojure中可以使用特殊形式def声明一个变量:

(defmyname "Khellendros")

;=> #'user/myname

myname

;=> "Khellendros"

变量的初始值不是必须的:

(defno-value)

no-value

#object[clojure.lang.Var$Unbound 0xcc0a548 "Unbound: #'user/no-value"]

使用def定义的变量都是全局变量,使用特殊形式let可以定义局部量,这些局部量只能在let范围内使用:

(let[myage 22, mygender :male]

(do(println"Name: " myname)

(println"Age: " myage)

(println"Gender: " mygender)))

;; Name: Khellendros

;; Age: 22

;; Gender: :male

;=> nil

myage

;=> CompilerException java.lang.RuntimeException: Unable to resolve symbol: myage in this context, compiling:(null:0:0)

关键字

关键字类似于符号,不同之处在于,符号通常都会引用其他事物(比如变量和函数名),而关键字仅仅代表它本身。关键字的命名规则与符号类似,但必须以冒号(:)开头,且数字可用紧跟着冒号。

:a-keywd

;=> :a-keywd

:1+!

;=> :1+!

关键字最常见的用法是充当关联结构(如map和set)的键值。在Python和JavaScript中,我们通常会用字符串充当键值,然而,比较两个字符串是相当低效的做法,关键字相当于是单例对象,只需要比较它们的内存地址就可以了,效率显然要高很多。此外,关键字也可以用于表示枚举值。

字符与字符串

在Clojure中,用反斜杠后面紧跟着一个字符表示字符类型:

(class\a)

;=> java.lang.Character

而字符串和java一样用双引号表示:

(class“Hello”)

;=> java.lang.String

字符串中间可以换行:

“Hello

world!”

;=> “Hello\nworld!”

str函数用以将一个值转化为字符串:

(str1) ;=> “1”

str也可以接受多个参数,此时它会将参数转化为字符串后拼接起来,并且会跳过nil

(str1 2 nil 3) ;=> “123”

正则表达式

在Clojure中可以使用井号(#)加字符串的方式定义一个正则表达式:

(defwords #”\w+”)

也可以使用re-pattern函数,不过注意要对正则表达式中的特殊字符用双反斜杠(\\)转义:

(defwords2 (re-pattern\\w+))

使用re-seq可以找到字符串中所有的匹配项:

(re-seqwords “aaa bbb|ccc,ddd”)

;=> (“aaa” “bbb” “ccc” “ddd”)

我们可以在正则表达式中添加分组:

(defmiddle-part #"\w+\-(\w+)\-\w+")

;=> #'user/middle-part

(re-seqmiddle-part "aaa-AAA-aaa bbb-BBB-bbb ccc-CCC-ccc")

;=> (["aaa-AAA-aaa" "AAA"] ["bbb-BBB-bbb" "BBB"] ["ccc-CCC-ccc" "CCC"])

此时re-seq将返回一个二维序列。

复合类型

Clojure提供了一组功能非常强大的容器,包括列表(list)、向量(vector)、映射表(map)、集合(set),由于篇幅有限这里仅对它们做一些简单的介绍。

所有这些Clojure容器都是不可变的,当我们使用诸如replace这样的函数改变容器内的元素时,我们将得到一个新的容器,而旧容器始终保持不变。

也许你会觉得这样的做法十分低效:“每次我要对容器进行更改时都需要把整个容器都复制一遍?”。其实恰恰相反,Clojure容器是十分高效的。不可变也意味着更容易实现“共享”——新旧容器之间可以共享大部分内容,当我们对容器做出更改时,通常只会生成一个新的根结点而不用拷贝整个容器。

列表(list)是Clojure(以及各种Lisp系语言)中最常见的结构,不过在Clojure中它的主要作用不是作为容器而是用来表示函数(或宏)调用。列表以一对括号表示,列表中的元素以空格或逗号分隔。

(+1 1)

;=> 2

在Clojure中,最常用的容器是向量(vector)。向量以一对中括号([ ])表示:

[1 2 3]

;=> [1 2 3]

向量的一大特点是它支持高效的随机访问。虽然向量的底层不是数组(array),但它进行随机访问的效率和数组相差无几。

(nth[1 2 3] 1) ; => 2

(get[1 2 3] 1) ; => 2

向量本身也可以当做函数使用,效果等同于nth:

([1 2 3] 1) ;=> 2

!但是注意,向量不能当成集合来使用,对向量使用contains?函数将永远返回true,哪怕该元素其实并不存在。

(contains?[1 2 3] 0) ;=> true

映射表(map)常用来表示一些相互关联的键值对,它使用花括号({ })来定义。

(defme {:name "Khellendros", :age 22, :gender :male})

;=> #’user/me

!注意:映射表中不能出现重复的键

使用get函数可以根据键查找值:

(getme :name)

;=> "Khellendros"

映射表也可以直接当成函数使用,效果等同于调用get函数:

(me :gender)

;=> :male

此外,关键字也能当成函数使用:

(:age me)

;=> 22

使用关键字作为函数对映射表进行查询,是Clojure的一种惯用法。

映射表的键可以是任意类型,同时同一个映射表的键的类型也可以各不相同:

(defmess-map {

:name "@#$$%",

[1 2 3] "aaa",

"?" 233 } )

;=> #'user/mess-map

(mess-map [1 2 3])

;=> "aaa"

(mess-map "?")

;=> 233

集合(set)相当于键和值相同的映射表。集合使用井号加花括号(#{ })表示,集合的内容不能重复。

(defimg-exts #{"jpg" "gif" "png" "bmp"})

;=> #'user/img-exts

集合通常用来判断其是否包含某个元素:

(contains?img-exts "jpg")

;=> true

集合自身也可以当做函数使用,效果等同于get

(img-exts "jpg")

;=> "jpg"

(img-exts "txt")

;=> nil

解构

复合类型可以进行解构。复合类型的构造可以看做是将多个量聚合成一个,而解构则是构造的逆过程,可以将复合解构拆解成多个量。

解构可以在许多地方发生,这里先以上面提到过的let为例:

(defnums [1 2 3])

;=> #’user/nums

(let[[a b c] nums] ; 解构nums

(+a b c))

;=> 6

可以看到,向量nums的第0、第1、第2项分别被绑定到了变量a、b、c上。

我们可以只取列表的前n项而忽略余项:

(defnatural-nums (iterate inc0)) ; 表示全体自然数

;=> #'user/natural-nums

(let[[a b c] natural-nums] (+a b c))

;=> 3

在这里natural-nums表示全体自然数的总集,因此它是一个无限长的序列,但因为我们只取其前三项,因此不会出现无限循环。

如果要只区第2项,忽略第0和第1项可以这么写:

(let[[_ _ a] natural-nums] a)

;=> 2

下划线(_)是一个合法的符号名,使用下划线忽略某些我们不关心的值是一种惯用法。注意这里下划线被绑定了两次,因此它最终的值是1而不是0。

:as命令可以将整个结构绑定到一个局部量上:

(let[[a b c :as all] nums] (str"Sum of: " all " = " (+a b c)))

;=> "Sum of: [1 2 3] = 6"

除了向量以外,映射表也可以进行绑定:

(let[{name:name, age :age} me] (str name" is " age " years old."))

:=> "Khellendros is 22 years old."

这种要把键名打两遍的做法略显繁琐,我们可以使用:keys命令进行简化:

(let[{:keys [nameage]} me] (str name“ is “ age “ years old.”))

:=> "Khellendros is 22 years old."

除了let以外,其他可以绑定局部量的位置都可以进行解构,包括但不限于函数参数,

loop,for等(见下文)。

函数

Clojure中有许多定义函数的方式,最常见的是使用宏defn:

(defnhello ;定义函数

"Say hello to someone." ;文档说明

[name] ;函数参数

(str"Hello, " name"!")) ;函数体

;=> #'user/hello

(hello "World")

;=> "Hello, World!"

函数可以有多个参数列表和函数体,此时各个参数列表的参数数量需要各不相同:

(defnvec-of

([a] [a])

([a b] [a b]))

;=> #'user/vec-of

(vec-of 1)

;=> [1]

(vec-of 1 2)

;=> [1 2]

在函数的参数声明处也可以对参数进行解构:

(defnthird [[_ _ x]] x)

;=> #'user/first3

(third natural-nums)

;=> 2

代码块

函数体只能包含一个形式,如果我们要执行多个表达式怎么办呢?使用特殊形式do可以解决这个问题:

(do

(println"first")

(println"second")

"not return"

"return")

;;first

;;second

;=> "return"

block会将最后一个形式的值当做返回值。

匿名函数

一些函数会使用一个回调函数作为参数,回调函数通常都只有一两行代码,如果我们懒得给它们起名字,可以使用匿名函数。匿名函数使用fn定义:

(defdouble-n (fn[n] (*n 2)))

;=> #’user/double-n

(double-n 10)

;=> 20

匿名函数还有一种简写形式,称作原位函数:

(defdouble-n-2 #(*% 2))

;=> #’user/double-n-2

(double-n-2 10)

;=> 20

原位函数使用井号加括号(#( ))定义,其中%[n]表示第n个函数参数,%1表示第一个参数,%2表示第二个……以此类推。单独一个%等价于%1

递归

Clojure不提供类似java的while和for循环,需要进行迭代时可以使用递归。在Clojure中可以使用特殊形式recur进行尾递归:

(defncount-down [n]

(when(pos?n) ; 如果n是正数就继续执行,否则返回nil

(printlnn) ; 输出n

(recur (decn)))) ; 递减n,然后递归调用count-down

;=> #'user/count-down

(count-down 10)

;;10

;;9

;;8

;;…

;;1

;=> nil

recur类似于C语言的goto语句,“跳转点”默认为recur所在的函数开始处,我们也可以用特殊形式loop自定义跳转点:

(defncount[start end]

(loop[n start, end end]

(when(

(printlnn)

(recur (incn) end))))

;=> #'user/count

(count0 10)

;; 0

;; 1

;; 2

;; …

;; 9

;=> nil

在loop中,我们绑定了两个局部量:n和end,当recur被调用时,他会将参数传递给loop绑定的变量。

注意:recur只能放置在一个函数或loop的出口处

遍历

虽然没有传统意义上的while和for循环,但是Clojure提供了类似java的foreach循环的设施——doseq和for。为了方便理解,我们会通过对比java代码来对它们进行说明。

doseq主要用于产生副作用(比如输出到控制台)。我们通过一个实际的例子来讲解一下doseq的用法:

(doseq[n [1 2 3]] ; 将列表[1 2 3]中的每一个元素依次绑定到n

(printlnn))

;; 1

;; 2

;; 3

;=> nil

下面是功能相同的java代码:

List nums =Arrays.asList(1, 2, 3);

for(intnum : nums) {

System.out.println(num);

}

doseq还能对多个结构进行遍历:

(doseq[m [1 2], n [3 4]]

(println

(strm " + " n " = " (+m n))))

;;1 + 3 = 4

;;1 + 4 = 5

;;2 + 3 = 5

;;2 + 4 = 6

;=> nil

上述例子清晰的展示了doseq是如何对两个向量进行遍历的。以下是等效的java代码:

List nums1 = Arrays.asList(1, 2);

List nums2 = Arrays.asList(3, 4);

for (int m : nums1) {

for (int n : nums2) {

String tmp = m + " + " + n + " = " + (m + n);

System.out.println(tmp);

}

}

for的功能比doseq更为强大,它拥有遍历、过滤、变换等多种功能。

; 求笛卡尔积

(for[m [1 2], n [\a \b]] [m n])

;=> ([1 \a] [1 \b] [2 \a] [2 \b])

; 变换

(for[num [1 2 3 4]] (incnum))

;=> (2 3 4 5)

;过滤出偶数

(for[n (range0 10) :when (even? n)] n)

;=> (0 2 4 6 8)

遍历映射表

对映射表进行遍历时,会将映射表转化为二维序列:

(seqme)

([:name "Khellendros"] [:age 22] [:gender :male])

因此我们可以像操作普通二维序列一样遍历映射表。

需要注意的是,Clojure映射表的默认实现是哈希表,因此元素的遍历顺序是无法预期的。

与Java互操作

在Clojure中可以使用已有的java类库,包括调用静态方法,构造类实例对象,调用方法,读取、设置字段值等。

调用静态方法/静态字段

可以用 (类名/静态方法名 [方法参数…])的形式调用静态方法和静态字段:

(Math/PI)

;=> 3.141592653589793

(Math/abs -1)

;=> 1

构造实例对象

(类名. [构造方法参数…]) 或者 (new 类名 [构造方法参数…])可以构造实例对象:

(defnums (java.util.ArrayList.))

;(def nums (new java.util.ArrayList))

;=> #’user/nums

nums

;=> []

方法调用

使用 (.方法名 对象 [方法参数…])调用方法:

(.add nums 1)

;=> true

nums

;=> [1]

读取、设置字段

读取字段的方法是 (.-字段名 对象):

(defpoint (java.awt.Point. 0 1))

;=> #’user/point

(.-x point)

;=> 0

使用set!函数可以设置字段值。

(set! (.-x point) 2)

;=> 2

(.-x point)

;=> 2

注:在Clojure中,以!结尾的函数往往意味着其会带来副作用(比如set!会改变对象的属性)。我们应该审慎的使用这些函数。

小试牛刀:index-of函数

我们上面提到contains?函数对向量无效,我们不妨自己实现一个功能类似的函数index-of。

index-of函数使用起来应该是这样的:

(index-of [1 1 4 5 1 4] 4)

;=> (2 5)

(index-of [1 1 4 5 1 4] 0)

;=> ()

如果序列内包含我们想要查找的目标,index-of会返回由所有匹配项的下表组成的序列。如果没有找到则返回空序列。

首先,我们需要将列表项与其对应的下标关联在一起。还记得我们之前定义的natural-nums吗?把它和向量结合起来就可以了:

(defnindexed-vec [vec]

(map vectornatural-nums vec))

;=> #’user/indexed-vec

(indexed-vec [\a \b \c \d])

;=> ([0 \a] [1 \b] [2 \c] [3 \d])

map函数用于将一个函数应用到一个序列的每一项上,例如:

(map inc[1 2 3])

;=> (2 3 4)

如果传递给map两个序列,那么它就可以通过一个二元函数将两个序列结合起来:

(map +[1 2 3] [3 2 1])

;=> (4 4 4)

同理,如果传入3个序列就要使用一个三元函数,以此类推。如果两个序列的长度不一致,较长的序列会被“截断”。

而vector函数的作用自然是构造一个向量,因此,(map vector natural-nums vec)就会把一个自然数序列(代表下标)和vec像拉链一样“拉”在一起。现在向量已经和下标关联起来了,我们就可以使用for简单的实现index-of了。

(defnindex-of [vec item]

(for[[indexvalue] (indexed-vec vec) :when (=value item)] index) )

;=> #’user/index-of

(index-of [1 1 4 5 1 4] 4)

;=> (2 5)

(index-of [1 1 4 5 1 4] 0)

;=> ()

更进一步?

index-of工作的很好,但还不够通用。如果对映射表、集合都能用通用的接口进行调用就好了。

pos 函数(通用版本的index-of)使用起来应该是这样的:

(pos [1 2 3 1] 1) ;=> (0 3)

(pos {:a 1, :b 2, :c 1} 1) ;=>(:a :c)

(pos #{1 2 3} 0) ;=> ()

要做到这一点,首先我们需要定义一个通用版本的indexed:

(defnindexed [xs]

(cond

(map?xs) (seqxs)

(set? xs) (seqxs)

:else (indexed-vec xs)))

pos相较于index-of只是把indexed-vec换成了indexed:

(defnpos [xs item]

(for[[indexvalue] (indexed xs) :when (=item value)] index))

再进一步,我们完全可以把item参数换成一个判断函数。来看看这个最终版本的pos-if吧:

(defnpos-if [xs pred]

(for[[indexvalue] (indexed xs) :when (pred value)] index))

;=> #’user/pos-if

(pos-if [1 2 3 1] #(>%1 1))

;=> (1 2)

clojure java math_Clojure学习笔记 02.快速上手相关推荐

  1. [原创]java WEB学习笔记02:javaWeb开发的目录结构

    本博客为原创:综合 尚硅谷(http://www.atguigu.com)的系统教程(深表感谢)和 网络上的现有资源(博客,文档,图书等),资源的出处我会标明 本博客的目的:①总结自己的学习过程,相当 ...

  2. Python学习笔记---day02快速上手

    day02快速上手 课程目标:学习Python最基础的语法知识,可以用代码快速实现一些简单的功能 课程概要: 初识编码(密码本) 编程体验 输出 初识数据类型 变量 注释 输入 条件语句 1. 编码 ...

  3. JavaEE学习笔记-SpringBoot快速上手、热部署、乱码问题、部分注解解释

    SpringBoot快速上手 一.快速创建SpringBoot应用 1.1利用IDEA提供的Spring Initializr创建Spring Boot应用 1.2Spring Boot生成的项目结构 ...

  4. oracle 快速上手,Oracle学习笔记:快速上手

    开启服务:先开启Lisenterner服务 再开启ORCL服务 oracle登录 用户: - 管理员 sysDBA - 无密码 - 系统用户 system - manager - 普通用户 scott ...

  5. Java Web学习笔记02:在Intellij里创建Web项目

    文章目录 一.利用Intellij创建Web项目 1.创建Java项目HelloWorld 2.添加Web服务器配置

  6. 2019年Java Web学习笔记目录

    Java Web学习笔记目录 1.Java Web学习笔记01:动态网站初体验 2.Java Web学习笔记02:在Intellij里创建Web项目 3.Java Web学习笔记03:JSP元素 4. ...

  7. Servlet和HTTP请求协议-学习笔记02【Servlet_体系结构与urlpartten配置、HTTP请求协议】

    Java后端 学习路线 笔记汇总表[黑马程序员] Servlet和HTTP请求协议-学习笔记01[Servlet_快速入门-生命周期方法.Servlet_3.0注解配置.IDEA与tomcat相关配置 ...

  8. XML学习笔记02【xml_解析】

    Java后端 学习路线 笔记汇总表[黑马程序员] XML学习笔记01[xml_基础.xml_约束][day01] XML学习笔记02[xml_解析][day01] 目录 03 xml_解析 xml_解 ...

  9. Bootstrap学习笔记02【全局CSS样式、组件和插件、案例_黑马旅游网_首页】

    Java后端 学习路线 笔记汇总表[黑马程序员] Bootstrap学习笔记01[快速入门.栅格布局][day01] Bootstrap学习笔记02[全局CSS样式.组件和插件.案例_黑马旅游网][d ...

最新文章

  1. 一阶暂态电路三要素法和三种响应
  2. Mac解决终端显示乱码
  3. 14条Yahoo(雅虎)十四条优化原则【转】
  4. java json jar包_jsonobject jar包下载
  5. 使用javascript模拟常见数据结构(四)
  6. 处理入参_看看优秀的程序员是如何处理NPE的
  7. 从hadoop-0.20.2升级到hadoop-1.0.3
  8. (35)FPGA面试题FPGA工程师努力的方向
  9. 今天有个销售员在问我:“自己每天都有在学习,但是为什么感觉没什么用,进步不大。”
  10. java 小球运动轨迹_java怎么实现小球的运动轨迹
  11. 开源漫画服务器Mango
  12. 解决阿里云ECS服务器下载 git 资源慢的问题
  13. 维基解密再爆猛料:CIA利用漏洞入侵全球数十亿个人电子设备
  14. 《利用Python进行数据分析》第七章——数据清洗与准备
  15. 网络 — MB/s、Mb/s、Mbps、Mbit/s、Kbps
  16. Python之解决tkinter.PhotoImage不显示图片的问题 Python3
  17. 性能之巅:常用性能分析方法
  18. python数据可视化之Matplotlib
  19. android仿今日头条个人中心页面
  20. 计算机网络-常用英文简写大全

热门文章

  1. 股票配资中的穿仓是什么意思
  2. 银行数据仓库体系实践(16)--数据应用之财务分析
  3. 工厂人员定位系统如何守护工厂安全生产“生命线”?
  4. 计算机键盘五大根键,Windows2000、XP、2003五大根键重点解析.doc
  5. 在RNS表示下如何做BGV的modulus switching
  6. 如何优化百万级别数据导出(excel 文件)
  7. RH436之资源与资源组
  8. sql server 2008主键id自增设置
  9. 架构系列---饿了么MySQL异地多活的数据双向复制
  10. 分享几个实用的代码片段(附代码例子)