summaryrefslogtreecommitdiffhomepage
path: root/zh-cn
diff options
context:
space:
mode:
Diffstat (limited to 'zh-cn')
-rw-r--r--zh-cn/crystal-cn.html.markdown567
-rw-r--r--zh-cn/kotlin-cn.html.markdown33
-rw-r--r--zh-cn/make-cn.html.markdown262
3 files changed, 845 insertions, 17 deletions
diff --git a/zh-cn/crystal-cn.html.markdown b/zh-cn/crystal-cn.html.markdown
new file mode 100644
index 00000000..14805114
--- /dev/null
+++ b/zh-cn/crystal-cn.html.markdown
@@ -0,0 +1,567 @@
+---
+language: crystal
+filename: learncrystal-cn.cr
+contributors:
+ - ["Vitalii Elenhaupt", "http://veelenga.com"]
+ - ["Arnaud Fernandés", "https://github.com/TechMagister/"]
+translators:
+ - ["Xuty", "https://github.com/xtyxtyx"]
+lang: zh-cn
+---
+
+```crystal
+
+# 这是一行注释
+
+# 一切都是对象(object)
+nil.class #=> Nil
+100.class #=> Int32
+true.class #=> Bool
+
+# nil, false 以及空指针是假值(falsey values)
+!nil #=> true : Bool
+!false #=> true : Bool
+!0 #=> false : Bool
+
+# 整数类型
+
+1.class #=> Int32
+
+# 四种有符号整数
+1_i8.class #=> Int8
+1_i16.class #=> Int16
+1_i32.class #=> Int32
+1_i64.class #=> Int64
+
+# 四种无符号整数
+1_u8.class #=> UInt8
+1_u16.class #=> UInt16
+1_u32.class #=> UInt32
+1_u64.class #=> UInt64
+
+2147483648.class #=> Int64
+9223372036854775808.class #=> UInt64
+
+# 二进制数
+0b1101 #=> 13 : Int32
+
+# 八进制数
+0o123 #=> 83 : Int32
+
+# 十六进制数
+0xFE012D #=> 16646445 : Int32
+0xfe012d #=> 16646445 : Int32
+
+# 浮点数类型
+
+1.0.class #=> Float64
+
+# Crystal中有两种浮点数
+1.0_f32.class #=> Float32
+1_f32.class #=> Float32
+
+1e10.class #=> Float64
+1.5e10.class #=> Float64
+1.5e-7.class #=> Float64
+
+# 字符类型
+
+'a'.class #=> Char
+
+# 八进制字符
+'\101' #=> 'A' : Char
+
+# Unicode字符
+'\u0041' #=> 'A' : Char
+
+# 字符串
+
+"s".class #=> String
+
+# 字符串不可变(immutable)
+s = "hello, " #=> "hello, " : String
+s.object_id #=> 134667712 : UInt64
+s += "Crystal" #=> "hello, Crystal" : String
+s.object_id #=> 142528472 : UInt64
+
+# 支持字符串插值(interpolation)
+"sum = #{1 + 2}" #=> "sum = 3" : String
+
+# 多行字符串
+"这是一个
+ 多行字符串"
+
+# 书写带有引号的字符串
+%(hello "world") #=> "hello \"world\""
+
+# 符号类型
+# 符号是不可变的常量,本质上是Int32类型
+# 符号通常被用来代替字符串,来高效地传递特定的值
+
+:symbol.class #=> Symbol
+
+sentence = :question? # :"question?" : Symbol
+
+sentence == :question? #=> true : Bool
+sentence == :exclamation! #=> false : Bool
+sentence == "question?" #=> false : Bool
+
+# 数组类型(Array)
+
+[1, 2, 3].class #=> Array(Int32)
+[1, "hello", 'x'].class #=> Array(Int32 | String | Char)
+
+# 必须为空数组指定类型
+[] # Syntax error: for empty arrays use '[] of ElementType'
+[] of Int32 #=> [] : Array(Int32)
+Array(Int32).new #=> [] : Array(Int32)
+
+# 数组可以通过下标访问
+array = [1, 2, 3, 4, 5] #=> [1, 2, 3, 4, 5] : Array(Int32)
+array[0] #=> 1 : Int32
+array[10] # raises IndexError
+array[-6] # raises IndexError
+array[10]? #=> nil : (Int32 | Nil)
+array[-6]? #=> nil : (Int32 | Nil)
+
+# 使用负位置编号,从后往前访问数组
+array[-1] #=> 5
+
+# With a start index and size
+# 使用起始位置编号+大小
+array[2, 3] #=> [3, 4, 5]
+
+# 使用范围(range)访问数组
+array[1..3] #=> [2, 3, 4]
+
+# 向尾部添加元素
+array << 6 #=> [1, 2, 3, 4, 5, 6]
+
+# 删除尾部元素
+array.pop #=> 6
+array #=> [1, 2, 3, 4, 5]
+
+# 删除首部元素
+array.shift #=> 1
+array #=> [2, 3, 4, 5]
+
+# 检查元素是否存在与数组之中
+array.includes? 3 #=> true
+
+# 一种特殊语法,用来创建字符串数组或符号数组
+%w(one two three) #=> ["one", "two", "three"] : Array(String)
+%i(one two three) #=> [:one, :two, :three] : Array(Symbol)
+
+# 对于定义了`new`和`#<<`方法的类,可以用以下语法创建新对象
+set = Set{1, 2, 3} #=> [1, 2, 3]
+set.class #=> Set(Int32)
+
+# 以下代码与上方等同
+set = Set(typeof(1, 2, 3)).new
+set << 1
+set << 2
+set << 3
+
+# 哈希表类型(Hash)
+
+{1 => 2, 3 => 4}.class #=> Hash(Int32, Int32)
+{1 => 2, 'a' => 3}.class #=> Hash(Int32 | Char, Int32)
+
+# 必须为空哈希表指定类型
+{} # Syntax error
+{} of Int32 => Int32 # {}
+Hash(Int32, Int32).new # {}
+
+# 可以使用键(key)快速查询哈希表
+hash = {"color" => "green", "number" => 5}
+hash["color"] #=> "green"
+hash["no_such_key"] #=> Missing hash key: "no_such_key" (KeyError)
+hash["no_such_key"]? #=> nil
+
+# 检查某一键哈希表中是否存在
+hash.has_key? "color" #=> true
+
+# 对于定义了`#[]=`方法的类,可以使用以下语法创建对象
+class MyType
+ def []=(key, value)
+ puts "do stuff"
+ end
+end
+
+MyType{"foo" => "bar"}
+
+# 以上与下列代码等同
+tmp = MyType.new
+tmp["foo"] = "bar"
+tmp
+
+# 范围类型(Range)
+
+1..10 #=> Range(Int32, Int32)
+Range.new(1, 10).class #=> Range(Int32, Int32)
+
+# 包含或不包含端点
+(3..5).to_a #=> [3, 4, 5]
+(3...5).to_a #=> [3, 4]
+
+# 检查某一值是否在范围内
+(1..8).includes? 2 #=> true
+
+# 元组类型(Tuple)
+
+# 元组类型尺寸固定,不可变,储存在栈中
+# 元组可以有不同类型的对象组成
+{1, "hello", 'x'}.class #=> Tuple(Int32, String, Char)
+
+# 使用下标访问元组
+tuple = {:key1, :key2}
+tuple[1] #=> :key2
+tuple[2] #=> syntax error : Index out of bound
+
+# 将元组中的元素赋值给变量
+a, b, c = {:a, 'b', "c"}
+a #=> :a
+b #=> 'b'
+c #=> "c"
+
+# 命名元组类型(NamedTuple)
+
+tuple = {name: "Crystal", year: 2011} # NamedTuple(name: String, year: Int32)
+tuple[:name] # => "Crystal" (String)
+tuple[:year] # => 2011 (Int32)
+
+# 命名元组的键可以是字符串常量
+{"this is a key": 1} # => NamedTuple("this is a key": Int32)
+
+# 过程类型(Proc)
+# 过程代表一个函数指针,以及可选的上下文(闭包)
+# 过程通常使用字面值创建
+proc = ->(x : Int32) { x.to_s }
+proc.class # Proc(Int32, String)
+
+# 或者使用`new`方法创建
+Proc(Int32, String).new { |x| x.to_s }
+
+# 使用`call`方法调用过程
+proc.call 10 #=> "10"
+
+# 控制语句(Control statements)
+
+if true
+ "if 语句"
+elsif false
+ "else-if, 可选"
+else
+ "else, 同样可选"
+end
+
+puts "可以将if后置" if true
+
+# 将if作为表达式
+a = if 2 > 1
+ 3
+ else
+ 4
+ end
+
+a #=> 3
+
+# 条件表达式
+a = 1 > 2 ? 3 : 4 #=> 4
+
+# `case`语句
+cmd = "move"
+
+action = case cmd
+ when "create"
+ "Creating..."
+ when "copy"
+ "Copying..."
+ when "move"
+ "Moving..."
+ when "delete"
+ "Deleting..."
+end
+
+action #=> "Moving..."
+
+# 循环
+index = 0
+while index <= 3
+ puts "Index: #{index}"
+ index += 1
+end
+# Index: 0
+# Index: 1
+# Index: 2
+# Index: 3
+
+index = 0
+until index > 3
+ puts "Index: #{index}"
+ index += 1
+end
+# Index: 0
+# Index: 1
+# Index: 2
+# Index: 3
+
+# 更好的做法是使用`each`
+(0..3).each do |index|
+ puts "Index: #{index}"
+end
+# Index: 0
+# Index: 1
+# Index: 2
+# Index: 3
+
+# 变量的类型取决于控制语句中表达式的类型
+if a < 3
+ a = "hello"
+else
+ a = true
+end
+typeof a #=> (Bool | String)
+
+if a && b
+ # 此处`a`与`b`均为Nil
+end
+
+if a.is_a? String
+ a.class #=> String
+end
+
+# 函数(Functions)
+
+def double(x)
+ x * 2
+end
+
+# 函数(以及所有代码块)均将最末尾表达式的值作为返回值
+double(2) #=> 4
+
+# 在没有歧义的情况下,括号可以省略
+double 3 #=> 6
+
+double double 3 #=> 12
+
+def sum(x, y)
+ x + y
+end
+
+# 使用逗号分隔参数
+sum 3, 4 #=> 7
+
+sum sum(3, 4), 5 #=> 12
+
+# yield
+# 所有函数都有一个默认生成、可选的代码块(block)参数
+# 在函数中可以使用yield调用此代码块
+
+def surround
+ puts '{'
+ yield
+ puts '}'
+end
+
+surround { puts "hello world" }
+
+# {
+# hello world
+# }
+
+
+# 可将代码块作为参数传给函数
+# "&" 表示对代码块参数的引用
+def guests(&block)
+ block.call "some_argument"
+end
+
+# 使用星号"*"将参数转换成元组
+def guests(*array)
+ array.each { |guest| puts guest }
+end
+
+# 如果函数返回数组,可以将其解构
+def foods
+ ["pancake", "sandwich", "quesadilla"]
+end
+breakfast, lunch, dinner = foods
+breakfast #=> "pancake"
+dinner #=> "quesadilla"
+
+# 按照约定,所有返回布尔值的方法都以问号结尾
+5.even? # false
+5.odd? # true
+
+# 以感叹号结尾的方法,都有一些破坏性(destructive)行为,比如改变调用接收者(receiver)
+# 对于某些方法,带有感叹号的版本将改变调用接收者,而不带有感叹号的版本返回新值
+company_name = "Dunder Mifflin"
+company_name.gsub "Dunder", "Donald" #=> "Donald Mifflin"
+company_name #=> "Dunder Mifflin"
+company_name.gsub! "Dunder", "Donald"
+company_name #=> "Donald Mifflin"
+
+
+# 使用`class`关键字来定义类(class)
+class Human
+
+ # 类变量,由类的所有实例所共享
+ @@species = "H. sapiens"
+
+ # `name`的类型为`String`
+ @name : String
+
+ # 构造器方法(initializer)
+ # 其中@name、@age为简写,相当于
+ #
+ # def initialize(name, age = 0)
+ # @name = name
+ # @age = age
+ # end
+ #
+ # `age`为可选参数,如果未指定,则使用默认值0
+ def initialize(@name, @age = 0)
+ end
+
+ # @name的setter方法
+ def name=(name)
+ @name = name
+ end
+
+ # @name的getter方法
+ def name
+ @name
+ end
+
+ # 上述getter与setter的定义可以用property宏简化
+ property :name
+
+ # 也可用getter与setter宏独立创建getter与setter
+ getter :name
+ setter :name
+
+ # 此处的`self.`使`say`成为类方法
+ def self.say(msg)
+ puts msg
+ end
+
+ def species
+ @@species
+ end
+end
+
+
+# 将类实例化
+jim = Human.new("Jim Halpert")
+
+dwight = Human.new("Dwight K. Schrute")
+
+# 调用一些实例方法
+jim.species #=> "H. sapiens"
+jim.name #=> "Jim Halpert"
+jim.name = "Jim Halpert II" #=> "Jim Halpert II"
+jim.name #=> "Jim Halpert II"
+dwight.species #=> "H. sapiens"
+dwight.name #=> "Dwight K. Schrute"
+
+# 调用类方法
+Human.say("Hi") #=> 输出 Hi ,返回 nil
+
+# 带有`@`前缀的变量为实例变量
+class TestClass
+ @var = "I'm an instance var"
+end
+
+# 带有`@@`前缀的变量为类变量
+class TestClass
+ @@var = "I'm a class var"
+end
+# 首字母大写的变量为常量
+Var = "这是一个常量"
+Var = "无法再次被赋值" # 常量`Var`已经被初始化
+
+# 在crystal中类也是对象(object),因此类也有实例变量(instance variable)
+# 类变量的定义由类以及类的派生类所共有,但类变量的值是独立的
+
+# 基类
+class Human
+ @@foo = 0
+
+ def self.foo
+ @@foo
+ end
+
+ def self.foo=(value)
+ @@foo = value
+ end
+end
+
+# 派生类
+class Worker < Human
+end
+
+Human.foo #=> 0
+Worker.foo #=> 0
+
+Human.foo = 2 #=> 2
+Worker.foo #=> 0
+
+Worker.foo = 3 #=> 3
+Human.foo #=> 2
+Worker.foo #=> 3
+
+module ModuleExample
+ def foo
+ "foo"
+ end
+end
+
+# include <Module> 将模块(module)中的方法添加为实例方法
+# extend <Module> 将模块中的方法添加为类方法
+
+class Person
+ include ModuleExample
+end
+
+class Book
+ extend ModuleExample
+end
+
+Person.foo # => undefined method 'foo' for Person:Class
+Person.new.foo # => 'foo'
+Book.foo # => 'foo'
+Book.new.foo # => undefined method 'foo' for Book
+
+
+# 异常处理
+
+# 定义新的异常类(exception)
+class MyException < Exception
+end
+
+# 再定义一个异常类
+class MyAnotherException < Exception; end
+
+ex = begin
+ raise MyException.new
+rescue ex1 : IndexError
+ "ex1"
+rescue ex2 : MyException | MyAnotherException
+ "ex2"
+rescue ex3 : Exception
+ "ex3"
+rescue ex4 # 捕捉任何类型的异常
+ "ex4"
+end
+
+ex #=> "ex2"
+
+```
+
+## 参考资料
+
+- [官方网站](https://crystal-lang.org/)
+- [官方文档](https://crystal-lang.org/docs/overview/)
+- [在线运行代码](https://play.crystal-lang.org/#/cr)
+- [Github仓库](https://github.com/crystal-lang/crystal)
diff --git a/zh-cn/kotlin-cn.html.markdown b/zh-cn/kotlin-cn.html.markdown
index 5d655029..f6dcd847 100644
--- a/zh-cn/kotlin-cn.html.markdown
+++ b/zh-cn/kotlin-cn.html.markdown
@@ -22,7 +22,7 @@ package com.learnxinyminutes.kotlin
/*
Kotlin程序的入口点是一个"main"函数
-该函数传递一个包含任何命令行参数的数组。
+该函数传递一个包含所有命令行参数的数组。
*/
fun main(args: Array<String>) {
/*
@@ -67,10 +67,10 @@ fun helloWorld(val name : String) {
模板表达式从一个美元符号($)开始。
*/
val fooTemplateString = "$fooString has ${fooString.length} characters"
- println(fooTemplateString)
+ println(fooTemplateString) // => 输出 My String Is Here! has 18 characters
/*
- 当某个变量的值可以为 null 的时候,我们必须被明确指定它是可为空的。
+ 当某个变量的值可以为 null 的时候,我们必须明确指定它是可为空的。
在变量声明处的类型后面加上?来标识它是可为空的。
我们可以用?.操作符来访问可为空的变量。
我们可以用?:操作符来指定一个在变量为空时使用的替代值。
@@ -96,24 +96,24 @@ fun helloWorld(val name : String) {
println(hello()) // => Hello, world!
/*
- 用"vararg"关键字来修饰一个函数的参数来允许可变参数传递给该函数
+ 函数的可变参数可使用 "vararg" 关键字来修饰
*/
fun varargExample(vararg names: Int) {
println("Argument has ${names.size} elements")
}
- varargExample() // => Argument has 0 elements
- varargExample(1) // => Argument has 1 elements
- varargExample(1, 2, 3) // => Argument has 3 elements
+ varargExample() // => 传入 0 个参数
+ varargExample(1) // => 传入 1 个参数
+ varargExample(1, 2, 3) // => 传入 3 个参数
/*
- 当函数只包含一个单独的表达式时,大括号可以被省略。
- 函数体可以被指定在一个=符号后面。
+ 当函数只包含一个单独的表达式时,大括号可以省略。
+ 函数体可以写在一个=符号后面。
*/
fun odd(x: Int): Boolean = x % 2 == 1
println(odd(6)) // => false
println(odd(7)) // => true
- // 如果返回值类型可以被推断,那么我们不需要指定它。
+ // 如果返回值类型可以推断,那么我们不需要指定它。
fun even(x: Int) = x % 2 == 0
println(even(6)) // => true
println(even(7)) // => false
@@ -122,15 +122,14 @@ fun helloWorld(val name : String) {
fun not(f: (Int) -> Boolean) : (Int) -> Boolean {
return {n -> !f.invoke(n)}
}
- // 命名函数可以用::运算符被指定为参数。
+ // 普通函数可以用::运算符传入引用作为函数参数。
val notOdd = not(::odd)
val notEven = not(::even)
- // 匿名函数可以被指定为参数。
+ // lambda 表达式可以直接作为参数传递。
val notZero = not {n -> n == 0}
/*
- 如果一个匿名函数只有一个参数
- 那么它的声明可以被省略(连同->)。
- 这个参数的名字是"it"。
+ 如果一个 lambda 表达式只有一个参数
+ 那么它的声明可以省略(连同->),内部以 "it" 引用。
*/
val notPositive = not {it > 0}
for (i in 0..4) {
@@ -152,7 +151,7 @@ fun helloWorld(val name : String) {
注意,Kotlin没有"new"关键字。
*/
val fooExampleClass = ExampleClass(7)
- // 可以使用一个点号来调用成员函数。
+ // 可以使用一个点号来调用成员方法。
println(fooExampleClass.memberFunction(4)) // => 11
/*
如果使用"infix"关键字来标记一个函数
@@ -162,7 +161,7 @@ fun helloWorld(val name : String) {
/*
数据类是创建只包含数据的类的一个简洁的方法。
- "hashCode"、"equals"和"toString"方法将被自动生成。
+ "hashCode"、"equals"和"toString"方法将自动生成。
*/
data class DataClassExample (val x: Int, val y: Int, val z: Int)
val fooData = DataClassExample(1, 2, 4)
diff --git a/zh-cn/make-cn.html.markdown b/zh-cn/make-cn.html.markdown
new file mode 100644
index 00000000..4cdf1e63
--- /dev/null
+++ b/zh-cn/make-cn.html.markdown
@@ -0,0 +1,262 @@
+---
+language: make
+contributors:
+- ["Robert Steed", "https://github.com/robochat"]
+- ["Jichao Ouyang", "https://github.com/jcouyang"]
+translators:
+- ["Jichao Ouyang", "https://github.com/jcouyang"]
+filename: Makefile-cn
+lang: zh-cn
+---
+
+Makefile 用于定义如何创建目标文件, 比如如何从源码到可执行文件. 创建这一工具的目标是
+减少不必要的编译或者任务.是传说中的 Stuart Feldman 在 1976 年花了一个周末写出来的,
+而今仍然使用广泛, 特别是在 Unix 和 Linux 系统上.
+
+虽然每个语言可能都有相应的或多或少提供 make 的功能, 比如 ruby 的 rake, node 的 gulp, broccoli
+, scala 的 sbt 等等. 但是 make 的简洁与高效, 和只做一件事并做到极致的风格, 使其至今仍是无可替代的,
+甚至与其他构建工具一起使用也并无冲突.
+
+尽管有许多的分支和变体, 这篇文章针对是标准的 GNU make.
+
+```make
+# 这行表示注释
+
+# 文件名一定要交 Makefile, 大小写区分, 使用 `make <target>` 生成 target
+# 如果想要取别的名字, 可以用 `make -f "filename" <target>`.
+
+# 重要的事情 - 只认识 TAB, 空格是不认的, 但是在 GNU Make 3.82 之后, 可以通过
+# 设置参数 .RECIPEPREFIX 进行修改
+
+#-----------------------------------------------------------------------
+# 初级
+#-----------------------------------------------------------------------
+
+# 创建一个 target 的规则非常简单
+# targets : prerequisites
+# recipe
+# …
+# prerequisites(依赖) 是可选的, recipe(做法) 也可以多个或者不给.
+
+# 下面这个任务没有给 prerequisites, 只会在目标文件 file0.txt 文件不存在是跑
+file0.txt:
+ echo "foo" > file0.txt
+ # 试试 `make file0.txt`
+ # 或者直接 `make`, 因为第一个任务是默认任务.
+ # 注意: 即使是这些注释, 如果前面有 TAB, 也会发送给 shell, 注意看 `make file0.txt` 输出
+
+# 如果提供 prerequisites, 则只有 prerequisites 比 target 新时会执行
+# 比如下面这个任务只有当 file1.txt 比 file0.txt 新时才会执行.
+file1.txt: file0.txt
+ cat file0.txt > file1.txt
+ # 这里跟shell里的命令式一毛一样的.
+ @cat file0.txt >> file1.txt
+ # @ 不会把命令往 stdout 打印.
+ -@echo 'hello'
+ # - 意思是发生错误了也没关系.
+ # 试试 `make file1.txt` 吧.
+
+# targets 和 prerequisites 都可以是多个, 以空格分割
+file2.txt file3.txt: file0.txt file1.txt
+ touch file2.txt
+ touch file3.txt
+
+# 如果声明重复的 target, make 会给一个 warning, 后面会覆盖前面的
+# 比如重复定义 file2.txt 会得到这样的 warning
+# Makefile:46: warning: overriding commands for target `file2.txt'
+# Makefile:40: warning: ignoring old commands for target `file2.txt'
+file2.txt: file0.txt
+ touch file2.txt
+
+# 但是如果不定义任何 recipe, 就不会冲突, 只是多了依赖关系
+file2.txt: file0.txt file3.txt
+
+#-----------------------------------------------------------------------
+# Phony(假的) Targets
+#-----------------------------------------------------------------------
+
+# phony targets 意思是 tagets 并不是文件, 可以想象成一个任务的名字而已.
+# 因为不是文件, 无法比对是否有更新, 所以每次make都会执行.
+all: maker process
+
+# 依赖于 phony target 的 target 也会每次 make 都执行, 即使 target 是文件
+ex0.txt ex1.txt: maker
+
+# target 的声明顺序并不重要, 比如上面的 all 的依赖 maker 现在才声明
+maker:
+ touch ex0.txt ex1.txt
+
+# 如果定义的 phony target 与文件名重名, 可以用 .PHONY 显示的指明哪些 targets 是 phony
+.PHONY: all maker process
+# This is a special target. There are several others.
+
+# 常用的 phony target 有: all clean install ...
+
+#-----------------------------------------------------------------------
+# 变量与通配符
+#-----------------------------------------------------------------------
+
+process: file*.txt | dir/a.foo.b # 可以用通配符匹配多个文件作为prerequisites
+ @echo $^ # $^ 是 prerequisites
+ @echo $@ # $@ 代表 target, 如果 target 为多个, $@ 代表当前执行的那个
+ @echo $< # $< prerequisite 中的第一个
+ @echo $? # $? 需要更新的 prerequisite 文件列表
+ @echo $+ # $+ 所有依赖, 包括重复的
+ @echo $| # $| 竖线后面的 order-only prerequisites
+
+a.%.b:
+ @echo $* # $* match 的target % 那部分, 包括路径, 比如 `make dir/a.foo.b` 会打出 `dir/foo`
+
+# 即便分开定义依赖, $^ 依然能拿到
+process: ex1.txt file0.txt
+# 非常智能的, ex1.txt 会被找到, file0.txt 会被去重.
+
+#-----------------------------------------------------------------------
+# 模式匹配
+#-----------------------------------------------------------------------
+
+# 可以让 make 知道如何转换某些文件到别格式
+# 比如 从 svg 到 png
+%.png: %.svg
+ inkscape --export-png $^
+
+# 一旦有需要 foo.png 这个任务就会运行
+
+# 路径会被忽略, 所以上面的 target 能匹配所有 png
+# 但是如果加了路径, make 会找到最接近的匹配, 如果
+# make small/foo.png (在这之前要先有 small/foo.svg 这个文件)
+# 则会匹配下面这个规则
+small/%.png: %.svg
+ inkscape --export-png --export-dpi 30 $^
+
+%.png: %.svg
+ @echo 重复定义会覆盖前面的, 现在 inkscape 没用了
+
+# make 已经有一些内置的规则, 比如从 *.c 到 *.o
+
+#-----------------------------------------------------------------------
+# 变量
+#-----------------------------------------------------------------------
+# 其实是宏 macro
+
+# 变量都是字符串类型, 下面这俩是一样一样的
+
+name = Ted
+name2="Sarah"
+
+echo:
+ @echo $(name)
+ @echo ${name2}
+ @echo $name # 这个会被蠢蠢的解析成 $(n)ame.
+ @echo \"$(name3)\" # 为声明的变量或扩展成空字符串.
+ @echo $(name4)
+ @echo $(name5)
+# 你可以通过4种方式设置变量.
+# 按以下顺序由高到低:
+# 1: 命令行参数. 比如试试 `make echo name3=JICHAO`
+# 2: Makefile 里面的
+# 3: shell 中的环境变量
+# 4: make 预设的一些变量
+
+name4 ?= Jean
+# 问号意思是如果 name4 被设置过了, 就不设置了.
+
+override name5 = David
+# 用 override 可以防止命令行参数设置的覆盖
+
+name4 +=grey
+# 用加号可以连接 (中间用空格分割).
+
+# 在依赖的地方设置变量
+echo: name2 = Sara2
+
+# 还有一些内置的变量
+echo_inbuilt:
+ echo $(CC)
+ echo ${CXX)}
+ echo $(FC)
+ echo ${CFLAGS)}
+ echo $(CPPFLAGS)
+ echo ${CXXFLAGS}
+ echo $(LDFLAGS)
+ echo ${LDLIBS}
+
+#-----------------------------------------------------------------------
+# 变量 2
+#-----------------------------------------------------------------------
+
+# 加个冒号可以声明 Simply expanded variables 即时扩展变量, 即只在声明时扩展一次
+# 之前的等号声明时 recursively expanded 递归扩展
+
+var := hello
+var2 := $(var) hello
+
+# 这些变量会在其引用的顺序求值
+# 比如 var3 声明时找不到 var4, var3 会扩展成 `and good luck`
+var3 := $(var4) and good luck
+# 但是一般的变量会在调用时递归扩展, 先扩展 var5, 再扩展 var4, 所以是正常的
+var5 = $(var4) and good luck
+var4 := good night
+
+echoSEV:
+ @echo $(var)
+ @echo $(var2)
+ @echo $(var3)
+ @echo $(var4)
+ @echo $(var5)
+
+#-----------------------------------------------------------------------
+# 函数
+#-----------------------------------------------------------------------
+
+# make 自带了一些函数.
+# wildcard 会将后面的通配符变成一串文件路径
+all_markdown:
+ @echo $(wildcard *.markdown)
+# patsubst 可以做替换, 比如下面会把所有 markdown
+# 后缀的文件重命名为 md 后缀
+substitue: *
+ @echo $(patsubst %.markdown,%.md,$* $^)
+
+# 函数调用格式是 $(func arg0,arg1,arg2...)
+
+# 试试
+ls: *
+ @echo $(filter %.txt, $^)
+ @echo $(notdir $^)
+ @echo $(join $(dir $^),$(notdir $^))
+
+#-----------------------------------------------------------------------
+# Directives
+#-----------------------------------------------------------------------
+
+# 可以用 include 引入别的 Makefile 文件
+# include foo.mk
+
+sport = tennis
+# 一些逻辑语句 if else 什么的, 顶个写
+report:
+ifeq ($(sport),tennis)
+ @echo 'game, set, match'
+else
+ @echo "They think it's all over; it is now"
+endif
+
+# 还有 ifneq, ifdef, ifndef
+
+foo = true
+
+# 不只是 recipe, 还可以写在外面哟
+ifdef $(foo)
+bar = 'bar'
+endif
+
+hellobar:
+ @echo bar
+```
+
+### 资源
+
++ GNU Make 官方文档 [HTML](https://www.gnu.org/software/make/manual/) [PDF](https://www.gnu.org/software/make/manual/make.pdf)
++ [software carpentry tutorial](http://swcarpentry.github.io/make-novice/)
++ learn C the hard way [ex2](http://c.learncodethehardway.org/book/ex2.html) [ex28](http://c.learncodethehardway.org/book/ex28.html)