分享 分享给同是新手的 Rubyists,简单说明了些 Ruby 基础知识

dandananddada · 2015年05月13日 · 最后由 chigco 回复于 2015年11月17日 · 8362 次阅读
本帖已被设为精华帖!

写在前面

这是关于 Ruby 相对于其他语言而言一些要说明的知识点,方便同样刚接触 Ruby 不久的新手。有不对的或者不合理的地方还望指正。

Instance Variablse and Class Variables

Ruby 中的实例变量以 @ 符开始,和其他语言不同实例变量可以直接赋值使用,无需声明。

class Item
  def initialize(title)
    @title = title
  end
end

item = Item.new('Chia Ruby')

和其他语言一样,想通过实例访问实例变量需要添加 set 和 get 方法

class Item
  def title=(t)
    @title = t
  end

  def title
    @title     
  end
end

item = Item.new
puts item.title.inspect # => nil
item.title = "Chia Ruby"
puts item.title # => "Chia Ruby"

通过这种方式定义 get 和 set 方法会让代码变得冗长,Ruby 提供了帮助方法可以简化定义。

attr_reader – define instance-level getters attr_writer – define instance-level setters attr_accessor – define both

class Thing
  attr_accessor :foo, :bar
  attr_reader :baz

  def initialize
    @baz = "cat"
  end
end

thing = Thing.new
thing.foo = 1
thing.bar = 2
puts thing.baz # => "cat"

除了实例变量外,ruby 还有一种被叫做 class variable 的,用 @@ 表示,这种变量值会被子类中覆盖。

class Parent
  @@value = 1
  def self.value
    @@value
  end
end

class Child < Parent
  @@value = 2
end

puts Parent.value #=> 2 (because Child overwrote @@value)

另外如果在 main 中定义 class variables,他可以被所有类重写,也就是说其本质就相当于一个全局变量。

@@value = 3
puts Parent.value # => 3    

modules

在 ruby 中 module 可以看做是 method 的容器,module 是 Module 的实例。 其实可以把类看做是 modules 的一种特殊情况。 module 中可以定义实例方法也可以定义类方法,而类中的方法只能是实例方法。

module MyModule
  # Instance method defined on MyModule which is an instance of Module
  def MyModule.hello
    puts 'hello from module' 
  end

  # Same as "def MyModule.hello" because self is MyModule here
  def self.hello
    puts 'hello from module'
  end

  # Instance method for an instance of a class mixing-in MyModule
  def hello
    puts 'hello from instance'   
  end
end

MyModule.hello # => "hello from module"

module 中也可以定义实例变量。

module Fooable
  def self.foo=(value)
    @foo = value
  end

  def self.foo
    @foo
  end
end

Fooable.foo = "baz"
puts Fooable.foo # => "baz"

module 的用途一般都是将它融入到类中,这个时候虽然 module 不能实例化,但是因为融入到类的缘故它的实例方法也是可以被调用的。 modlule 融入到类有两种方法,一种是#include,另一种是#extendinclude – add a module’s methods as instance methods extend – add a module’s methods as class methods

module Helloable
  def hello
    puts "Hello World"
  end
end

class IncludeClass
  include Helloable
end

class ExtendClass
  extend Helloable
end

IncludeClass.new.hello
ExtendClass.hello

module 被引用两次的时候,第二次会被忽略掉

module HelloModule
  def say
    "hello from module"
  end
end

module GoodbyeModule
  def say
    "goodbye from module"
  end
end

class MyClass
  include HelloModule
  include GoodbyeModule
  include HelloModule
end

MyClass.new.say # => "goodbye from module"

两个 modules 定义了同一个方法,那么第一个会被第二个覆盖掉。

module Module1
  def hello
    "hello from module 1"
  end
end

module Module2
  def hello
    "hello from module 2"
  end
end

class HelloClass
  include Module1
  include Module2
end

HelloClass.new.hello # => "hello from module 2"

module 定义的方法不会覆盖类中定义的同名方法

module HelloModule
  def hello
    'hello from module'
  end
end

class HelloClass
  def hello
    'hello from class'
  end

  include HelloModule
end

HelloClass.new.hello # => 'hello from class'

Symbols

ruby 中的 Symbols 可以看做是其他语言的 enum,Symbol 以:开始。Symbol 也不需要事先声明,可以直接使用。

worker = {
  :name => "John Doe",
  :age => "35",
  :job => "Basically Programmer"
}

puts worker[:name] # => "John Doe"

