《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
"
-
x
"
%~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是基于什么样的考虑允许这样的行为。
文章转自庄周梦蝶 ,原文发布时间5.17