一步一步学Ruby(十七):Ruby动态特性

Ruby中的一切都是动态的,例如,我们可以在程序运行时,动态的添加方法,类等。前面我们已经看到了Ruby的动态特性,例如:给单个对象添加方法,重新打开类等。

如果熟悉Rails,就知道ActiveRecord提供基于数据库表的字段名的方法。每一个字段都有一个方法,这个就依赖于Ruby的动态特性。

一、单例类的位置

我们可以为一个对象定义只属于自己的方法
obj=Object.new
def obj.say
    puts "Hello World"
end
obj.say  #输出 Hello World
那么单例方法定义在哪里呢?Ruby把单例方法定义在单例类(singleton class)中.每一个对象实际上都有两个类:
  • 多个实例共享的类
  • 单例类

对象调用的方法就是定义在这两个类中的方法,以及祖先类和混含模块中的方法,对象可以调用它所属的类的实例方法,也可以调用单例类中的方法。单例类是匿名的,他们是类对象的实例(Class类的实例),但他们是自动生成且没有名字

str="Hello World"
class<<str
   def bye
      self+", bye"
   end
end
puts str.bye

输出 "Hello World, bye"

在方法查找上,单例方法是最先找到的。

二、eval方法

这个和其它很多语言一样,具有在运行时执行以字符串形式保存代码的的功能。

1.直接执行代码

image

2. 运行时提供方法名的例子
print "Greeting Method:"
m=gets.chomp
eval("def #{m};puts 'Hello'; end")
eval(m)
输出:Hello
如果输入hi, eval求职的字符串是 def hi; puts 'Hello'; end
三、eval的危险性
eval很强大,但是它也潜在着危险,这个有点像sql注入
假如,上面我们输入的不是hi而是下面的内容
hi; end; system("rm -rf /*"); #

eval求值以后是这样的

def hi; end; system("rm -rf /*"); # puts  'Hello'; end
求值的结果是:#后面的所有内容会被作为注释忽略掉。使用system命令试图删除系统所有文件
Ruby有一个全局变量$SAFE(取值范围是0到4)、以获取对如非法写文件这一类危险的防护。
四、instance_eval
该方法把self变为instance_eval调用的接收者,对字符串或代码块进行求职
p self
a=[]
a.instance_eval(p self)
输出:
main
[]
instance_eval常用于访问其它对象的私有数据,特别是实例变量
class C
  def initialize
    @a=1
  end
end
c=C.new
c.instance_eval {puts @a}
 
五、class_eval
class_eval可进入类定义体中
c=Class.new
c.class_eval do
  def some_method
    puts "created in class_eval"
  end
end

c=c.new
c.some_method
利用class_eval可以访问外围作用域的变量。
var="test variable"
class C
  puts var
end
C.class_eval {puts var}
变量var在标准的类定义体的作用域之外,但是在class_eval的代码块的作用域之内
image
当在class_eval的块中定义一个实例方法时,又有不同
var="test"
class C
end
C.class_eval {def hi; puts var; end}
c.new.hi
# undefined local variable or method `c' for main:Object (NameError)
但我们可以使用另外一种方法
C.class_eval {define_method("hi"){ puts var}}
六、Proc对象
pr=Proc.new {puts "Hello from inside of proc block"}
pr.call
#输出: Hello from inside of proc block
1、做为闭包的Proc对象
Proc对象随身携带了它的上下文,下面上下文包含一个变量a,该变量被赋值为一个特殊的字符串保存在Proc对象中。像这样带着产生它的上下文信息的
一段代码被称为闭包。产生一个闭包就像是打包一件行李:任何时候打开行李,都包含打包进去的东西,在打开一个闭包时(通过调用它),它包含产生
它的时候你放进去的东西。
def call_proc(pr)
  a="Jack"
  puts a
  pr.call
end
a="Tom"
pr=Proc.new {puts a}
pr.call
call_proc(pr)
#输出: Tom
#      Jack
#      Tom
2. Proc对象的参数
产生Proc对象时所提供的代码块可以接收参数
pr=Proc.new {|x| puts "#{x} is better man"}
pr.call("Jack")
输出:Jack is better man
七、匿名函数(lambda)
lam=lambda {puts "Hello World"}
lam.call
输出: Hello World
lambda不是Lambda类的对象,他们是Proc类的对象
lam.class
输出: Proc

和所有的Proc对象一样,lambda是闭包;他们随身携带了生成他们的局部的上下文环境

lambda生成的Proc对象和用Proc.new生成的对象之间的差别与return有关,lambda中的return从lambda返回,而Proc中的return从外围方法返回

def test_return
  l=lambda {return}
  l.call
  puts "I am here"
  p=Proc.new {return}
  p.call
  puts "bye"
end
test_return
输出: "I am here"
八、再论代码块
可以在方法中吧代码块转换成Proc对象,可以通过参数中最后一个变量,且必须&开头来捕获代码块
def say_block(&block)
  block.call
end
say_block {puts "How are you?"}
#output: how are you?
def test_block(x,y,  &block)
  puts x+y
  block.call
end
test_block(1,2) {puts "Hi"}
#output: 3 
#        Hi
也可以将Proc对象或lambda转换为代码块
def say_block(&block)
  block.call
end

lam=lambda {puts "Hello world"}
say_block(&lam)
#output: Hello world

本文作者:王德水
未经同意,禁止转载
原文地址:https://www.cnblogs.com/cnblogsfans/p/1391004.html