这里有一点需要注意,最好不要动态创建 symbols,因为 symbols 本身不会被垃圾收集机制管理,一旦创建,直到程序退出才会释放。

Blocks and Storing Blocks

ruby 代码段可以用{}包裹,也可以用 do end 包裹。 像 Javascript 这样的语言,函数是可以赋值给变量,并通过变量传递给其他函数的,这样的函数被称作为 “first-class functions”,严格的说 Ruby 是不支持 first-class functions 的。但是 Ruby 可以用 procs 和 lambdas 存储区块的方式实现类似的功能。

add = lambda { |a, b| a + b }
puts add.call(1,2) # => 3

self

self 并不是一种特殊的语法,Ruby 允许用 self 在对象上定义方法。

a_string = "blerp"
def a_string.hello
  "hello"
end

a_string.hello # => "hello"

self 指代的是当前代码段的那个对象。

# At the top level, self is a special object called "main"
self # => main
self.class # => Object

# class definitions change the value of self
class SelfClass
  self # => SelfClass

  # Will be called on instances
  def self
    self
  end

  # Same as SelfClass.self
  def self.self
    self
  end
end

SelfClass.self # => SelfClass
SelfClass.new.self # => Instance of SelfClass

Lazy Initialization

大家习惯在构造函数中初始化实例变量。比如:

class Item
  attr_accessor :title

  def initialize
    @title = ""
  end
end

这样做存在一些弊端,譬如当有很多实例变量时,构造函数会很混乱,有些变量赋值后又不一定会被用到,我们不想在构造函数中赋值变量。这个时候可以用||=运算符。

class Item
  attr_writer :title

  def title
    @title ||= ""
  end
end

这样只有变量真正需要 set 的时候才会赋值(赋值为空的话就是一个空的字符串)。

Scope Resolution Operator (::)

当你定义了一个 module 或者 class 和 Ruby 核心的 module 重名的时候,你需要在你的 module 前加上::操作符。

module MyModule
  class File
  end

  class Thing
    def exist?(path)
      File.exist?(path)
    end
  end
end

thing = MyModule::Thing.new
thing.exist?('/etc/hosts') # => udefined method `exist?' for MyModule::File:Class

#add :: operator to the beginning of the colliding constant.
module MyModule
  class File
  end

  class Thing
    def exist?(path)
      ::File.exist?(path)
    end
  end

end

thing = MyModule::Thing.new
thing.exist?('/etc/hosts') # => true

instance_eval and class_eval

instance_eval/class_eval 允许在对象外部定义方法。 不同的是 class_eval 定义的是类级别的方法,也就是说调用对象是 class 或者 module,而 instance_eval 针对的是对象,它的调用对象本身就是一个 object,相对的 class_eval 创建的就是一个实例方法,而 instance_eval 创建的就是一个类方法了。

#class_eval
class Person
end

Person.class_eval do
  def say_hello
   "Hello!"
  end
end

jimmy = Person.new
jimmy.say_hello # "Hello!"
#instance_eval
class Person
end

Person.instance_eval do
  def human?
    true
  end
end

Person.human? # true

Privacy

在 ruby 中通过 private 关键词定义私有属性或方法。

class Secret
  def initialize
    @hidden = 123
  end

  private

  def hidden_method
    "You can't call me!"
  end
end

对象是无法直接调用私有方法的,可以通过 send 方式调用私有方法。

secret.send(:hidden_method) 

Eigenclasses

这是一种没有实际名称的类,这个类中定义了一些方法供其他类来使用。这个概念有点像匿名类。

letters = ['a', 'b', 'c']

class << letters
  def capitalize
    self.map do |letter|
      letter.upcase
    end
  end
end

letters.capitalize #=> ['A', 'B', 'C']

Summary

实例变量以 @ 开始,属于当前实例。 实例变量可以在任何地方创建,包括 modules。 没有赋值的实例变量默认为 nil。 类变量以 @@ 开始,几乎作为全局变量,很少使用。 module 是方法的容器 Class 是 modules 的一种,可以实例化并且有子类。 Ruby 不支持多重继承,但是多个 module 可以混合到一个类。 所有方法都是实例方法,类方法是一个类对象的实例方法。 代码块可以用 procs 或者 lambdas 存储引用。 Procs 像便携的代码,而 lambdas 像便携的方法。 Symbols 相当于枚举类型。 Symbols 不被垃圾机制回收,所有动态创建的时候要考虑到安全性。 self 返回的是当前的对象 相比静态语言中在构造函数中初始化变量,有时 ruby 的 lazy initialization 更优雅些。 instance_eval 可以让类或者实例执行一个方法体或者一段代码。 可以用 class_eval 去给类的实例定义方法。 Ruby 中静态变量命名规则为全大写字母。 私有方法不能够被直接调用。 对象可以通过 send 的方式调用方法。 method_missing 像 404 页面一样做方法的定义。

