庄周梦蝶

生活、程序、未来
   :: 首页 ::  ::  :: 聚合  :: 管理

ruby查缺补漏

Posted on 2007-04-07 10:30 dennis 阅读(2329) 评论(0)  编辑  收藏 所属分类: 动态语言
    《Programming Ruby中文版》前3部分我并不准备细看,毕竟我接触ruby也有一段时间了,只准备快速地掠过一遍,查缺补漏;重点放在第3部分的核心内容上,至于第四部分的参考手册更多作为工具书了。仅在此记录下一些值的注意的东西。

1.全局变量$_,默认当gets方法返回输入的行时,同时保存在全局变量$_,并且正则表达式如果作为条件语句(if或者while)时默认是跟这个全局变量进行匹配,而print参数为空时也是打印这个全局变量。这是早期ruby向perl语言学习的结果。可以看看这个例子:
while gets
  
if /Ruby/
      print
  end
end

这样的风格不值的提倡,全局变量的使用应该尽力减少,ruby也在逐渐脱离perl主义的风格

2.ruby中的单例模式:
class Logger
  private_class_method:new
  @@logger
=nil
  
def Logger.create
    @@logger
=new unless @@logger
    @@logger
  end
end
log1
=Logger.create
log2
=Logger.create

puts log1.object_id
puts log2.object_id

3.ruby中的block作用:
1)迭代器,通常是内部迭代器
2)事务Blocks,c#的using语句倒是跟这个有点像,其实就是让对象自身负责资源的打开和关闭,这是通过Kernel.block_given?实现的,比如File.open方法,当后面跟着一个block的时候,就会自动关闭打开的文件资源,如果不是,就需要自己处理。
3)作为闭包,与javascript和其他语言中的闭包概念一致,一个例子:
def n_times(thing)
  
return lambda {|n| thing*n}
end
p1
=n_times(23)
puts p1.call(
3)
puts p1.call(
2)
通过lambda方法将一个block转为Proc对象,尽管参数thing在block被真正调用时已经离开了作用范围,但是仍然可以使用

4.ruby中数字的最大长度取决于系统,这跟java,C#通过虚拟机规范的不同,数字类型的几个常用迭代器:times,upto,downto,step,如:
2.step(10,2){|i| print i,' '}  =>2,4,6,8,10

5.ruby中的字符串是8字节的序列,可以存储可打印的字符和二进制数据。比较有趣3种构建字符串常量方式:%q(对应于单引号定义的字符串),%Q(双引号)以及here documents,比如:
s=<<END_OF_STRING
   测试测试啦
END_OF_STRING

6.Range,书中翻译为区间,我倒更喜欢范围这个词。区间的3个用途:
1)用作序列,最常见的,如1..2,a..z等,可以定义自己的区间,只要实现succ和<=>比较方法
2)作为条件,书中的例子很经典:
while line=gets
   puts line 
if line=~/start/..line=~/end/
end

#利用全局变量简化为,不建议这样写
while gets
   
print if /start/../end/
end

3)作为间隔,看看某个值是否落入区间范围内,使用===操作符比较

