文章目录
  1. 1. 说明
  2. 2. 语法教程
    1. 2.1. 注释
    2. 2.2. 变量类型
      1. 2.2.1. Nil
      2. 2.2.2. 布尔值
      3. 2.2.3. 整数
      4. 2.2.4. 浮点数
      5. 2.2.5. 字符
      6. 2.2.6. 字符串
        1. 2.2.6.1. 转义
        2. 2.2.6.2. 插值
        3. 2.2.6.3. 百分符字符串
        4. 2.2.6.4. 字符串允许换行
        5. 2.2.6.5. Heredoc
      7. 2.2.7. 符号
      8. 2.2.8. 数组
        1. 2.2.8.1. 带百分号的数组
        2. 2.2.8.2. Array风格的数组表示方式
      9. 2.2.9. 哈希
        1. 2.2.9.1. 哈希风格的数据类型
      10. 2.2.10. 范围
      11. 2.2.11. 正则
      12. 2.2.12. 元组
      13. 2.2.13. 带名称的元组
      14. 2.2.14. 过程定义
        1. 2.2.14.1. 调用Proc
      15. 2.2.15. 赋值
        1. 2.2.15.1. 多次赋值
    3. 2.3. 控制流
    4. 2.4. 文件包含
    5. 2.5. 类和方法
      1. 2.5.1. 实例化,初始化和内存分配
      2. 2.5.2. 方法和实例属性
      3. 2.5.3. 类型推断
      4. 2.5.4. 联合类型
      5. 2.5.5. 方法重载,多态
      6. 2.5.6. 默认值和 命名参数
      7. 2.5.7. Splats and tuples
      8. 2.5.8. Double splats and named tuples
      9. 2.5.9. 静态类的属性
    6. 2.6. 模块
    7. 2.7. 泛型
    8. 2.8. 结构体
    9. 2.9. 常量
      1. 2.9.1. 内置常量
    10. 2.10. 枚举类型
    11. 2.11. 进程和块
      1. 2.11.1. yield参数
      2. 2.11.2. 捕获代码块
      3. 2.11.3. block的转发(Block forwarding)
      4. 2.11.4. Closures
    12. 2.12. 别名 (alias)
  3. 3. 异常处理
    1. 3.1. 抛出异常
    2. 3.2. 自定义异常
    3. 3.3. 处理异常
    4. 3.4. 类型定义语法参考
    5. 3.5. 类型反射
      1. 3.5.1. is_a?
      2. 3.5.2. nil?
      3. 3.5.3. responds_to?
      4. 3.5.4. as
      5. 3.5.5. as?
      6. 3.5.6. typeof
    6. 3.6.
      1. 3.6.1. 作用域
      2. 3.6.2. 插值
      3. 3.6.3. 宏调用
      4. 3.6.4. 条件语句
      5. 3.6.5. 迭代
      6. 3.6.6. 宏方法定义
      7. 3.6.7. 钩子
      8. 3.6.8. 变量刷新
    7. 3.7. 属性
    8. 3.8. 底层操作(Low-level primitives )
    9. 3.9. 编译时标志(Compile-time flags )
    10. 3.10. C语言绑定(C bindings )

说明

本节内容较多,老司机打算用最简洁的语言带你快速飙车,看完保证不翻车^_^
友情提示:先装好icr 以方便验证及练习 https://github.com/crystal-community/icr

语法教程

注释

使用#做单行注释,目前仅支持单行。

1
# This is a comment

变量类型

Nil

代表空值, 类似其它语言中的null

布尔值

仅有两值 true, false

整数

分别有4个无符号整型 和4个整型
Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, UInt64 , 附表

1
2
3
4
5
6
7
8
9
Type	Length	Minimum Value	Maximum Value
Int8 8 -128 127
Int16 1632,768 32,767
Int32 322,147,483,648 2,147,483,647
Int64 64263 263 - 1
UInt8 8 0 255
UInt16 16 0 65,535
UInt32 32 0 4,294,967,295
UInt64 64 0 264 - 1

在不带后缀的情况下,整型匹配的范围是Int32 ~ Int64 和 UInt64 ,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1      # Int32

1_i8 # Int8
1_i16 # Int16
1_i32 # Int32
1_i64 # Int64

1_u8 # UInt8
1_u16 # UInt16
1_u32 # UInt32
1_u64 # UInt64

+10 # Int32
-20 # Int32

2147483648 # Int64
9223372036854775808 # UInt64

后缀前面的 是可选的, 能使代码更具可读性:

1
1_000_000 # 好过于 1000000

二进制数以0b开头 :

1
0b1101 # == 13

八进制以0o开头:

1
0o123 # == 83

十六进制以0x开头

1
2
0xFE012D # == 16646445
0xfe012d # == 16646445

浮点数

有两种 Float32 和 Float64, 没有符号限制, 可以+/- ,跟整型一样可以加后缀 没有后缀默认为 Float64

1
2
3
4
5
6
7
8
9
10
1.0      # Float64
1.0_f32 # Float32
1_f32 # Float32

1e10 # Float64
1.5e10 # Float64
1.5e-7 # Float64

+1.3 # Float64
-0.5 # Float64

字符

一个字符代表一个32位的Unicode code point, 使用单引号创建

1
2
3
4
5
'a'
'z'
'0'
'_'
'あ'

反斜杠是一个特殊的字符, 可以用于转义或正则 也可以用于表达一个16进制整数在unicode codepoint中的字符
A backslash denotes a special character, which can either be a named escape sequence or a numerical representation of a unicode codepoint.

Available escape sequences:

1
2
3
4
5
6
7
8
9
10
11
'\'' # single quote
'\\' # backslash
'\b' # backspace
'\e' # escape
'\f' # form feed
'\n' # newline
'\r' # carriage return
'\t' # tab
'\v' # vertical tab
'\uNNNN' # hexadecimal unicode character
'\u{NNNN...}' # hexadecimal unicode character

