一步一步学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




本文作者:王德水
未经同意,禁止转载

你可能感兴趣的:(Ruby)