类型和方法

类和方法

Crystal中一切都是对象

  1. person = Person.new # 创建一个对象
  2. # 定义一个类
  3. class Person
  4. def initialize(name : String) # 构造子 ,没有赋于具体值的属性需要指定类型
  5. @name = name
  6. @age = 0
  7. end
  8. # 也可以这样写
  9. def initialize(@name : String) # 直接在此处定义了一个属性
  10. @age = 0
  11. end
  12. def name
  13. @name
  14. end
  15. def age
  16. @age
  17. end
  18. end
  19. # 调用
  20. john = Person.new "John"
  21. peter = Person.new "Peter"
  22. john.name #=> "John"
  23. john.age #=> 0
  24. peter.name #=> "Peter"
  25. # getter 和 setter方法 。Crystal在标准库中已经定义了相关的宏简化操作 ,参看
  26. [getter](https://crystal-lang.org/api/0.24.1/Object.html#getter%28%2Anames%29-macro)
  27. [settter](https://crystal-lang.org/api/0.24.1/Object.html#setter%28%2Anames%29-macro)
  28. [property](https://crystal-lang.org/api/0.24.1/Object.html#property%28%2Anames%29-macro)
  29. # 方法重载,previous_def
  30. class Person
  31. def become_older
  32. @age += 1
  33. end
  34. end
  35. class Person
  36. def become_older
  37. @age += 2
  38. end
  39. end
  40. person = Person.new "John"
  41. person.become_older
  42. person.age #=> 2
  43. # 重载方法中调用之前的定义
  44. class Person
  45. def become_older
  46. @age += 1
  47. end
  48. end
  49. class Person
  50. def become_older
  51. previous_def
  52. @age += 2
  53. end
  54. end
  55. person = Person.new "John"
  56. person.become_older
  57. person.age #=> 3

类型推断

crystal的设计哲学是尽可能的不要求显式指定数据类型,但也有一些情况下是必须指定类型的。

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

在上面代码中@name没有指明类型,crystal编译器会扫描所有Person被调用的地方,这样会有以下问题:

  • 代码可读性差
  • 不利于编译优化,当代码变复杂时编译时间会变的很长
    因此,需要指明类和实例的属性变量类型。

直接指定数据类型

  1. class Person
  2. @name : String
  3. @age : Int32
  4. def initialize(@name)
  5. @age = 0
  6. end
  7. end

不指定类型

编译器会使用一系列的规则去匹配,当没有规则匹配成功时变量的类型将会是一个包含各种类型的联合类型。在变量没有初始化的情况下,联合中还将包括Nil类型。规则有很多,但常用的一般是前3个匹配,无需了解。当编译器报错时,显式的指定类型即可。下面的规则对于类和实例都适用:

  • 向变量赋于一个字面量值, 下面的例子@name将被推断为String类型,@age将是Int32类型。
  1. class Person
  2. def initialize
  3. @name = "John Doe"
  4. @age = 0
  5. end
  6. end
  • 使用类的new方法进行变量赋值,下面的例子@address的类型将被推断为Address
  1. class Person
  2. def initialize
  3. @address = Address.new("somewhere")
  4. end
  5. end
  6. # 普通类型也一样适用
  7. class Something
  8. def initialize
  9. @values = Array(Int32).new
  10. end
  11. end
  • 用指定类型的参数进行赋值
  1. class Person
  2. def initialize(name : String)
  3. @name = name # @name将被推断为String类型
  4. end
  5. end
  6. #精简语法
  7. class Person
  8. def initialize(@name : String)
  9. end
  10. end
  1. # 这个例子,编译会认为@name是String类型, 在编译时将报错
  2. class Person
  3. def initialize(name : String)
  4. name = 1
  5. @name = name
  6. end
  7. end
  • 使用一个有返回值类型的类方法赋值
  1. class Person
  2. def initialize
  3. @address = Address.unknown
  4. end
  5. end
  6. class Address
  7. # No need for a return type annotation here
  8. def self.unknown
  9. new("unknown")
  10. end
  11. def initialize(@name : String)
  12. end
  13. end
  • 使用一个带有默认值的参数赋值
  1. class Person
  2. def initialize(name = "John Doe")
  3. @name = name
  4. end
  5. end
  6. class Person
  7. def initialize(@name = "John Doe")
  8. end
  9. end
  • 使用Lib库的调用结果赋值
  1. class Person
  2. def initialize
  3. @age = LibPerson.compute_default_age
  4. end
  5. end
  6. lib LibPerson
  7. fun compute_default_age : Int32
  8. end
  • 使用out lib expression因为lib function必定有指定类型,编译器可以使用out指定参数类型(通常是指针)
  1. # @age类型是Int32
  2. class Person
  3. def initialize
  4. LibPerson.compute_default_age(out @age)
  5. end
  6. end
  7. lib LibPerson
  8. fun compute_default_age(age_ptr : Int32*)
  9. end
  • 其它规则 if: 将会对所有的流程分支进行扫描,推断的类型一般是所有分支产生类型的一个联合。@abc ||= 55: @abc类型将被推断为 Int32 | Nil常量赋值推断:
  1. # @luck_number类型为Int32
  2. class SomeObject
  3. DEFAULT_LUCKY_NUMBER = 42
  4. def initialize(@lucky_number = DEFAULT_LUCKY_NUMBER)
  5. end
  6. end

联合类型(Union types)

表达式或变量可以由多种类型组成,称之为联合。

  1. if 1 + 2 == 3
  2. a = 1
  3. else
  4. a = "hello"
  5. end
  6. a # : Int32 | String

上面的代码片段,a将获取 Int32和String两种类型,这个是在编译时由编译器确定。在运行时,a只可能取得一种类型。

  1. # The runtime type
  2. a.class # => Int32
  3. # The compile-time type
  4. typeof(a) # => Int32 | String

如果有需要,变量可以在编译时被定义为联合类型

  1. # set the compile-time type
  2. a = 0.as(Int32|Nil|String)
  3. typeof(a) # => Int32 | Nil | String

联合的类型规则 :一般情况下相T1和T2两种类型联合,联合的类型将会是T1 | T2 , 但也有例外。1. 继承相同Class的类的联合,返回类型为 class+

  1. class Foo
  2. end
  3. class Bar < Foo
  4. end
  5. class Baz < Foo
  6. end
  7. bar = Bar.new
  8. baz = Baz.new
  9. # Here foo's type will be Bar | Baz,
  10. # but because both Bar and Baz inherit from Foo,
  11. # the resulting type is Foo+
  12. foo = rand < 0.5 ? bar : baz
  13. typeof(foo) # => Foo+
  • 相同大小的元组形成的联合,返回类型为所有元组数据类型的集合。
  1. t1 = {1, "hi"} # Tuple(Int32, String)
  2. t2 = {true, nil} # Tuple(Bool, Nil)
  3. t3 = rand < 0.5 ? t1 : t2
  4. typeof(t3) # Tuple(Int32 | Bool, String | Nil)
  • 带有相同Key的命名元祖构成的集合, 看例子
  1. t1 = {x: 1, y: "hi"} # Tuple(x: Int32, y: String)
  2. t2 = {y: true, x: nil} # Tuple(y: Bool, x: Nil)
  3. t3 = rand < 0.5 ? t1 : t2
  4. typeof(t3) # NamedTuple(x: Int32 | Nil, y: String | Bool)

重载

类和类的方法都可以重载,方法重载可以写在同一个类定义中 也可以重复定义相同的类。最新的定义将覆盖之前的定义。

默认值和命名参数

  1. class Person
  2. def become_older(by = 1)
  3. @age += by
  4. end
  5. end
  6. def some_method(x, y = 1, z = 2, w = 3)
  7. # do something...
  8. end
  9. some_method 10 # x: 10, y: 1, z: 2, w: 3
  10. some_method 10, z: 10 # x: 10, y: 1, z: 10, w: 3
  11. some_method 10, w: 1, y: 2, z: 3 # x: 10, y: 2, z: 3, w: 1
  12. some_method y: 10, x: 20 # x: 20, y: 10, z: 2, w: 3
  13. some_method y: 10 # Error, missing argument: x

*与元组

通过使用*,方法可以接收不固定数量的参数。

  1. def sum(*elements)
  2. total = 0
  3. elements.each do |value|
  4. total += value
  5. end
  6. total
  7. end
  8. sum 1, 2, 3 #=> 6
  9. sum 1, 2, 3, 4.5 #=> 10.5
  10. # 传入的参数在方法内部转化成一个元组
  11. # elements is Tuple(Int32, Int32, Int32)
  12. sum 1, 2, 3
  13. # elements is Tuple(Int32, Int32, Int32, Float64)
  14. sum 1, 2, 3, 4.5
  15. # 如果方法还定义了其它的参数 ,在调用时 必须要指明参数名称
  16. def sum(*elements, initial = 0) # 定义了参数的默认值
  17. total = initial
  18. elements.each do |value|
  19. total += value
  20. end
  21. total
  22. end
  23. sum 1, 2, 3 # => 6
  24. sum 1, 2, 3, initial: 10 # => 16
  25. # 方法多态
  26. def foo(*elements, x)
  27. 1
  28. end
  29. def foo(*elements, y)
  30. 2
  31. end
  32. foo x: "something" # => 1
  33. foo y: "something" # => 2
  34. # *参数 也可以只用一个*表示 这时它并不是一个参数,它的意思是: 在方法调用时 *位之后的参数必须是带名称的
  35. def foo(x, y, *, z)
  36. end
  37. foo 1, 2, 3 # Error, wrong number of arguments (given 3, expected 2)
  38. foo 1, 2 # Error, missing argument: z
  39. foo 1, 2, z: 3 # OK
  40. # 在元组之前加* , 可以把元组展开 传入方法中
  41. def foo(x, y)
  42. x + y
  43. end
  44. tuple = {1, 2}
  45. foo *tuple # => 3
  46. # 双*(**) ,可以展开一个命名的元组 传入方法中
  47. def foo(x, y)
  48. x - y
  49. end
  50. tuple = {y: 3, x: 10}
  51. foo **tuple # => 7
  52. # 在定义时指定**
  53. def foo(x, **other)
  54. # Return the captured named arguments as a NamedTuple
  55. other
  56. end
  57. foo 1, y: 2, z: 3 # => {y: 2, z: 3}
  58. foo y: 2, x: 1, z: 3 # => {y: 2, z: 3}

类型限制

  1. def add(x : Number, y : Number)
  2. x + y
  3. end
  4. # Ok
  5. add 1, 2 # Ok
  6. # Error: no overload matches 'add' with types Bool, Bool
  7. add true, false
  8. # self restriction
  9. ...略
  10. # 字面量的类型限制
  11. 定义一个方法只接收参数Int32(不是实例), 可以使用 .class后缀
  12. def foo(x : Int32.class)
  13. end
  14. foo Int32 # OK
  15. foo String # Error
  16. # 可变参数中的类型限制
  17. def foo(*args : Int32)
  18. end
  19. def foo(*args : String)
  20. end
  21. foo 1, 2, 3 # OK, invokes first overload
  22. foo "a", "b", "c" # OK, invokes second overload
  23. foo 1, 2, "hello" # Error
  24. foo() # Error , 空参数不能与上面的任意一个匹配,必须重写一个不接收参数的foo方法。
  25. # 使用Object作为类型约束,是用来配对任意类型参数的简单方法。
  26. def foo(*args : Object)
  27. end
  28. foo() # Error
  29. foo(1) # OK
  30. foo(1, "x") # OK
  31. # 自由类型变量
  32. 通过使用forall , 可以使用一个或多个参数的类型来做类型限制
  33. def foo(x : T) forall T
  34. T
  35. end
  36. foo(1) #=> Int32
  37. foo("hello") #=> String

返回类型

方法的返回类型是编译器自动确定的,但是也可以手动指定类型

  1. def some_method : String
  2. "hello"
  3. end
  4. # 返回nil
  5. 指定Nil类型返回,程序就不需要在末尾再特别返回nil。使用Void也可以达到相同效果,在C绑定时,比较偏向使用Void
  6. def some_method : Nil
  7. 1 + 2
  8. end
  9. some_method # => nil

方法参数

…略

操作符

可以给类型赋于操作

  1. struct Vector2
  2. getter x, y
  3. def initialize(@x : Int32, @y : Int32)
  4. end
  5. def +(other)
  6. Vector2.new(x + other.x, y + other.y)
  7. end
  8. end
  9. v1 = Vector2.new(1, 2)
  10. v2 = Vector2.new(3, 4)
  11. v1 + v2 #=> Vector2(@x=4, @y=6)

二进制操作符

  1. + addition
  2. - subtraction
  3. * multiplication
  4. / division
  5. % modulo
  6. & bitwise and
  7. | bitwise or
  8. ^ bitwise xor
  9. ** exponentiation
  10. << shift left, append
  11. >> shift right
  12. == equals
  13. != not equals
  14. < less
  15. <= less or equal
  16. > greater
  17. >= greater or equal
  18. <=> comparison
  19. === case equality

下标操作符

  1. [] # array index (越界将报错)
  2. []? # array index (越界返回nil)
  3. []= # array index 赋值
  4. # 例子
  5. class MyArray
  6. def [](index)
  7. # ...
  8. end
  9. def [](index1, index2, index3)
  10. # ...
  11. end
  12. def []=(index, value)
  13. # ...
  14. end
  15. end
  16. array = MyArray.new
  17. array[1] # invokes the first method
  18. array[1, 2, 3] # invokes the second method
  19. array[1] = 2 # invokes the third method
  20. array.[](1) # invokes the first method
  21. array.[](1, 2, 3) # invokes the second method
  22. array.[]=(1, 2) # invokes the third method

可见性

使用private关键词定义: private def xxx

  1. 1. 私有方法: 只能在类的内部以及子类中调用 并且不需要加 self
  2. 2. 私有类型:
  3. class Foo
  4. private class Bar
  5. end
  6. Bar # OK
  7. Foo::Bar # Error
  8. end
  9. Foo::Bar # Error

受保护的(protected)方法: protected def xxx只能被与当前类型同类型的,或与当前类型在相同命名空间中的(class, struct, module, etc.)实例调用。例子, …略

在最外层定义的私有方法 和私有类,只能在定义的当前文件中可见。

继承

  1. class Person
  2. def initialize(@name : String)
  3. end
  4. def greet
  5. puts "Hi, I'm #{@name}"
  6. end
  7. end
  8. class Employee < Person
  9. end
  10. employee = Employee.new "John"
  11. employee.greet # "Hi, I'm John"

如果子类中定义了initialize方法,父类的构造子将不被调用。

  1. class Person
  2. def initialize(@name : String)
  3. end
  4. end
  5. class Employee < Person
  6. def initialize(@name : String, @company_name : String)
  7. end
  8. end
  9. Employee.new "John", "Acme" # OK
  10. Employee.new "Peter" # Error: wrong number of arguments
  11. # for 'Employee:Class#new' (1 for 2)

子类可以重载父类的方法(重写或者使用类型限制实现多态)。可以使用super关键词来调用父类方法

  1. class Person
  2. def greet(msg)
  3. puts "Hello, #{msg}"
  4. end
  5. end
  6. class Employee < Person
  7. def greet(msg)
  8. super # Same as: super(msg)
  9. super("another message") # 可以另外指定参数
  10. end
  11. end

抽象类

…略

类方法

不需要通过实例化就可以调用

  1. module CaesarCipher
  2. def self.encrypt(string : String)
  3. string.chars.map{ |char| ((char.upcase.ord - 52) % 26 + 65).chr }.join
  4. end
  5. end
  6. CaesarCipher.encrypt("HELLO") # => "URYYB"
  7. def CaesarCipher.decrypt(string : String)
  8. encrypt(string)
  9. end

类属性

在变量名称前使用 @@ , 存在于静态类中 所有实例可以共享之

  1. class Counter
  2. @@instances = 0
  3. def initialize
  4. @@instances += 1
  5. end
  6. def self.instances
  7. @@instances
  8. end
  9. end
  10. Counter.instances #=> 0
  11. Counter.new
  12. Counter.new
  13. Counter.new
  14. Counter.instances #=> 3

解构方法

在一个实例被回收时调用 finalize

  1. class Foo
  2. def finalize
  3. # Invoked when Foo is garbage-collected
  4. # Use to release non-managed resources (ie. C libraries, structs)
  5. end
  6. end