(此处翻车)A backslash followed by a u denotes a unicode codepoint. It can either be followed by exactly four hexadecimal characters representing the unicode bytes (\u0000 to \uFFFF) or a number of one to six hexadecimal characters wrapped in curly braces (\u{0} to \u{10FFFF}.

1
2
3
'\u0041' # => 'A'
'\u{41}' # => 'A'
'\u{1F52E}' # => '🔮'

字符串

一个字符串代表一个不可更改的UTF8字符序列,使用双引号包含。

转义

A backslash denotes a special character inside a string, which can either be a named escape sequence or a numerical representation of a unicode codepoint.

1
2
3
4
5
6
7
8
9
10
11
12
13
"\"" # double quote
"\\" # backslash
"\b" # backspace
"\e" # escape
"\f" # form feed
"\n" # newline
"\r" # carriage return
"\t" # tab
"\v" # vertical tab
"\NNN" # octal ASCII character
"\xNN" # hexadecimal ASCII character
"\uNNNN" # hexadecimal unicode character
"\u{NNNN...}" # hexadecimal unicode character

A backslash followed by at most three digits denotes a code point written in octal:

1
2
3
4
"\101" # => "A"
"\123" # => "S"
"\12" # => "\n"
"\1" # string with one character with code point 1

A backslash followed by a u denotes a unicode codepoint. It can either be followed by exactly four hexadecimal characters representing the unicode bytes (\u0000 to \uFFFF) or a number of one to six hexadecimal characters wrapped in curly braces (\u{0} to \u{10FFFF}.

1
2
3
"\u0041" # => "A"
"\u{41}" # => "A"
"\u{1F52E}" # => "🔮"

One curly brace can contain multiple unicode characters each separated by a whitespace.

1
"\u{48 45 4C 4C 4F}" # => "HELLO"

插值

字符串在运行时允许嵌入表达式

1
2
3
a = 1
b = 2
"sum: #{a} + #{b} = #{a + b}" # => "sum: 1 + 2 = 3"

字符串中允许插入任何表达式,但为了保证程序可读性,表达式尽量简短。
在#前加入反斜杠可以禁止插值, 也可以使用表达式: %q()

1
2
"\#{a + b}"  # => "#{a + b}"
%q(#{a + b}) # => "#{a + b}"

通过调用String::Builder以及在每个#{…}之后调用Object#to_s(IO)实现字符串插值。表达式”sum: #{a} + #{b} = #{a + b}”与下方的等价

1
2
3
4
5
6
7
8
String::Builder.new do |io|
io << "sum: "
io << a
io << " + "
io << b
io << " = "
io << a + b
end

百分符字符串

除了双引号之外,字符串还支持百分号(%)+一双分隔符的形式。可以使用的分隔符有(),[],{},<>,以及|| 。这可以用来处理字符串中带有双引号的情况。

1
2
3
4
5
%(hello ("world")) # => "hello (\"world\")"
%[hello ["world"]] # => "hello [\"world\"]"
%{hello {"world"}} # => "hello {\"world\"}"
%<hello <"world">> # => "hello <\"world\">"
%|hello "world"| # => "hello \"world\""

%q表示不转义插值, %Q则等价于%

1
2
3
name = "world"
%q(hello \n #{name}) # => "hello \\n \#{name}"
%Q(hello \n #{name}) # => "hello \n world"

字符串允许换行
1
2
"hello
world" # => "hello\n world"

一个字符串可以被分割成多行, 只需在每行的末尾加上反斜杠

1
2
3
"hello " \
"world, " \
"no newlines" # same as "hello world, no newlines"

1
2
3
"hello \
world, \
no newlines" # same as "hello world, no newlines"
Heredoc

heredoc以 <<- 紧跟一个标识符开始, 以标识符开头的一个新行作为结束。

1
2
3
4
5
<<-XML
<parent>
<child />
</parent>
XML

heredoc中允许调用方法或使用函数,变量等

1
2
3
4
5
6
7
8
9
10
11
<<-SOME
hello
SOME.upcase # => "HELLO"

def upcase(string)
string.upcase
end

upcase(<<-SOME
hello
SOME) # => "HELLO"

如果要在heredoc中禁用转义和插值,可以在定义heredoc的标识符上加上单引号

1
2
3
<<-'HERE'
hello \n #{world}
HERE # => "hello \n #{world}"

符号

符号是一个被命名的常量,并且不能用整数赋值 。在内部,一个符号被表示成一个 Int32。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
:hello
:good_bye

# With spaces and symbols
:"symbol with spaces"

# Ending with question and exclamation marks
:question?
:exclamation!

# For the operators
:+
:-
:*
:/
:==
:<
:<=
:>
:>=
:!
:!=
:=~
:!~
:&
:|
:^
:~
:**
:>>
:<<
:%
:[]
:[]?
:[]=
:<=>
:===

数组

数组是一个带数字索引的某个数据类型(type T)的值的一个集合。用方括号括起,用逗号分隔 。

1
[1, 2, 3]

数组的类型可以是单一的 或多种类型的混合。

1
2
[1, 2, 3]          # => Array(Int32)
[1, "hello", 'x'] # => Array(Int32 | String | Char)

规定单一类型的数组可以在数组定义时的末尾加上 “of type” ,空数组必须指明类型。

1
2
3
4
5
6
array_of_numbers = [1, 2, 3] of Number  # => Array(Number)
array_of_numbers + [0.5] # => [1, 2, 3, 0.5]

array_of_int_or_string = [1, 3, 4] of Int32 | String # => Array(Int32 | String)
array_of_int_or_string + ["foo"] # => [1, 2, 3, "foo"]
[] of Int32 # => Array(Int32).new

带百分号的数组

可以用带百分号的方法来创建字符串数组和符号数组。

1
2
%w(one two three)  # => ["one", "two", "three"]
%i(one two three) # => [:one, :two, :three]

Array风格的数组表示方式

使用大括号表示,元素之间使用逗号分隔。

1
Array{1, 2, 3}

这种写法适用于任意类型的数组。
This literal can be used with any type as long as it has an argless constructor and responds to <<.

1
2
IO::Memory{1, 2, 3}
Set{1, 2, 3}

对于像IO::Memory这样不寻常的类型,等价于

1
2
3
4
array_like = IO::Memory.new
array_like << 1
array_like << 2
array_like << 3

对于像Set这样的普通类型, the generic type T is inferred from the types of the elements in the same way as with the array literal. The above is equivalent to:

1
2
3
4
array_like = Set(typeof(1, 2, 3)).new
array_like << 1
array_like << 2
array_like << 3

类型的参数可以作为类型名称的一部分显式的指定。

1
Set(Number) {1, 2, 3}

哈希

1
2
{1 => 2, 3 => 4}     # Hash(Int32, Int32)
{1 => 2, 'a' => 3} # Hash(Int32 | Char, Int32)

哈希的键和值都可以包含多种数据类型。当创建一个空的哈希表时 ,必须分别指定键和值的数据类型。

1
2
{} of Int32 => Int32 # 等价于 Hash(Int32, Int32).new
{} # 此种玩法将报错

哈希风格的数据类型

你可以使用哈希数据跟其它数据类型一起玩耍。as long as they define an argless new method and a []= method:

1
MyType{"foo" => "bar"}

If MyType is not generic, 上面等价于

1
2
3
tmp = MyType.new
tmp["foo"] = "bar"
tmp

If MyType is generic,上面等价于

1
2
3
tmp = MyType(typeof("foo"), typeof("bar")).new
tmp["foo"] = "bar"
tmp

一般数据类型也可以这么搞

1
MyType(String, String) {"foo" => "bar"}

范围

1
2
x..y  # 包含末尾, in mathematics: [x, y]
x...y # 不包含末尾, in mathematics: [x, y)

正则

1
2
3
foo_or_bar = /foo|bar/
heeello = /h(e+)llo/
integer = /\d+/

正则使用斜杠分隔 ,并且使用 PCRE语法 。
可以跟这些参数

1
2
3
4

i: ignore case (PCRE_CASELESS)
m: multiline (PCRE_MULTILINE)
x: extended (PCRE_EXTENDED)

例子

1
2
3
r = /foo/imx
slash = /\//
r = %r(regex with slash: /) # 这是另一种写法

元组

创建和使用方法如下 , 创建空元组可以使用 Tuple.new

1
2
3
4
tuple = {1, "hello", 'x'} # Tuple(Int32, String, Char)
tuple[0] #=> 1 (Int32)
tuple[1] #=> "hello" (String)
tuple[2] #=> 'x' (Char)

要规定元组的数据类型,可以这么玩

1
2
3
4
5
# The type denoting a tuple of Int32, String and Char
Tuple(Int32, String, Char)

# An array of tuples of Int32, String and Char
Array({Int32, String, Char})

带名称的元组

1
2
3
4
5
6
tuple = {name: "Crystal", year: 2011} # NamedTuple(name: String, year: Int32)
tuple[:name] # => "Crystal" (String)
tuple[:year] # => 2011 (Int32)

# The type denoting a named tuple of x: Int32, y: String
NamedTuple(x: Int32, y: String)

过程定义

一个过程可以理解为一段特定功能的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# A proc without arguments
->{ 1 } # Proc(Int32)

# A proc with one argument
->(x : Int32) { x.to_s } # Proc(Int32, String)

# A proc with two arguments:
->(x : Int32, y : Int32) { x + y } # Proc(Int32, Int32, Int32)

Proc(Int32, String).new { |x| x.to_s } # Proc(Int32, String) #另一种写法

# 接收一个Int32类型的参数, 返回一个String类型
Proc(Int32, String)

# A proc accepting no arguments and returning Void
Proc(Void)

# A proc accepting two arguments (one Int32 and one String) and returning a Char
Proc(Int32, String, Char)

参数的类型必须指定 ,except when directly sending a proc literal to a lib fun in C bindings.
返回值的类型会在proc过程中自动确定。

调用Proc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
proc = ->(x : Int32, y : Int32) { x + y }
proc.call(1, 2) #=> 3 # 使用call调用一个proc

# 从一个现成的函数调用
def one
1
end
proc = ->one
proc.call #=> 1


def plus_one(x)
x + 1
end
proc = ->plus_one(Int32) # 带参数的必须指明参数类型
proc.call(41) #=> 42

str = "hello"
proc = ->str.count(Char)
proc.call('e') #=> 1
proc.call('l') #=> 2

赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 赋值给一个本地变量 
local = 1

# 赋值给一个实例变量
@instance = 2

# 赋值给一个类的静态变量
@@class = 3

# 一些高级玩法
local += 1 # 如同: local = local + 1
# 上面的赋值可以使用以下操作符文:
# +, -, *, /, %, |, &, ^, **, <<, >>
local ||= 1 # 如同: local || (local = 1)
local &&= 1 # 如同: local && (local = 1)

# 方法调用的一些操作技法
# A setter
person.name=("John")
# The above can be written as:
person.name = "John"

# An indexed assignment
objects.[]=(2, 3)
# The above can be written as:
objects[2] = 3

# Not assignment-related, but also syntax sugar:
objects.[](2, 3)
# The above can be written as:
objects[2, 3]

#使用[]?可以检测键的存在性
person.age += 1 # same as: person.age = person.age + 1

person.name ||= "John" # same as: person.name || (person.name = "John")
person.name &&= "John" # same as: person.name && (person.name = "John")

objects[1] += 2 # same as: objects[1] = objects[1] + 2

objects[1] ||= 2 # same as: objects[1]? || (objects[1] = 2)
objects[1] &&= 2 # same as: objects[1]? && (objects[1] = 2)
多次赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
name, age = "Crystal", 1

# The above is the same as this:
temp1 = "Crystal"
temp2 = 1
name = temp1
age = temp2

#交换值
a = 1
b = 2
a, b = b, a
a #=> 2
b #=> 1

name, age, source = "Crystal,1,github".split(",")
# 以下是等价写法:
temp = "Crystal,1,github".split(",")
name = temp[0]
age = temp[1]
source = temp[2]

names = "John", "Peter", "Jack"
# 以下为等价写法
names = ["John", "Peter", "Jack"]

# 也可以用于对象的赋值
person.name, person.age = "John", 32

# 也可以对数组赋值
objects[1], objects[2] = 3, 4
# 等价:
temp1 = 3
temp2 = 4
objects[1] = temp1
objects[2] = temp2

控制流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
if some_condition
do_something
elsif some_other_condition
do_something_else
else
do_that
end

a = 2 if some_condition #作为表达式后缀的写法

a = 1 > 2 ? 3 : 4 # 三元运算

注: if 表达式(或值) , 这种形式不适用于对象属性或类属性 ,如:
if @a
# 这里 @a的值可以是 nil
end

有两种解决方法:
# 1: 把它赋值给一个变量
if a = @a
# here a can't be nil
end

# 2: 使用 `Object#try` found in the standard library
@a.try do |a|
# here a can't be nil
end

That logic also doesn't work with proc and method calls, including getters and properties, because nilable (or, more generally, union-typed) procs and methods aren't guaranteed to return the same more-specific type on two successive calls.

上面写到的关于对象属性的内容,对于proc和方法调用都一样。

if var.is_a?(...)
测试变量的类型是否跟指定的类型一致(适用于任意数据类型的判断)
if a.is_a?(String)
# here a is a String
end

if b.is_a?(Number)
# here b is a Number
end

if a.is_a?(String) && b.is_a?(Number)
# here a is a String and b is a Number
end

上面的写法对于对象和类的属性判断不适用 。
if @a.is_a?(String)
# 这里 @a 可能不是String类型的
end

解决方法同上 , 先把值赋给一个变量
a = @a
if a.is_a?(String)
# here a is guaranteed to be a String
end

# A bit shorter:
if (a = @a).is_a?(String)
# here a is guaranteed to be a String
end

if var.responds_to?(...)
用来测试变量是否存在指定的方法
if a.responds_to?(:abs)
# 这里 变量a的类型必须要支持abs方法
end

以上表达式 对于对象和类的属性依然是不适用的。处理方法仍然是将它赋值给变量。
if @a.responds_to?(:abs)
# here @a is not guaranteed to respond to `abs`
end
if (a = @a).responds_to?(:abs)
# here a is guaranteed to respond to `abs`
end

if var.nil?
if a.nil?
# 此处 ,a的值为nil
end

while句式
while some_condition
do_this
end
while循环中支持 breaknext

文件包含

大的程序一般都会按照一定的分割方式,将代码写到不同的文件中。使用时可以用require “…” 的方式来组合这些程序。
require接受一个字符串的参数 。

require “filename”
这种写法将会在 require 的路径中查找 filename
默认情况下 ,require路径是编译器指定的标准库路径,并且是相对于当前路径的一个叫做“lib”的路径。
查找的规则如下:
如果文件 “filename.cr”在 require路径中存在, 它将被召唤
如果存在一个”filename”的目录并且在此目录中存在 “filename.cr”,它将被召唤
否则 程序抛出异常

1
require "foo/bar/baz"

require “./filename”
这种召唤使用的路径是相对于当前调用require的程序文件的。可以使用 ../ 或../../等进行上层目录的require

1
require "./foo/bar/baz"

可以使用通配符进行require

1
2
require "foo/*"  #将会require foo目录下的所有 .cr文件
require "foo/**" # 将会 require foo及其子目录下的所有 .cr文件

类和方法

在Crystal中,一切都是对象。

实例化,初始化和内存分配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
person = Person.new    # 创建一个实例
class Person
def initialize(name : String) # initialize是构造方法,参数的类型需要指定
@name = name # 对象的属性都以 @开头
@age = 0 # 在构造子中直接使用具体的值来初始化对象属性 ,不需要指定类型
end

def name # 定义name方法 , 返回 @name的值 不需要写return
@name
end

def age
@age
end
end

方法和实例属性

Crystal的标准库定义了宏来使getter和setter变的简单。

1
2
3
4
5
6
7
8
9
10
11
12
class Person
property age
getter name : String

def initialize(@name)
@age = 0
end
end

john = Person.new "John"
john.age = 32
john.age #=> 32

类的方法允许分开写,在编译的时候将会被合并到一个类中

1
2
3
4
5
6
7
8
9
10
11
class Person
def initialize(@name : String)
@age = 0
end
end

class Person
def become_older
@age += 1
end
end

注:方法重定义时,后面定义的方法将会覆盖之前的方法。

你可以通过调用 previous_def 在调用之前定义的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person
def become_older
@age += 1
end
end

class Person
def become_older
previous_def # 不带括号和参数的情况下 ,将接收当前方法调用时的参数 否则使用自定义参数。
@age += 2
end
end

person = Person.new "John"
person.become_older
person.age #=> 3

对象的属性也可以在构造子之外被初始化

1
2
3
4
5
6
class Person
@age = 0

def initialize(@name : String)
end
end

This will initialize @age to zero in every constructor. This is useful to avoid duplication, but also to avoid the Nil type when reopening a class and adding instance variables to it.

类型推断

看下面的例子

1
2
3
4
5
class Person
def initialize(@name)
@age = 0
end
end

@age毫无疑问是一个integer,但是@name的类型不好判断了。 编译器会结合所有用到Person类的地方对@name的类型进行判断,但是这么搞会有点问题:
对编译器不友好,也不利于增加代码的可读性 。随着项目代码量的增加,这个问题会变的严重。编译变慢,代码也看不懂了 。。。
Crystal有以下几种方法来杜绝这个问题
1.显式的注明类型

1
2
3
4
5
6
7
8
class Person
@name : String
@age : Int32

def initialize(@name)
@age = 0
end
end

2.定义时赋于具体的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class Person
def initialize
@name = "John Doe" # 用值墳充
@age = 0 # 用值墳充
end
end

class Person
def initialize
@address = Address.new("somewhere") #对象调用赋值
end
end

class Something
def initialize
@values = Array(Int32).new #数组
end
end

class Person
def initialize(name : String) #用参数赋值
@name = name
end
end

class Person
def initialize(@name : String) #同上等价
end
end

class Person
def initialize(name = "John Doe")
@name = name
end
end

class Person
def initialize(@name = "John Doe")
end
end

class Person
def initialize
@age = LibPerson.compute_default_age # 使用库方法的返回值
end
end

lib LibPerson
fun compute_default_age : Int32
end

联合类型

方法重载,多态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Person
getter :age

def initialize(@name : String, @age : Int = 0)
end

def become_older
@age += 1
end

def become_older(years)
@age += years
end
end

john = Person.new "John"
john.age #=> 0

john.become_older
john.age #=> 1

john.become_older 5
john.age #=> 6

默认值和 命名参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Person
def become_older(by = 1)
@age += by
end
end

def some_method(x, y = 1, z = 2, w = 3)
# do something...
end
some_method 10 # x: 10, y: 1, z: 2, w: 3
some_method 10, z: 10 # x: 10, y: 1, z: 10, w: 3
some_method 10, w: 1, y: 2, z: 3 # x: 10, y: 2, z: 3, w: 1
some_method y: 10, x: 20 # x: 20, y: 10, z: 2, w: 3

some_method y: 10 # Error, missing argument: x

Splats and tuples

通过使用 * ,方法可以接收可变数量的参数。在方法内部 ,参数转化为元组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def sum(*elements)
total = 0
elements.each do |value|
total += value
end
total
end

sum 1, 2, 3 #=> 6
sum 1, 2, 3, 4.5 #=> 10.5

# elements is Tuple(Int32, Int32, Int32, Float64)
sum 1, 2, 3, 4.5

def sum(*elements, initial = 0) # 可变参数的后面还可以跟命名参数
total = initial
elements.each do |value|
total += value
end
total
end

sum 1, 2, 3 # => 6
sum 1, 2, 3, initial: 10 # => 16

Double splats and named tuples

双星号(**) 会匹配命名元组构成的参数中的 没有被匹配到的参数形成的元组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def foo(x, **other)
# Return the captured named arguments as a NamedTuple
other
end

foo 1, y: 2, z: 3 # => {y: 2, z: 3}
foo y: 2, x: 1, z: 3 # => {y: 2, z: 3}

# 将元组送入参数
def foo(x, y)
x - y
end

tuple = {y: 3, x: 10}
foo **tuple # => 7

静态类的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Parent
@@numbers = [] of Int32 # 类的静态属性

def self.numbers
@@numbers
end
end

class Child < Parent #类的继承
end

Parent.numbers # => []
Child.numbers # => []

Parent.numbers << 1
Parent.numbers # => [1]
Child.numbers # => []

class Foo
def finalize # 解构函数,类似其它语言中的 distruct
# Invoked when Foo is garbage-collected
# Use to release non-managed resources (ie. C libraries, structs)
end
end

模块

模块的定义有两个用途:
1.为定义新的数据类型,方法和常量提供命名空间
2.作为可以被其它代码块调用的代码片段 (as partial types that can be mixed in other types )

1
2
3
4
5
6
module Curses
class Window
end
end

Curses::Window.new # 通过命名空间调用

自定义库文件时建议创建命名空间 以防止出现冲突。标准库没有使用命名空间,因为使用比较频繁 这样可以方便书写。
模块允许再调用别的模块。
模块不能被实例化。

作为代码片段时可以通过 include或extend来调用 。include 和extend 允许在代码的顶层调用,to avoid writing a namespace over and over

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module ItemsSize
def size
items.size
end
end

class Items
include ItemsSize

def items
[1, 2, 3]
end
end

items = Items.new
items.size #=> 3

泛型

结构体

可以使用结构体替代Class来创建新的类型

1
2
3
4
5
6
struct Point
property x, y

def initialize(@x : Int32, @y : Int32)
end
end

结构体和类的区别
1.在结构体上调用new方法,将在栈上开辟空间而不是堆
2.结构体是传值调用而类是传址
3.结构体无法继承一个非抽象结构体

The last point has a reason to it: a struct has a very well defined memory layout. For example, the above Point struct occupies 8 bytes. If you have an array of points the points are embedded inside the array’s buffer:

1
2
# The array's buffer will have 8 bytes dedicated to each Point
ary = [] of Point

If Point is inherited, an array of such type must also account for the fact that other types can be inside it, so the size of each element must grow to accommodate that. That is certainly unexpected. So, non-abstract structs can’t be inherited. Abstract structs, on the other hand, will have descendants, so it’s expected that an array of them will account for the possibility of having multiple types inside it.

A struct can also include modules and can be generic, just like a class.

A struct is mostly used for performance reasons to avoid lots of small memory allocations when passing small copies might be more efficient.

So how do you choose between a struct and a class? The rule of thumb is that if no instance variable is ever reassigned, i.e. your type is immutable, you could use a struct, otherwise use a class.

常量

常量可以在任意地方定义 , 首字母必须大写 ,通常整个写成大写。

1
2
3
4
5
6
7
8
PI = 3.14

module Earth
RADIUS = 6_371_000
end

PI #=> 3.14
Earth::RADIUS #=> 6_371_000

常量允许调用方法,并且可以有复杂的逻辑封装。

1
2
3
4
5
6
7
8
9
TEN = begin
a = 0
while a < 10
a += 1
end
a
end

TEN #=> 10

内置常量

Crystal中内置了一些常量

1
2
3
4
5
6
7
__LINE__ 定义了程序中的当前行数,当它被声明为一个方法的参数时 它代表了在该方法中的行号。 

__END_LINE__ is the line number of the end of the calling block. Can only be used as a default value to a method parameter.

__FILE__ 代表了当前被调用的程序文件的完整路径

__DIR__ 表示被调用程序文件所在的完整目录

枚举类型

进程和块

方法定义时可以接受带有yield关键字的代码块。

1
2
3
4
5
6
7
8
9
def twice
yield
yield
end

# 这里将会输出Hello两次
twice do
puts "Hello!"
end

定义时只需要在方法中加入yield关键词,也可以通过 & 符号显式的定义。

1
2
3
4
def twice(&block)
yield
yield
end

要调用方法和传入块代码时,只需要使用 do … 或 {…}的形式。

1
2
3
4
5
6
7
8
9
twice() do
puts "Hello!"
end

twice do
puts "Hello!"
end

twice { puts "Hello!" }

两者的区别在于 do … end and { … } is that do … end binds to the left-most call, while { … } binds to the right-most call:

1
2
3
4
5
6
7
8
9
10
11
foo bar do
something
end
# 上面等价于
foo(bar) do
something
end

foo bar { something }
# 上面等价于
foo(bar { something })

yield参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
def twice
yield 1
yield 2
end

twice do |i|
puts "Got #{i}"
end

# 或者
twice { |i| puts "Got #{i}" }

# 上面两个方法打印出 "Got 1" and "Got 2".

# 可以传入更多的参数
def many
yield 1, 2, 3
end

many do |x, y, z|
puts x + y + z
end
# Output: 6

# 少于yield的参数个数
def many
yield 1, 2, 3
end

many do |x, y|
puts x + y
end
# Output: 3

# 传入的参数数量不能多于 yield中定义的参数

# 单参数的简写语法
method do |argument|
argument.some_method
end

method &.some_method #上面可以被简单写成这样
method(&.some_method) # 这样写也行
method &.some_method(arg1, arg2)

# 运算符也可以直接调用
method &.+(2)
method &.[index]

# 指定类型
def transform_int(start : Int32, &block : Int32 -> Int32)
result = yield start
result * 2
end

transform_int(3) { |x| x + 2 } #=> 10
transform_int(3) { |x| "foo" } # Error: expected block to return Int32, not String

# break 用来中止块语句
def twice
yield 1
yield 2
end

twice { |i| i + 1 } #=> 3
twice { |i| break "hello" } #=> "hello"

# next

# with ... yield
class Foo
def one
1
end

def yield_with_self
with self yield
end

def yield_normally
yield
end
end

def one
"one"
end

Foo.new.yield_with_self { one } # => 1
Foo.new.yield_normally { one } # => "one"

捕获代码块

一段代码可以被捕获并转换成Block
定义一个block

1
2
3
4
5
6
def int_to_int(&block : Int32 -> Int32)
block
end

proc = int_to_int { |x| x + 1 }
proc.call(1) #=> 2

下面的例子定义一个块,作为回调来使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Model
def on_save(&block)
@on_save_callback = block
end

def save
if callback = @on_save_callback
callback.call
end
end
end

model = Model.new
model.on_save { puts "Saved!" }
model.save # prints "Saved!"

# 上例没有指明 &block的类型, 用来表明 不接收任何参数 也不返回任何值
# 注:如果没有指明返回类型, 将不返回任何值
def some_proc(&block : Int32 ->)
block
end

proc = some_proc { |x| x + 1 }
proc.call(1) # void

break 和 next
return和break不能用在block中 ,使用next会退出block并返回值

with …yield
在block中无效

定义一个block和Proc,并将block放入proc中执行

1
2
3
4
5
6
7
8
9
10
def some_proc(&block : Int32 -> Int32)
block
end

x = 0
proc = ->(i : Int32) { x += i }
proc = some_proc(&proc)
proc.call(1) #=> 1
proc.call(10) #=> 11
x #=> 11

一个Proc也可以通过一个现成的方法来创建

1
2
3
4
5
6
def add(x, y)
x + y
end

adder = ->add(Int32, Int32)
adder.call(1, 2) #=> 3

block的转发(Block forwarding)

Closures

别名 (alias)

异常处理

抛出异常

1
2
raise "OH NO!"
raise Exception.new("Some error")

自定义异常

1
2
3
4
5
class MyException < Exception
end

class MyOtherException < Exception
end

处理异常

使用 begin … rescue … end 的代码结构来处理异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
begin
raise "OH NO!"
rescue
puts "Rescued!"
end

# 可以在rescue后新增参数来接收详细的异常信息
begin
raise "OH NO!"
rescue ex
puts ex.message
end

# 处理具体的异常
begin
raise MyException.new("OH NO!")
rescue MyException
puts "Rescued MyException"
end

begin
raise MyException.new("OH NO!")
rescue ex : MyException
puts "Rescued MyException: #{ex.message}"
end

# 多重异常处理
begin
# ...
rescue ex1 : MyException
# only MyException...
rescue ex2 : MyOtherException
# only MyOtherException...
rescue
# any other kind of exception
end

begin
# ...
rescue ex : MyException | MyOtherException
# only MyException or MyOtherException
rescue
# any other kind of exception
end

# else
begin
something_dangerous
rescue
# execute this if an exception is raised
else
# execute this if an exception isn't raised
end

# ensure ,相当于finally , 不论有无异常 都会执行
begin
something_dangerous
ensure
puts "Cleanup..."
end

类型定义语法参考

当需要
1.指定限制类型
2.指定参数类型
3.声明变量
4.声明别名
5.定义新类型
6.认证 is_a? 的call调用
7.认证as表达式
8.认证sizeof表达式
9.认证instance_sizeof表达式
10.方法的返回值类型
一些常见的类型都有简洁的操作方法。这对于当需要书写C库绑定时很有用,并且也可以用于上面的所有情况。
常规类型的声明写法

1
2
3
Int32
My::Nested::Type
Array(String)

多种类型联合

1
alias Int32OrString = Int32 | String

空值

1
2
3
alias Int32OrNil = Int32?
# 上面等价于
alias Int32OrNil = Int32 | ::Nil

指针

1
2
3
alias Int32Ptr = Int32*
# 等价于
alias Int32Ptr = Pointer(Int32)

静态数组

1
2
3
alias Int32_8 = Int32[8]
# 等价于
alias Int32_8 = StaticArray(Int32, 8)

元组

1
2
3
alias Int32StringTuple = {Int32, String}
# 等价于
alias Int32StringTuple = Tuple(Int32, String)

命名元组

1
2
3
alias Int32StringNamedTuple = {x: Int32, y: String}
# 等价于
alias Int32StringNamedTuple = NamedTuple(x: Int32, y: String)

过程(Proc)

1
2
3
4
5
6
7
8
9
10
11
alias Int32ToString = Int32 -> String
# 等价于
alias Int32ToString = Proc(Int32, String)

# To specify a Proc without arguments:
alias ProcThatReturnsInt32 = -> Int32
# To specify multiple arguments:
alias Int32AndCharToString = Int32, Char -> String

# Proc嵌套需要使用括号
alias ComplexProc = (Int32 -> Int32) -> String

self
self can be used in the type grammar to denote a self type. Refer to the type restrictions section.

class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def foo(x : Int32)
"instance"
end

def foo(x : Int32.class)
"class"
end

foo 1 # "instance"
foo Int32 # "class"

# class也可以用来创建一个class类型的数组
class Parent
end

class Child1 < Parent
end

class Child2 < Parent
end

ary = [] of Parent.class
ary << Child1
ary << Child2

下划线(Underscore)
下划线可以用来表示任何数据类型

1
2
3
4
5
6
7
# Same as not specifying a restriction, not very useful
def foo(x : _)
end

# A bit more useful: any two arguments Proc that returns an Int32:
def foo(x : _, _ -> Int32)
end

typeof
用于返回传入参数的类型(相同的类型不会重复)

1
2
typeof(1 + 2) # => Int32
typeof(1, "a") # => (Int32 | String)

类型反射

is_a?

用于判断变量在运行时的类型是否和表达式匹配

1
2
3
4
5
a = 1
a.is_a?(Int32) #=> true
a.is_a?(String) #=> false
a.is_a?(Number) #=> true
a.is_a?(Int32 | String) #=> true

nil?

判断表达式的值是否为nil

1
2
3
4
5
a = 1
a.nil? # => false

b = nil
b.nil? # => true

responds_to?

判断一个变量是否有参数指定的方法

1
2
3
a = 1
a.responds_to?(:abs) #=> true
a.responds_to?(:size) #=> false

as

as 用于约束一个表达式的值类型。参数为一个数据类型。
注意: as不能用于做数据类型转换。Methods on integers, floats and chars are provided for these conversions.

1
2
3
4
5
6
7
if some_condition
a = 1
else
a = "hello"
end

# a : Int32 | String

在上面 ,a的类型为 Int32 | String。 在if段中可以强制编译器指定一个类型(Int)。as采用运行时检查,如果a不是Int32,将抛出异常。

1
2
a_as_int = a.as(Int32)
a_as_int.abs # works, compiler knows that a_as_int is Int32

Converting between pointer types
The as pseudo-method also allows to cast between pointer types:

1
2
ptr = Pointer(Int32).malloc(1)
ptr.as(Int8*) #:: Pointer(Int8)

as?

同as一样, 但是在出现异常时只是返回nil ,并且也不支持指针类型同其它数据类型之间的转换。

1
2
3
4
value = rand < 0.5 ? -3 : nil
result = value.as?(Int32) || 10

value.as?(Int32).try &.abs

typeof

返回一个表达式值的类型

宏是一些预编译的方法(在编译时被加入到抽像语法树中),这些方法必须是完整有效的Crystal代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
macro define_method(name, content)
def {{name}}
{{content}}
end
end

# This generates:
#
# def foo
# 1
# end
define_method foo, 1

foo #=> 1

作用域

宏被定义在顶层作用域,所有代码中都可用。如果某个宏被标记为private 那么它只在当前文件中可见。
宏也可以定义在类和模块中, 并且在定义它的作用域中可见。程序也会在先祖域中搜索宏(父类 及被包含的模块中)。
For example, a block which is given an object to use as the default receiver by being invoked with with … yield can access macros defined within that object’s ancestors chain:

1
2
3
4
5
6
7
8
9
10
11
class Foo
macro emphasize(value)
"***#{ {{value}} }***"
end

def yield_with_self
with self yield
end
end

Foo.new.yield_with_self { emphasize(10) } #=> "***10***"

定义在类中或模块中的宏 也可以在外部被调用。

1
2
3
4
5
6
7
class Foo
macro emphasize(value)
"***#{ {{value}} }***"
end
end

Foo.emphasize(10) # => "***10***"

插值

1
使用 {{...}} 在语法树中进行插入值。

Note that :foo was the result of the interpolation, because that’s what was passed to the macro. You can use the method ASTNode#id in these cases, where you just need an identifier.

宏调用

你可以在编译时调用一些方法, 这些方法被定义在Crystal::Macros 模块中。

条件语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 你可以使用{% if condition %} ... {% end %} 来按条件生成代码。宏的条件语句可以在宏定义之外的地方使用。
macro define_method(name, content)
def {{name}}
{% if content == 1 %}
"one"
{% else %}
{{content}}
{% end %}
end
end

define_method foo, 1
define_method bar, 2

foo #=> one
bar #=> 2

迭代

迭代语句也可以在宏定义之外的地方使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
macro define_dummy_methods(names)
{% for name, index in names %}
def {{name.id}}
{{index}}
end
{% end %}
end

define_dummy_methods [foo, bar, baz]

foo #=> 0
bar #=> 1
baz #=> 2

#
macro define_dummy_methods(hash)
{% for key, value in hash %}
def {{key.id}}
{{value}}
end
{% end %}
end
define_dummy_methods({foo: 10, bar: 20})
foo #=> 10
bar #=> 20

#
{% for name, index in ["foo", "bar", "baz"] %}
def {{name.id}}
{{index}}
end
{% end %}

foo #=> 0
bar #=> 1
baz #=> 2

宏方法定义

钩子

在一些场景下,一些特殊的宏被当成钩子来使用。这些场景是 inherited, included, extended 和 method_missing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1.inherited在编译时被触发,如果定义了子类。@type is the inheriting type.
2.included在编译时被触发,当一个模块被include时。@type is the including type.
3.extended在编译时被触发,当一个模块被extend时。@type is the extending type.
4.method_missing 在编译时被触发,当方法不存在时.
例子: inherited

class Parent
macro inherited
def lineage
"{{@type.name.id}} < Parent"
end
end
end

class Child < Parent
end

Child.new.lineage #=> "Child < Parent"

例子: method_missing

1
2
3
4
5
6
macro method_missing(call)
print "Got ", {{call.name.id.stringify}}, " with ", {{call.args.size}}, " arguments", '\n'
end

foo # Prints: Got foo with 0 arguments
bar 'a', 'b' # Prints: Got bar with 2 arguments

变量刷新

属性

底层操作(Low-level primitives )

编译时标志(Compile-time flags )

C语言绑定(C bindings )

文章目录
  1. 1. 说明
  2. 2. 语法教程
    1. 2.1. 注释
    2. 2.2. 变量类型
      1. 2.2.1. Nil
      2. 2.2.2. 布尔值
      3. 2.2.3. 整数
      4. 2.2.4. 浮点数
      5. 2.2.5. 字符
      6. 2.2.6. 字符串
        1. 2.2.6.1. 转义
        2. 2.2.6.2. 插值
        3. 2.2.6.3. 百分符字符串
        4. 2.2.6.4. 字符串允许换行
        5. 2.2.6.5. Heredoc
      7. 2.2.7. 符号
      8. 2.2.8. 数组
        1. 2.2.8.1. 带百分号的数组
        2. 2.2.8.2. Array风格的数组表示方式
      9. 2.2.9. 哈希
        1. 2.2.9.1. 哈希风格的数据类型
      10. 2.2.10. 范围
      11. 2.2.11. 正则
      12. 2.2.12. 元组
      13. 2.2.13. 带名称的元组
      14. 2.2.14. 过程定义
        1. 2.2.14.1. 调用Proc
      15. 2.2.15. 赋值
        1. 2.2.15.1. 多次赋值
    3. 2.3. 控制流
    4. 2.4. 文件包含
    5. 2.5. 类和方法
      1. 2.5.1. 实例化,初始化和内存分配
      2. 2.5.2. 方法和实例属性
      3. 2.5.3. 类型推断
      4. 2.5.4. 联合类型
      5. 2.5.5. 方法重载,多态
      6. 2.5.6. 默认值和 命名参数
      7. 2.5.7. Splats and tuples
      8. 2.5.8. Double splats and named tuples
      9. 2.5.9. 静态类的属性
    6. 2.6. 模块
    7. 2.7. 泛型
    8. 2.8. 结构体
    9. 2.9. 常量
      1. 2.9.1. 内置常量
    10. 2.10. 枚举类型
    11. 2.11. 进程和块
      1. 2.11.1. yield参数
      2. 2.11.2. 捕获代码块
      3. 2.11.3. block的转发(Block forwarding)
      4. 2.11.4. Closures
    12. 2.12. 别名 (alias)
  3. 3. 异常处理
    1. 3.1. 抛出异常
    2. 3.2. 自定义异常
    3. 3.3. 处理异常
    4. 3.4. 类型定义语法参考
    5. 3.5. 类型反射
      1. 3.5.1. is_a?
      2. 3.5.2. nil?
      3. 3.5.3. responds_to?
      4. 3.5.4. as
      5. 3.5.5. as?
      6. 3.5.6. typeof
    6. 3.6.
      1. 3.6.1. 作用域
      2. 3.6.2. 插值
      3. 3.6.3. 宏调用
      4. 3.6.4. 条件语句
      5. 3.6.5. 迭代
      6. 3.6.6. 宏方法定义
      7. 3.6.7. 钩子
      8. 3.6.8. 变量刷新
    7. 3.7. 属性
    8. 3.8. 底层操作(Low-level primitives )
    9. 3.9. 编译时标志(Compile-time flags )
    10. 3.10. C语言绑定(C bindings )