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

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

写在前面

这是关于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 也就是说这句话的前提是在类下声明方法的时候。

共收到 31 条回复
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

好文

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