# 这是一行注释 # 一切都是对象(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)中的方法添加为实例方法 # extend 将模块中的方法添加为类方法 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"