7.正则表达式,这是重头戏。ruby中的perl风格的正则表达式,其实也是内建在ruby中的正则表达式对象的外部包装,关键的就是两个类Regexp类和MatchData类。一些peri程序员熟悉的记号:
$&    匹配的字符串
$`    匹配前的字符串
$'    匹配后的字符串
$1    第一个分组,$2,$3...类似
详细的就不抄书了,正则表达式我在学习javascript的时候已经系统地学过,倒是不感觉吃力。

8.在方法中定义可变长度参数,只要参数前加*号即可,java1.5也已经支持可变参数,比如Object...obj。
另外,在方法中,将数组展开为参数,可以在数组前加一个*号,比如:
def three(a,b,c)
   
print "this is #{a},#{b},#{c}"
end

three([
1,2,3)]
#上面这样调用报参数数目错误,正确的用法如下:
three(*[1,2,3)] =>this is 1,2,3
将hash列表直接做为参数,可能在2.0支持,目前采用的要求散列数组在正常的参数之后,并位于任何的block或者数组之前

9.ruby中的多线程:
1)ruby创建线程,见下面这个例子,开3个线程分别访问3个站点,并且对3个线程通过调用join方法,直到3个线程都结束,主线程才结束,来自书中例子:
require 'net/http'
pages
=%w(www.javaeye.com www.sina.com.cn www.blogjava.net)
$proxy_addr 
= 'x.x.x.x'
$proxy_port 
= 80
threads
=[]
for page_to_fetch in pages
  threads
<<Thread.new(page_to_fetch) do |url|
    h
=Net::HTTP.Proxy($proxy_addr, $proxy_port).new(url,80)
    puts 
"Fetcing:#{url}"
    resp
=h.get('/',nil)
    puts 
"Got #{url}:#{resp.message}"
  end
end    
threads.each{
|thr| thr.join}

2)线程中如何共享变量?可以通过[]=简单地把当前线程看成一个散列表,这里没有考虑同步问题:
count=0
threads
=[]
10.times do |i|
  threads[i]
=Thread.new do 
    sleep(rand(
0.1))
    Thread.current[
"mycount"]=count
    count
+=1
  end
end
threads.each{
|t| t.join;print t["mycount"],""}
puts 
"count =#{count}"

3)通过设置abort_on_exception,如果是true,未处理的线程异常将杀死所有正在运行的线程,如果是false,则杀死当前运行的线程,其他线程继续运行。修改上面的例子查看下:
count=0
threads
=[]
10.times do |i|
  threads[i]
=Thread.new(i) do |j|
    
raise "boom!" if j==4 
    sleep(rand(
0.1))
    Thread.current[
"mycount"]=count
    count
+=1
  end
end
threads.each do 
|t|
  begin
    t.join
    
print t["mycount"],""
  rescue RuntimeError
=>e
    puts 
"Failed:#{e.message}"
  end
end
puts 
"count =#{count}"
输出(随机的):
8, 1, 6, 3, Failed:boom!
2, 4, 7, 0, 5, count =9

在开头加上:
Thread.abort_on_exception=true
杀死所有的运行进程,报出异常,而不会产生输出。

4)通过线程的一系列方法:pass,join,value,stop来进行线程的调度
5)互斥的实现,与其他语言一样,不外乎加锁、信号量、队列的方式。看看加锁是如何做的,通过monitor库的关键字synchronize实现,如下面这个例子,两个线程递增同一个变量,似乎结果应该是20000:
#require 'monitor'
class Counter#<Monitor
  attr_reader:count
  
def initialize
    @count
=0
  
#  super
  end
  
def tick
  
#  synchronize do
      @count+=1
  
#  end
  end
end
c
=Counter.new
t1
=Thread.new{10000.times{c.tick}}
t2
=Thread.new{10000.times{c.tick}}

t1.join;t2.join

print c.count
很遗憾,结果不会是20000,而是比它小的一个数值,这里的问题就是因为访问共享资源没有进行同步的缘故,使用monitor库,请将上面代码中的注释去掉,可以得到正确的结果
使用monitor,不一定要使用继承,也可以使用mixin,甚至:
lock=Monitor.new
t1
=Thread.new{10000.times{lock.synchronize{c.tick}}}
还可以把特定的对象放入monitor,比如:
c=Counter.new
c.extend(MonitorMixin)
t1
=Thread.new{10000.times{c.synchronize{c.tick}}}
.

6)条件变量和队列的方式不准备抄书了,ruby中对线程的操作都是直接调用操作系统的命令,特别是*nix支持的非常好,可惜我对linux也是个初哥。

10.ruby中表达式很重要的一个特点是:任何表达式都有返回值,包括赋值语句、条件语句、循环语句之类。
1)ruby中对布尔表达式的规定是:任何不是nil或者常量false的值都为真
2)注意,在方法中调用访问属性的函数,需要写上调用者self,否则将处理为局部变量
3)defined?方法用于返回参数的描述,如果未定义,返回nil
4)逻辑表达式中,and和or的优先级低于&&,||
5)ruby没有for语句,因为ruby通过内建在对象中的迭代器提供了循环访问的能力,最简单的内建迭代器:loop do ....end
6)只要你的类支持each方法,你就可以使用for ... in ..语句循环它
7)对循环可以使用break(打断跳出),redo(从头重新循环,当前迭代),next进行调度。另外,还有retry,用于完全重新开始循环
8)while,until和for循环内建到了ruby语言中,但没有引入新的作用域:前面存在的局部变量可以在循环中使用,而循环中新创建的局部变量也可以在循环后使用。而被迭代器使用的block则不同,在block中创建的局部变量无法在block外访问。

11.ruby的异常处理
类似于java的try...catch...finnaly,ruby对应的是begin...rescue...ensure...end,将产生异常的代码放在这个块中进行处理。可以通过$!得到异常信息,或者提供局部变量名,我改写了一下我的google在线翻译机,增加异常处理,并用exit代替break:
require 'net/http'
def translate
  txt
=STDIN.gets
  exit 
if txt.strip=='e' or txt.strip=='exit'
  temp
=txt.split(' ')
  
if temp[1]=='1' or temp.size==1
    langpair
='en|zh-CN'
  
else
    langpair
='zh-CN|en'
  end
  
#使用代理  
  begin 
    $proxy_addr 
= 'localhost'
    $proxy_port 
= 80
    response 
= Net::HTTP.Proxy($proxy_addr,$proxy_port).post_form(URI.parse("http://translate.google.com/translate_t"),{'text'=>temp[0],'langpair'=>langpair})
    response.body 
=~ /<div id=result_box dir=ltr>(.*)<\/div>/
  rescue  StandardError 
=>e
    $stderr.
print "网络错误:"+e
  
else
    result 
= $1 
    puts 
'翻译内容:'+temp[0]
    puts 
'google返回:'+result
    puts 
'-------------------退出请打e或者exit---------------'
    translate
  end
end
translate
引发一个异常使用raise语句,重新引发当前异常,如果没有,就引发一个RuntimeError,常见使用方式:
raise InterfaceException,"keyboard failure",caller
其中的caller生成了栈的信息。另外,catch...throw语句用于在异常发生时从深度嵌套的结构中跳转出来。

12。关于模块,作用有二:作为命名空间和Mixin机制。模块的Mixin机制可以说是ruby的一个精华所在,通过Mixin,可以变相地实现了多重继承,并且可以动态地为类添加和删除功能。这一部分注意两点:
1)模块中定义的实例变量可能与包含模块的类的实例变量产生名称冲突。可以使用模块一级的散列表,以当前对象的ID做索引,来保存特定于当前模块的实例变量解决这个问题。比如:
module Test
  State
={}
  
def state=(value)
    State[object_id]
=value
  end
  
def state
    State[object_id]
  end
end
class Client
  include Test
end
c1
=Client.new
c2
=Client.new
c1.state
='A'
c2.state
='B'

puts c1.state
puts c2.state
2)是关于方法的查找路径,顺序是:当前类-》类的mixin模块-》超类-》超类的mixin,另外mixin的模块,最后混入的同名方法将覆盖前面混入的。

13.irb的配置和命令,今天发现irb原来也是可以玩出很多花样的。记录些有趣的:
1)可以使用按tab键两次来自动补全,要求加载irb/completaion库。比如这样启动irb:
 
irb -r irb/completion

或者进入irb后手工require:
require 'irb/completation'

当然,还有更好的方法,呆会介绍
2)子会话,在irb中使用irb可以创建子会话,通过命令jobs可以查看所有的子会话。创建子会话的时候指定一个对象,子会话的self将绑定该对象,比如:
irb 'test'
reverse
=>"tset"
length
=>4
self
=>"test"
irb_quit

3)在linux下可以通过配置.irbrc配置文件来进行初始化定制,在windows环境你可以在ruby安装目录下的bin看到一个irb.bat文件,通过配置文件来定制irb,比如我们为irb增加ri和tab自动补齐功能:
@echo off
goto endofruby
#!/bin/ruby
#
#
   irb.rb - intaractive ruby
#
       $Release Version: 0.9.5 $
#
       $Revision: 1.2.2.1 $
#
       $Date: 2005/04/19 19:24:56 $
#
       by Keiju ISHITSUKA(keiju@ruby-lang.org)
#

require 
"irb"
require 
'irb/completion'
def ri(*names)
  system(
%{ri.bat #{names.map{ |name| name.to_s}.join(" ")}})
end
if __FILE__ == $0
  IRB.start(
__FILE__)
else
  
# check -e option
  if /^-e$/ =~ $0
    IRB.start(
__FILE__)
  
else
    IRB.setup(
__FILE__)
  end
end
__END__
:endofruby
"%~d0%~p0ruby" -"%~f0" %*

4)常用命令:
exit,quit,irb_exit,irb_quit——退出
conf,context,irb_context——查看配置信息
irb <obj>——创建子会话,如果提供obj,作为self
jobs,irb_jobs——列出irb的子会话
irb_fg,fg n——切换子会话
kill n,irb_kill n——杀死一个irb子会话

14.类的实例变量,类除了类变量、实例变量外,还有一个类的实例变量的概念:
class Test
  #类的实例变量
  @cls_var 
= 123
  def Test.inc
    @cls_var 
+= 1
  end
  
class<<self
    attr_accessor:cls_var
  end
end
Test.inc
Test.inc


15.子类竟然可以改变父类定义方法的访问级别:
class Base
  
def aMethod
    puts 
"Got here"
  end
  private :aMethod
end

class Derived1 < Base
  public :aMethod
end

class Derived2 < Base
  
def aMethod(*args)
    super
  end
  public:aMethod  
end

d1
=Derived1.new
d2
=Derived2.new
d1.aMethod
d2.aMethod

不知道ruby是基于什么样的考虑允许这样的行为。














只有注册用户登录后才能发表评论。


网站导航: