这是关于 Ruby 相对于其他语言而言一些要说明的知识点,方便同样刚接触 Ruby 不久的新手。有不对的或者不合理的地方还望指正。
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
在 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
,另一种是#extend
。
include – 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'
ruby 中的 Symbols 可以看做是其他语言的 enum,Symbol 以:开始。Symbol 也不需要事先声明,可以直接使用。
worker = {
:name => "John Doe",
:age => "35",
:job => "Basically Programmer"
}
puts worker[:name] # => "John Doe"
这里有一点需要注意,最好不要动态创建 symbols,因为 symbols 本身不会被垃圾收集机制管理,一旦创建,直到程序退出才会释放。
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 并不是一种特殊的语法,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
大家习惯在构造函数中初始化实例变量。比如:
class Item
attr_accessor :title
def initialize
@title = ""
end
end
这样做存在一些弊端,譬如当有很多实例变量时,构造函数会很混乱,有些变量赋值后又不一定会被用到,我们不想在构造函数中赋值变量。这个时候可以用||=
运算符。
class Item
attr_writer :title
def title
@title ||= ""
end
end
这样只有变量真正需要 set 的时候才会赋值(赋值为空的话就是一个空的字符串)。
当你定义了一个 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/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
在 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)
这是一种没有实际名称的类,这个类中定义了一些方法供其他类来使用。这个概念有点像匿名类。
letters = ['a', 'b', 'c']
class << letters
def capitalize
self.map do |letter|
letter.upcase
end
end
end
letters.capitalize #=> ['A', 'B', 'C']
实例变量以@开始,属于当前实例。 实例变量可以在任何地方创建,包括 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 也就是说这句话的前提是在类下声明方法的时候。