更正

1.symbol 可被垃圾机制回收 感谢@chaucerling指出。 Feature #7791 中指出在 ruby2.2 中 symbol 将会被垃圾机制回收,其中指出一些 symbols 将会存储在指定空间的内存中,当空间占满时,最新存入的 symbol 会替换掉最早存入的 symbols。 2.关于 prepend @zhang_soledad指出。 在 ruby2.0 中新增 prepend 用来在类中融入 module。 具体的一些介绍可以参考 wosuopu 的这篇文章:https://ruby-china.org/topics/25397 3.关于 instance_eval 和 class_eval 定义方法说明的更正 感谢@jyootai 指出。 这里我之前提到 instance_eval 用来定义类方法,class_eval 用来定义实例方法,确实有疏漏的地方。准确的说明如下: Use ClassName.instance_eval to define a class method. Use ClassName.class_eval to define an instance method. 参考:http://web.stanford.edu/~ouster/cgi-bin/cs142-winter15/classEval.php 也就是说这句话的前提是在类下声明方法的时候。

lambda 

good

:plus1: 我感觉楼主快要放出「元编程」大招了

本人由 java 转到 ruby,ruby china 社区的码农平均水平在中国真的算高的了。

在 2.2 之后,有一部分 Symbol 能被回收

楼主请继续

按照约定,今天已经更新完结~thx

我也是新手。

好文,slef -> self

辛苦了,lz~

很棒啊,学习了,收藏一下!

2.0 以后还有 prepend

modlue 拼错了。。貌似精华不能修改了么。。

你这不是新手啊!!!

学多长时间了你?

辛苦辛苦,很好的总结。

感谢分享,用一个小时都敲了一遍。 更详细点就好了

谢谢分享

这是赤果果地鄙视吾等真新手。。。。

24楼 已删除

刚听完麦子学院的赵阳 ruby 语言进阶,有很多共鸣。instance_eval 和 class_eval,define_methods 属于元编程内容,用于编写 lib。有些概念,没有高手讲解,怎可看书都无济于事。

有些地方的解释还是有误导的地方,比如:

1,module 中可以定义实例方法也可以定义类方法,而类中的方法只能是实例方法 2, ......而 instance_eval 创建的就是一个类方法了

针对 1 处,类中的方法只能是实例方法这点不准确; 2 处, instance_eval方法就是深入到对象内部执行操作,它可以访问对象的实例变量,也可以创建当前对象的实例方法,你的举例有一定的巧合性:

#instance_eval
class Person
end
Person.instance_eval do
  def human?
    true
  end
end
Person.human? # true

因为此处的 Person 对象本来就是 class 的实例,这时创建的 human? 就是 class 的实例方法,也就是 Person 这个类的类方法,如果这样操作:

class Person
end

obj = new Person
obj.instance_eval do
  def human?
    true
  end
end
obj.human? # true

这时的 human? 就是一个实例方法,所以你说的 instance_eval创建的就是一个类方法了就不准确。

虽然有些地方有误,但是给 LZ 这种分享精神点赞!

#26 楼 @jyootai 多谢指出,你的代码我试了下确实是可以跑通的,我又查询了下资料,确实在这里我的说法太不严谨了,已作修正~再次感谢。

学习了~ruby 里面有个 binding,我一直搞不懂怎么回事~希望楼主能讲讲~^^

#28 楼 @iaiae binding 可以看做是把一个代码段封装成为一个整体,然后你就可以重用这个代码段里面的东西了(比如局部变量、定义的方法等),一般它是配合和 eval,instance_eval,class_eval 方法(作为参数)使用的。

class A
  def hi
    @a = 'a'
    b = 'b'
    binding
  end
end

binding = A.new.hi # returns a binding object
eval("b.concat('aaaaa')", binding) # => "baaaaa"
eval("self", binding) # => "baaaaa" # => #<A:0x007f87022c8ce8 @a="a">
eval("instance_variable_get('@a')", binding) # => "a"

可参考:https://codequizzes.wordpress.com/2014/05/18/rubys-binding-class-binding-objects/

#29 楼 @dandananddada 今天终于看懂了~thx

cool,heihei

好文

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册