类型
§ 字符串
Crystal 使用双引号来表示字符串,单引号是 Char 类型(在栈中分配)。
1
2 p typeof("Hello") # => String
3 p typeof("汉") # => String
4 p typeof('汉') # => Char
5
这几乎总是从 Ruby 切换到 Crystal 最先遇到的坑。
而且,Crystal 字符串是不可变的, 因此,Ruby 中常用的方法 String#<< 是没有的。
尝试修改一个字符串,例如 String#+ 或 interpolation 都会产生新的字符串,你需要将修改后字符串重新赋值给一个变量。
1
2 str = "Hello"
3 p str.object_id # => 104918042676384
4 str = str.sub("H", "h")
5 p str.object_id # => 132565044809664
6
你仍然可以使用 % 或 %q 来分别表示 Ruby 中等价的 "双引号" 和 '单引号'
1
2 recipient = "Billy"
3 p %(Hello #{recipient}!) # => "Hello Billy!"
4 p %q(Hello #{recipient}!) # => "Hello \#{recipient}!"
5
§ Crystal 中没有全局变量
Crystal 不支持定义 $ 开头的全局变量,事实上 $ 开头的其实根本不是全局变量,因为只是方法可见的。
默认,只有很少的几个预定义的 $ 开头的变量,正则匹配相关的 $~, $1, $2 ... 以及前一个被执行的进程退出状态 $?
1
2 "hello" =~ /(ll)o/
3
4 p $~ # => Regex::MatchData("llo" 1:"ll")
5 p $0 # => llo, 等价于 $~[0],等价于 Ruby 中的 $&
6 p $1 # => ll,等价于 $~[1]
7 p $2 # => Unhandled exception: Invalid capture group index: 2 (IndexError)
8
9 `ls -alh`
10 p $? # => Process::Status[0]
11
§ 整数与布尔类型
整数类型细分为 8 个,而 Ruby 是 Integer 一个。
1
2 p typeof(1_i8) # => Int8
3 p typeof(1_u8) # => UInt8
4 p typeof(1_i16) # => Int16
5 p typeof(1_u16) # => UInt16
6 p typeof(1) # => Int32 这是整数默认类型, 等价于:typeof(1_i32)
7 p typeof(1_u32) # => UInt32
8 p typeof(1_i64) # => Int64
9 p typeof(1_u64) # => UInt64
10
Ruby 中,当数字大到 Fixnum 无法容纳它时,会自动转化为 Bignum, Crystal 则抛出 OverflowError 异常
1
2 x = 127_i8 # An Int8 type
3 x # => 127
4 x += 1 # Unhandled exception: Arithmetic overflow (OverflowError)
5
Crystal 标准库提供了专门的任意大小和精度的类型
BigDecimal | BigFloat | BigInt |
Crystal 中,true/false 类属于 Bool 类型, 而 Ruby 是单独的 TrueClass 和 FalseClass
§ 哈希与 NamedTuple
Crystal 当中:哈希火箭表示一个哈希,而 1.9 新哈希表示法
{:foo => 100} 这是定义了一个哈希。
{foo: 100} 则是定义了一个 NamedTuple
后者是一个不可变的、编译时确定大小的特定的 NamedTuple 类型, 并且是栈分配的,可以简单的将理解为就是一个不可变的 Struct。
1
2 x = {:foo => 100, :bar => "Hello"}
3 y = {foo: 100, bar: "Hello"}
4
5 p typeof(x) # => Hash(Symbol, Int32 | String)
6 p typeof(y) # => NamedTuple(foo: Int32, bar: String), 编译时类型就是一个包含了整数类型属性 foo, 以及字符串类型属性 bar 的 NamedTuple
7
8 # 通过符号和字符串取值都是可以的。
9 p y[:foo] # => 100
10 p y["foo"] # => 100
11
个人认为这是对 Ruby 错误设计的完美修复,关键字参数本来就应该设计成这样。
§ 实例变量和类变量
Crystal 中不存在 Ruby 的类变量那样的东西,Crystal 的 类变量 的行为和 Ruby 中 类的实例变量 相同。
1
2 class Foo
3 @@value = 1
4
5 def self.value
6 @@value
7 end
8
9 def self.value=(@@value)
10 end
11 end
12
13 class Bar < Foo
14 end
15
16 p Foo.value # => 1
17 p Bar.value # => 1
18
19 Foo.value = 2
20
21 p Foo.value # => 2
22 p Bar.value # => 1
23
24 Bar.value = 3
25
26 p Foo.value # => 2
27 p Bar.value # => 3
28
即:@@类变量 是属于类自身的变量,每一个子类也会继承父类的类变量值(类型必须相同), 但是会创建自己独立的类变量实例, 因此修改子类的类变量的值,不会影响父类,反之亦然。
这是再一次对 Ruby 错误行为的修复。
因为 Crystal 中的 @@类变量 其行为表现就是 Ruby 中的 类级别 的 @实例变量, 那么问题来了,Crystal 中类级别定义的实例变量又是什么呢?
1
2 class Foo
3 @value = 100
4
5 def value
6 @value
7 end
8
9 def initialize
10 puts "Foo"
11 end
12 end
13
14 class Bar < Foo
15 end
16
17 bar = Bar.new
18 p bar.value # => Crystal 输出 100, Ruby 输出 nil
19
结果为:上面的 @value 其实就是一个普通的实例变量,只不过在类定义中进行了初始化, 如果这个类有签名不同的多个不同版本的构造器(initialize 方法),它相当于为所有构造器 初始化了变量 @value,避免了重复初始化。 而且当这样的类被 reopen 时,@value 值也是存在的。
由于目前编译器的限制,在方法定义中初始化一个的可空的实例变量是不合法的。
1
2 class A
3 def initialize
4 @x : Int32?
5 end
6 end
7
8 a = A.new
9
10 # 3 | @x : Int32?
11 # ^-
12 # Error: declaring the type of an instance variable must be done at the class level
13
即使赋初值为 nil 也不行。
1
2 class A
3 def initialize
4 @x : Int32? = nil
5 end
6 end
7
8 a = A.new
9
10 # 3 | @x : Int32? = nil
11 # ^
12 # Error: instance variable @x of A was inferred to be Nil, but Nil alone provides no information
13
只能通过类级别来初始化
1
2 class A
3 @x : Int32?
4
5 def initialize
6 end
7 end
8
9 p A.new # => #<A:0x7a69c688bfc0 @x=nil>
10
当然,在方法中直接使用 literal 值初始化一个实例变量是可以的。
1
2 # 但是赋值一个非 nilable 的值,来推断类型是可以的。
3
4 class A
5 def initialize
6 @x = 100
7 end
8
9 def hello
10 @hello = "Hello"
11 end
12 end
13
14 p A.new # => #<A:0x7d736b629ce0 @x=100, @hello=nil>
15