线程是指共享内存空间并可同时运行的控制流。使用线程便可进行并行编程。
现在Ruby的线程是用户级的线程,可在所有平台上实现相同的运作。下面就来看看线程的运作情况。
Ruby线程的运作方式
程序开始时同步生成的线程称作main thread。当main thread结束时,其他所有线程以及整个程序也会结束。用户的介入所引起的异常也会被送给main thread。
线程启动时会指定一个块。当这个块结束运行时,该线程也会终结。块的结束既包括正常结束又包括因错误等引起的异常结束。
Ruby的线程调度采用的是一种带优先顺序的循环调度法。每隔一段时间或者当运行中的线程放弃权利的时候,就对进行调度。从可运行的线程中选出优先级别最高的加以运行。
线程和异常
当某线程发生异常,且没有被rescue捕捉到时,该线程通常会被无警告地终止。但是,若有其它线程因为Thread#join的关系一直等待该线程的话,则等待的线程同样会被引发相同的异常。
begin
t = Thread.new do
Thread.pass # 主线程确实在等join
raise "unhandled exception"
end
t.join
rescue
p $! # => "unhandled exception"
end
使用下列3个方法,就可以让解释器在某个线程因异常而终止时中断运行。
* 启动脚本时指定-d选项,并以调试模时运行。
* 用Thread.abort_on_exception设置标志。
* 使用Thread#abort_on_exception对指定的线程设定标志。
当使用上述3种方法之一后,整个解释器就会被中断。
安全模型
为了安全地运行CGI等程序,Ruby设置了安全结构。
Ruby的安全模型由“对象的污染”和“安全级别”构成。
对象的污染
Ruby有时会认为对象“遭到了污染”,这主要有两种用途。
第一,以不安全的输入为基础制成的对象就是“受污染”的对象,不能用作“危险操作”的参数。这主要是为了防止恶意数据导致程序作出一些意外的危险动作。
第二,可以使安全对象(未遭污染的对象)得到保护,免遭不安全对象的威胁。若安全级别为4,则对未受污染的对象进行操作时就会受到很多限制,这正体现了对于安全方面的考虑。
与对象的污染有关的方法
Object#taint
污染对象
Object#tainted?
若对象受到了污染就返回真
Object#untaint
消除对象受到的污染
安全级别
每个线程都有特有的“安全级别”。安全级别越高,操作受到的限制也就越多。线程局部变量$SAFE标明了安全级别。
[ruby-list:37415]
$SAFE的相关规则
* 程序开始时$SAFE的值为0
* 各线程在生成时继承父线程的$SAFE值
* 不能降低现有的$SAFE值
从原则上讲,低安全等级时的限制也适用于高安全等级。例如,若某操作在1级就被禁止的话,在2级就更不可能通过了。
0级
默认的安全级别。
被污染对象
*
可从IO、环境变量或命令行参数(ARGV)中获得的字符串
(只有环境变量PATH例外)
环境变量PATH比较特殊,只有当其值中含有危险路径时才会受到污染。
这时所说的危险路径是指,谁都可以变更或写入的路径。从根目录起层层检查,若包含谁都可以更改的地方的话,该路径就是危险的。
禁止的操作
* 没有
1级
特指以安全程序处理不安全数据的情况。适合于用CGI等处理用户的输入。
被污染对象
* 与0级相同
禁止的操作
* 下列以受污染字符串为参数的操作
o Dir, IO, File、FileTest的类方法、方法
o 使用FileTest操作符、比较文件的更新时间
o 执行外部命令(system, exec, ``)
o eval (参考4级的说明)
o 加载顶层(若使用第二参数进行wrap则可以执行)
o require
o trap
* 执行外部命令(只有当环境变量PATH中包含危险路径时)
2级
被污染对象
* 与1级相同
禁止的操作
在1级限制的基础上,以下操作也被禁止。
* Dir.chdir Dir.chroot Dir.mkdir Dir.rmdir
* File.chown File.chmod File.umask File.truncate File#lstat File#chmod File#chown File#delete File#unlink File#truncate File#flock 以及FileTest模块的方法
* IO#ioctl, IO#fcntl
* Process.fork Process.setpgid Process.setsid Process.setpriority Process.egid= Process.kill
* 使用危险路径load
* 以被污染字符串为参数的load(即使被wrap也不行)
* syscall
* exit!
* trap
3级
所有生成的对象都被污染。适于为在4级状态下运行程序提供环境。
被污染对象
* 所有生成的对象
禁止的操作
在2级限制的基础上,以下操作也被禁止。
* Object#untaint
4级
执行不安全程序时等级。
此时,3级时禁止的“受污染字符串的eval”却被解禁。(这是因为用eval时,所有的危险操作都已经被禁止了。)
被污染对象
* 与3级相同。
禁止的操作
在3级限制(如上所述,不包括eval)的基础上,以下操作也被禁止。
* Object#taint
* 改变顶层的定义(autoload, load, include)
* 对既存方法的再定义
* 改变Object类的定义
* 改变未被污染的类和模块的定义或改变类变量
* 改变未被污染的对象的状态
* 改变未被污染的全局变量
* 使用未被污染的IO及File的处理
* 输出到IO
* 程序的终结(exit, abort)(且out of memory也不fatal)
* 对其他线程造成影响的Thread类的操作以及其他线程的Thread#[]
* ObjectSpace._id2ref
* ObjectSpace.each_object ruby 1.7 feature
* 改变环境变量
* srand
其他的安全级别相关信息
* 当$SAFE = 0时才执行require
* 若超过Level1的话,启动时会有下列不同
o 不把环境变量RUBYLIB添加到$:之中
o 不把当前目录添加到$:之中
o 不处理环境变量RUBYOPT
o 不能使用下列开关 -s -S -e -r -i -I -x (就算脚本被setgid, setuid也是如此)
o 不会从标准输入读入程序 (就算脚本被setgid, setuid也一样)
* 被setuid, setgid的脚本将在超过$SAFE = 1的状态下运行。
* 在3级以上的环境中生成的Proc将会记下该时刻的安全级别。若受污染的Proc对象被call的话,它将以记忆的安全级别来运行。
* 若受污染的Method对象被call的话,将以4级状态运行。
* 若将受污染的字符串指定为trap/trace_var的第二参数时,将以4级状态运行ruby 1.7 feature:在 version 1.7中,若将受污染的字符串指定为第二参数而运行trap/trace_var的话,马上就会引发异常SecurityError。
* 超过4级的话,即使out of memory也不会fatal。
* 根据您安装情况的不同,Fixnum Symbol true false nil可能不会被污染。但请注意Bignum Float可能会受到污染。
实例
$SAFE级别一旦升高就不能调低了。如下所示,可以使用线程将程序的一部分置入高安全级别状态下运行。
例:
def safe(level)
result = nil
Thread.start {
$SAFE = level
result = yield
}.join
result
end
safe(4) { puts "hello" } # 因为是$SAFE所以例外
puts "world" # 外部不受影响
扩展库中的应对
* 在扩展库中,有必要对对象的污染状态进行适当的传播。
* 改变全局状态或与外部联系之前,有必要检查安全级别。
[ruby-list:37407]
正则表达式
* 后方参考
* 字符范围
* 回缩(backtrack)
* 范例
下面就来讲讲ruby支持的正则表达式符号(元字符)。
有这么个规则:
* 不带\的数字和字母不是元字符
* 带\的符号也不是元字符
下文中出现的“匹配多字节字符的正则表达式”是指,通过使用$KCODE进行设定,或显式地使用汉字选项(请参考正则表达式字面值)等方式进行的匹配多字节字符的正则表达式。
*
^
行首。与字符串的头部或换行符之后的位置相匹配。
*
$
行尾。与字符串的尾部或换行符之前的位置相匹配。不包括换行符本身。
ruby 1.8 特性:以前,只匹配字符串尾部换行符前的位置,现在则扩大到字符串的尾部。trap::Regexp
p "\n".gsub(/$/, "o")
=> "o\n" (1.6)
=> "o\no" (1.8)
*
.
匹配除换行符以外的任意一个字符。使用正则表达式选项 m(多行模式。请参考正则表达式字面值) 时,则匹配包括换行符在内的任意一个字符。在匹配多字节字符的正则表达式中,则匹配一个字(非单字节)。
当遇到不完整的多字节字符的一部分(无法判断该字符是多字节字符?二进制?还是ASCII)时,也不会匹配。
p /./e =~ "あ"[0,1] # => nil
*
\w
字母和数字。等同于[0-9A-Za-z]。
若为匹配多字节字符的正则表达式时,则也会匹配日语的全角字符。
*
\W
非字母和数字。\w以外的单个字符。
*
\s
空字符。相当于[ \t\n\r\f]
*
\S
非空字符。[ \t\n\r\f] 以外的单个字符。
*
\d
数字。即[0-9]
*
\D
非数字
*
\A
字符串头部。与^不同的是,它不受有无换行符的影响 。
*
\Z
字符串尾部。若字符串以换行符结尾,则匹配换行符前的位置。
ruby 1.8 特性:以前,只匹配字符串尾部换行符前的位置,现在则扩大到字符串的尾部。trap::Regexp
p "\n".gsub(/\Z/, "o")
=> "o\n" (1.6)
=> "o\no" (1.8)
*
\z
字符串结尾。与$以及\Z不同的是,它不受有无换行符的影响。
*
\b
在字符范围描述符之外时表示词边界(匹配从\w到\W)。在字符范围描述符之内时表示退格符(0x08)。
*
\B
非词边界
*
\G
在上次成功匹配的地方(之后)进行匹配(不留余地)。只有在首次使用时才会匹配到头部(与\A相同)。
可以用在scan和gsub中。当您想在上次匹配的地方之后再进行匹配的话,可以使用。
举个简单(没什么用)的例子。
# 从头取出3位数字(数字必须相连)。
str = "123456 789"
str.scan(/\G\d\d\d/) {|m| p m }
*
[ ]
指定字符范围。请参考字符范围
* 。
*
前面元素至少出现0次。尽可能匹配较长的部分。
*
*?
负责指定数量(quantifiers)。表示前面元素至少出现0次(尽量匹配短的部分)
*
+
负责指定数量(quantifiers)。表示前面元素至少出现1次
*
+?
负责指定数量(quantifiers)。表示前面元素至少出现1次(尽量匹配短的部分)
* {m}
* {m,}
*
{m,n}
指定元素重复出现的次数(interval quantifier)。分别表示前面元素重复出现
o m 次
o 至少 m 次
o 至少 m 次,至多 n 次
{,n} 或 {,} 将导致匹配失败。
str = "foofoofoo"
p str[/(foo){1}/] # => "foo"
p str[/(foo){2,}/] # => "foofoofoo"
p str[/(foo){1,2}/] # => "foofoo"
正则表达式 ?, *, + 分别等同于 {0,1}, {0,} {1,} 。
* {m}?
* {m,}?
*
{m,n}?
指定元素重复出现的次数(interval quantifier)。分别表示前面元素重复出现
o m 次
o 至少 m 次
o 至少 m 次,至多 n 次
(尽量匹配短的部分)。
*
?
负责指定数量(quantifiers)。表示前面元素至多出现1次。
*
??
负责指定数量(quantifiers)。表示前面元素至多出现1次(尽量匹配短的部分)
*
|
选择(alternative)。
*
( )
正则表达式的群组化。与括号中的正则表达式相匹配的字符串将被保存下来,供后方参考使用。
*
\1, \2 ... \n
后方参考(back reference)。请参考后方参考。
*
(?# )
注释。括号中的任意字符串将被忽视。
*
(?: )
不具备后方参考功能的群组化。它不为\1,\2(或$1,$2)提供服务,是一种单纯的群组功能。
/(abc)/ =~ "abc"
p $1
=> "abc"
/(?:abc)/ =~ "abc"
p $1
=> nil
*
(?= )
先行(lookahead)。使用模式(pattern)指定位置(不留间隔)
(?=re1)re2
表示将匹配同时符合re1和re2的要求的字符串。
re1(?=re2)
という山附は、稿に re2 とマッチする矢机误が鲁く、正则表达式 re1 です。
p /foo(?=bar)/ =~ "foobar" # => 0
p $& # => "foo" (bar の婶尸の攫鼠はない)
*
(?! )
否定先行(negative lookahead)。使用否定的模式(pattern)来指定位置(不留间隔)
(?!re1)re2
该正则表达式表示,匹配re1但不匹配re2。
# 除000以外的3位数字
re = /(?!000)\d\d\d/
p re =~ "000" # => nil
p re =~ "012" # => 0
p re =~ "123" # => 0
# C语言标识符 (首位是[A-Za-z_]然后是[0-9A-Za-z_]的字符串)
/\b(?![0-9])\w+\b/
*
(?> )
禁用回缩功能。
该功能尚处于试验阶段。将来有可能被停用,请您注意使用。特别是不要在广义库中使用。
*
(?ixm-ixm)
正则表达式中的i选项、x选项、m选项的开关。请您参考正则表达式字面值来了解选项的详细内容。
re = /A(?i)a(?-i)A/
p re =~ "AaA" # => 0
p re =~ "AAA" # => 0
p re =~ "AAa" # => nil
*
(?ixm-ixm: )
括号中的i选项、x选项、m选项的开关。在括号范围内有效。
re = /A(?i:a)A/
p re =~ "AaA" # => 0
p re =~ "AAA" # => 0
p re =~ "AAa" # => nil
后方参考
正则表达式 \1 \2 ... \n 表示后方参考。\n表示将匹配第n个括号(正则表达式的()表示群)的内容保存起来,供后面使用。
/((foo)bar)\1\2/
和
/((foo)bar)foobarfoo/
是一样的。
例:
re = /(foo|bar|baz)\1/
p re =~ 'foofoo' # => 0
p re =~ 'barbar' # => 0
p re =~ 'bazbaz' # => 0
p re =~ 'foobar' # => nil
对应的括号必须位于后方参考表达式的左侧。
若后方参考表达式位于对应的括号中时,匹配常常会失败。当后方参考表达式中的数字是1位,且没有对应的括号时,匹配也将失败。
p /(\1)/ =~ "foofoofoo" # => nil
p /(foo)\2/ =~ "foo\2" # => nil
虽然可以指定2位以上的后方参考表达式,但是不要把它同反斜线表示法的\nnn(对应于8进制数nnn的字符)混为一谈。当数字只有1位时,通常是后方参考表达式。当指定了一个超过2位的数字时,若没有对应括号的话,则被看作是8进制代码。
相反地,若在正则表达式中使用1位的8进制代码时,必须以0打头,例如\01等(不可能存在形如\0这样的后方参考表达式,因此不会混淆)。
p /\1/ =~ "\1" # => nil # 无对应括号的后方参考
p /\01/ =~ "\1" # => 0 8 进制代码
p /\11/ =~ "\11" # => 0 8 进制代码
# 8 进制代码 (因为没有对应括号)
p /(.)\10/ =~ "1\10" # => 0
# 后方参考 (因为有对应的括号)
p /((((((((((.))))))))))\10/ =~ "aa" # => 0
# 8 进制代码 (因为没有像"\0" + "8" -> \08 这样的8进制代码)
p /(.)\08/ =~ "1\0008" # => 0
# 如果想在后方参考表达式之后插入数字的话,就必须使用括号加以分隔。
p /(.)(\1)1/ =~ "111" # => 0
字符范围
正则表达式 [] 负责指定字符范围。这将匹配 [] 内列出的任何一个字符。
例如/[abc]/表示只要匹配"a", "b", "c"中任何一个即可。也可以按照ASCII代码顺序,在连续的字符串之间插入“-”后写成/[a-c]/也是一样的效果。另外,若头上是“^”的话,表示要匹配指定字符之外的一个字符。
若“^”不在头上的话,表示匹配该字符本身。同时,当“-”出现在头或尾上时,表示匹配该字符本身。
p /[a^]/ =~ "^" # => 0
p /[-a]/ =~ "-" # => 0
p /[a-]/ =~ "-" # => 0
p /[-]/ =~ "-" # => 0
空的字符范围将引发错误。
p /[]/ =~ ""
p /[^]/ =~ "^"
# => invalid regular expression; empty character class: /[^]/
当“]”出现在头上(或否定的“^”之后)时,表示“]”本身,而并非字符范围的结尾。
p /[]]/ =~ "]" # => 0
p /[^]]/ =~ "]" # => nil
可以使用反斜线对"^", "-", "]" 以及 "\\"(反斜线)进行转义,使其匹配该字符本身。
p /[\^]/ =~ "^" # => 0
p /[\-]/ =~ "-" # => 0
p /[\]]/ =~ "]" # => 0
p /[\\]/ =~ "\\" # => 0
在[]中可以使用反斜线表示法以及正则表达式\w, \W, \s, \S, \d, \D (这些都是表示字符范围的简写法)。
请注意,下列包含否定意味的字符范围也将匹配换行符(正则表达式 \W,\D 也是如此)。
p /[^a-z]/ =~ "\n" # => 0
字符范围中也可以使用下列特殊的表达法,但是,将来这些表达法是否会继续得到支持还未可知(所以此处从略,欲知详情请参考grep(1)的手册)。
[:alnum:] 数字和字母 0-9a-zA-Z
[:alpha:] 字母 a-zA-Z
[:blank:] 空白类
[:cntrl:] 控制字符
[:digit:] 数字
[:graph:] 除空白以外的可打印可视字符
[:lower:] 小写字母
[:print:] 可视字符
[:punct:] 符号
[:space:] 空白字符
[:upper:] 大写字母
[:xdigit:] 16进制字符
例: (包括"[]"在内,"[:...:]"表示1个字符。并非文字类的"[]")
p /[[:alnum:]][[:cntrl:]]/ =~ "a\x01" # => 0
注: 全角字符不在考虑范围之内。即使指定让正则表达式对汉字进行匹配时,[:alpha:]等也不会匹配全角的字母。
p /[[:alpha:]]/e =~ "A" # => nil
回缩(backtrack)
用特殊括号(?> )将正则表达式括起来后,与该正则表达式相匹配的字符串中的回缩功能就将失效。举例如下。
例如在通常的正则表达式中
p /(a*)ab/ === 'aaab'
是匹配的。该匹配过程如下所示。
1. 正则表达式 a* 从索引0开始匹配3个a
2. 正则表达式 a 匹配失败
3. 正则表达式 a* 将匹配部分稍稍“缩小”一下,匹配2个a(使用了回缩功能)
4. 正则表达式 a 与字符a匹配
5. 正则表达式 b 与字符b匹配
但是,如果用括号(?> )把正则表达式括起来的话,就不再匹配了。详细过程如下。
1. 正则表达式 a* 从索引0开始匹配3个a
2. 正则表达式 a 匹配失败
3. a* 想把匹配部分回缩一下,但由于特殊括号的作用,回缩功能失效。
4. 正则表达式 a* 从索引1开始匹配2个a
接下来的匹配都不成功,最终导致整体匹配失败。
简单说来,通常的正则表达式是“贪婪型的匹配”,而(?> )则是“超贪婪型的匹配”,因为它一旦匹配成功就决不放手。
范例
为了便于您拷贝使用,我们将其代入到以$re_开头的全局变量中。
数值
*
浮点数(包括整数)
$re_float = /[-+]?(?:[0-9]+(\.[0-9]*)?|(\.[0-9]+))([eE][-+]?[0-9]+)?/
p $re_float =~ "1.23" # => 0
p $&.to_f # => 1.23
p $re_float =~ ".23" # => 0
p $&.to_f # => 0.23
p $re_float =~ "1.23e1" # => 0
p $&.to_f # => 12.3
p $re_float =~ "1.23e-1" # => 0
p $&.to_f # => 0.123
用逗号将数字划分成3位一组的形式
*
方法1:使用回行和先行的方法(回行(lookbehind)需要Oniguruma库的支持)
p "tone of 12345Hz".gsub(/(?<=\d)(?=(?:\d\d\d)+(?!\d))/, ',')
=> ruby 1.8.0 (2003-08-07) [i586-linux]
"tone of 12,345Hz"
*
方法2:只使用先行的方法
p "tone of 12345Hz".gsub(/(\d)(?=(?:\d\d\d)+(?!\d))/, '\1,')
=> ruby 1.8.0 (2003-08-07) [i586-linux]
"tone of 12,345Hz"
*
方法3:不使用先行的方法
s = "tone of 12345Hz"
nil while s.gsub!(/(.*\d)(\d\d\d)/, '\1,\2')
p s
=> ruby 1.8.0 (2003-08-07) [i586-linux]
"tone of 12,345 Hz"
字句构造
* 标识符
* 注释
* 内嵌文档
* 保留字
现在Ruby使用的是ASCII字符集。对英文字母的大小区别是敏感的。您可以在任何地方插入空字符或注释,除了标识符和部分字面值的中部。空字符包括space,tab,垂直tab,backspace,回车,换页。换行比较特殊,若换行后内容是继续上一行内容的话,该换行符就是空字符,除此以外的换行符就被解释为语句的切分。
标识符
例:
foobar
ruby_is_simple
Ruby的标识符的首位由字母或下划线('_')构成,后面部分可以是字母、下划线('_')或数字。对标识符的长度没有限制。
注释
例:
# this is a comment line
遵从脚本语言的习惯,Ruby把 除了字符串内部和数值字面值'?#'之外的 以#开始的行当作注释行来处理。
内嵌文档
例:
=begin
the everything between a line beginning with `=begin' and
that with `=end' will be skipped by the interpreter.
=end
可以在Ruby代码中嵌入文档。从行首为=begin的行开始到行首为=end的行为止的部分就是内嵌文档。虽然Ruby解释器对内嵌文档的内容没有什么要求,但是内嵌文档的格式最好是RD。
保留字
下列词语就是Ruby的保留字。
BEGIN class ensure nil self when
END def false not super while
alias defined? for or then yield
and do if redo true
begin else in rescue undef
break elsif module retry unless
case end next return until
保留字不可用作类名和变量名。但若这些词语前面出现$,@,@@等前缀的话,就不再是保留字了。当在def定义中或方法调用操作符'.'后面出现这些词语时,则可确定它们是方法名,这时就可以使用这些词语。
程序
* 表达式
* 程序的结束
将表达式连接起来就构成程序。使用分号(;)或换行将表达式分隔开来。但反斜线后出现的换行并非分隔符,它表示该行将继续下去。
例:
print "hello world!\n"
表达式
例:
true
(1+2)*3
foo()
if test then ok else ng end
Ruby的表达式包括,变量和常数、各种字面值、相关计算和赋值、if或while等控制结构、方法调用、类/方法的定义。
还可以使用括号将表达式括起来进行群组化。
空表达式 () 返回nil。
Ruby的表达式包括返回值的表达式和不返回值的表达式。
不返回值的表达式的例子
* while, until, while 修饰表达式, until 修饰表达式
另外,有的表达式不能用作某方法的参数,而有的则可以(有时将这种表达式称作“句”以示区别)。
不能用作参数的表达式的例子
* and, or, not
* if/unless/rescue 修饰表达式, ...
若使用括号将那些不能用作参数的表达式括起来进行群组化之后,就可以像使用普通表达式一样使用它们了。
程序的结束
Ruby解释器在读取程序时,若遇到下列标识就会停止读取动作。
* 文件的末尾(eval中字符串的末尾)
* ^D(control D)、^Z(control Z)
* 只包含__END__的行(若前后出现空白字符将无法辨认)
变量和常数
* 局部变量
* 实例变量
* 类变量
* 全局变量
* 伪变量
* 常数
o 引用常数的优先顺序
您可以通过区分Ruby变量名的首位字符来确定它是局部变量、实例变量、类变量、全局变量还是常数。通常情况下,变量名的第二位字符以后是数字、字母或下划线,但有的内部变量名比较特殊,如“'$'+1个符号”(请参考内部变量)。变量名长度只受内存大小的限制。
局部变量
例:
foobar
若标识符首位是小写字母或“_”,则该标识符就是局部变量或方法调用。在局部变量的作用域(类、模块、方法的定义部分)内,若对一个首位是小写字母的标识符进行首次赋值的话,也就意味着声明了一个属于该作用域的局部变量。若引用尚未被声明的标识符的话,就会被解释成一个无参数的方法调用。
局部变量的作用域起始于声明处,结束于该声明所在的块、方法定义、类/模块定义的结尾。随着块的消亡,局部变量也将寿终正寝(顶层局部变量则一直持续到程序终结),但也有例外。若块已经变成过程对象的话,则局部变量将一直持续到该过程对象终结为止。若多个过程对象引用同一个作用域的话,局部变量将被这些对象所共享。
# (A)的部分位于作用域之外
2.times {
p defined?(v) # (A)
v = 1 # 从(开始声明)起
p v # 到(块的终结)是 v 的作用域
}
# => nil
1
nil <- 请注意这里是 nil
1
即使声明部分未被执行仍将有效。
v = 1 if false # 虽未赋值,但声明有效
p defined?(v) # => "local-variable"
p v # => nil
若使用了 -K 选项的话就可以使用日语标识符,日语标识符被当作局部变量处理。实际上,我们并不推荐您这样做。
实例变量
例:
@foobar
以@开始的变量是实例变量,它属于特定的对象。可以在类或子类的方法中引用实例变量。若引用尚未被初始化的实例变量的话,其值为nil。
类变量
例:
class Foo
@@foo = 1
def bar
puts @@foo
end
end
以@@开始的变量是类变量。在类的定义中定义类变量,可以在类的特殊方法、实例方法等处对类变量进行引用/赋值。
类变量与常数的区别如下。
* 可以重复赋值(常数则会发出警告)
* 不能在类的外部直接引用(在继承类中则可以引用/赋值)
类变量与类的实例变量的区别如下。
* 可在子类中引用/赋值
* 可在实例方法中引用/赋值
可以把类变量看作一种被类、子类以及它们的实例所共享的全局变量。
class Foo
@@foo = 1
end
class Bar < Foo
p @@foo += 1 # => 2
end
class Baz < Bar
p @@foo += 1 # => 3
end
模块中定义的类变量(模块变量)被所有包含该模块的类所共享。
module Foo
@@foo = 1
end
class Bar
include Foo
p @@foo += 1 # => 2
end
class Baz
include Foo
p @@foo += 1 # => 3
end
全局变量
例:
$foobar
$/
以$开始的变量是全局变量,可以在程序的任何地方加以引用(因此需要特别留意)。全局变量无需变量声明。引用尚未初始化的全局变量时,其值为 nil。
伪变量
除普通的变量之外,还有一种叫做伪变量的特殊变量。
self
当前方法的执行主体
nil
NilClass类的唯一实例
true
TrueClass类的唯一实例
false
FalseClass类的唯一实例。nil 和 false 表示“伪”。
__FILE__
当前源文件名
__LINE__
当前源文件中的行号
伪变量的值不可改变,若对伪变量赋值将引发语法错误。
常数
例:
FOOBAR
以大写字母([A-Z])开始的标识符是常数.常数的定义(和初始化)由赋值过程完成.不能在方法中对常数进行定义.若对已定义的常数进行赋值的话,会出现警告信息.若引用未定义的常数的话,则会引发NameError异常.
可以在下列地方引用常数,如,定义常数的类/模块的定义句(也包括方法正文以及嵌套类/模块的定义句)中,继承该类的子类中,以及包含模块的类/模块中等等.在类定义之外(顶层)定义的常数属于Object.
例:
class Foo
FOO = 'FOO' # 定义Foo类的常数FOO(Foo::FOO)
end
class Bar < Foo
BAR = 'BAR' # 定义Bar类的常数BAR(Bar::BAR)
# 可直接引用父类的常数
p FOO # => "FOO"
class Baz
# 虽然嵌套类与该类间不存在继承关系
# 但还是可以直接引用嵌套外部的常数
p BAR # => "BAR"
end
end
另外,在类定义表达式生成类对象的同时,还会将类对象赋值给一个与该类同名的常数.从语法上讲,引用类名也就是引用该常数.
class C
end
p C # => C
若想在外部访问类或模块中的常数时,要使用"::"操作符.若想准确地访问Object类中的常数(顶层的常数)时,也需要也使用"::"操作符,但操作符左边为空.另外,不能使用该操作符对常数进行赋值.
ruby 1.8 特性: 可以使用"::"对常数进行赋值.
例:
module M
I = 35
class C
end
end
p M::I #=> 35
p M::C #=> M::C
p ::M #=> M
M::NewConst = 777 # error--> parse error
引用常数的优先顺序
若在父类和嵌套外侧存在同名常数时,会先引用嵌套外侧的常数.也就是说,引用常数时会先搜索嵌套关系的外侧,然后才会按照继承关系向上搜索.
例:
class Foo
CONST = 'Foo'
end
class Bar
CONST = 'Bar'
class Baz < Foo
p CONST # => "Bar" 外侧的常数
# 此时,若不显式地指定父类中的常数的话,则无法找到该常数
p Foo::CONST # => "Foo"
end
end
一般认为顶层常数定义并不是位于嵌套外侧,所以在搜索了继承关系之后才能找到它.可见顶层常数的优先度很低.
例:
class Foo
CONST = 'Foo'
end
CONST = 'Object'
class Bar < Foo
p CONST # => "Foo"
end
# 若能明显看出是嵌套关系的话,按规则来说
# 首先搜索到的是Object的常数(位于嵌套外侧)
class Object
class Bar < Foo
p CONST # => "Object"
end
end
若对与父类常数同名的常数进行赋值的话,则意味着在该类中定义一个新常数,而并不会对父类常数进行赋值.
例:
class Foo
CONST = 'Foo'
end
class Bar < Foo
p CONST # => "Foo"
CONST = 'Bar' # *定义*Bar的常数CONST
p CONST # => "Bar" (Foo::CONST 被屏蔽了)
p Foo::CONST # => "Foo" (若使用:: 的话就可以了)
end
字面值
* 数值字面值
* 字符串字面值
o 反斜线表示法
o 展开式
* 命令输出
* 集成字符串(here document)
* 正则表达式字面值
* 数组表达式
* 哈希表表达式
* 范围对象
* 符号
* %表示法
我们把形如数字1或字符串“hello world”这样可以直接写入Ruby程序中的值叫做字面值。
数值字面值
123
0d123 ruby 1.7 特性
整数
-123
有符号整数 trap::Numeric
123.45
浮点数 ruby 1.8 特性: 不能把0.1写成".1",而只能完整地写为0.1。
1.2e-3
浮点数
0xffff
16进制整数
0b1011
2进制整数
0377
0o377 ruby 1.7 特性
8进制整数
?a
字符a的代码(97)
ruby 1.7 特性: 用在空白类字符上时,必须写成这样,?\s、?\t等。
?\C-a
Control a 的代码(1)
?\M-a
Meta a 的代码(225)
?\M-\C-a
Meta-Control a 的代码(129)
在?句中,所有的反斜线表示法都是有效的。
您可以在除了字符代码以外的数值字面值中插入“_”。Ruby解释器会忽视“_”而不作处理。当数值位数非常多时,使用“_”会让您快速看清其位数。但是不能在字面值的前面或后面插入“_”(若在字面值前面(包括(+,-)号与字面值间的位置)插入_时,它会被误认成局部变量或方法调用)。
1_000_000_000 => 1000000000
0xffff_ffff => 0xffffffff
字符串字面值
例:
"this is a string expression\n"
'this is a string expression\n'
%q!I said, "You said, 'She said it.'"!
%!I said, "You said, 'She said it.'"!
%Q('This is it.'\n)
字符串被双引号或单引号包围着。在被双引号中括起来的字符串中,反斜线表示法和展开式(后面详述)都是有效的。在被单引号括起来的字符串中,除了\\(反斜线本身)、\'(单引号)和行尾的\(忽略换行)以外,不会对字符串内容作任何处理。
中间留有空白的字符串在编译时会被当作1个字符串字面值来处理。
p "foo" "bar"
=> "foobar"
还有其他一些使用%表示法的字符串项目。
每次对字符串表达式进行计算时都会生成新的字符串对象。
反斜线表示法
\t
Tab(0x09)
\n
换行(0x0a)
\r
回车(0x0d)
\f
换页(0x0c)
\b
BackSpace(0x08)
\a
Bell (0x07)
\e
Escape(0x1b)
\s
空白(0x20)
\nnn
8 进制数表示 (n 为 0-7)
\xnn
16 进制数表示 (n 为 0-9,a-f)
\cx
\C-x
Control字符 (x 为 ASCII 字符)
\M-x
Meta x (c | 0x80)
\M-\C-x
Meta Control x
\x
字符 x 本身
展开式
例:
(假设$ruby = "RUBY")
"my name is #{$ruby}" #=> "my name is RUBY"
'my name is #{$ruby}' #=> "my name is #{$ruby}"
在以双引号(")括起来的字符串表达式、命令字符串以及正则表达式中,可以使用“#{表达式}”把表达式的内容(以字符串的形式)嵌入其中。若表达式是以($,@)开始的变量的话,则可以省略{}部分,只使用“#变量名”的形式即可。如果在字符#后出现的是“{,$,@” 以外的其他字符的话,字符#仍代表"#"本身。若想显式地阻止展开式的话,在#前添加反斜线即可。
展开式中可以包括双引号和其他的Ruby表达式。注释也可以纳入其中。
p "#{ "string" # comment }" # => "string"
ruby 1.7 特性: 在version 1.7中,展开式中的注释内容开始于“#”终止在行尾,而非“}”。因此,上例应该写成
p "#{ "string" # comment
}" # => "string"
这个样子。
命令输出
例:
`date`
%x{ date }
被后引号(`)括起来的字符串与被双引号括起来的字符串是一样的。首先会处理其中的反斜线表示法和展开式,然后才会执行该命令。命令的标准输出是字符串。每次计算命令时都会把它执行一次。若想得到命令的结束status,请参考$?。
还有其他一些使用%表示法的命令输出。
here document - 集成字符串 (多行字符串的字面值)
语法:
<<[-]["'`]标识符["'`]
...
标识符
普通的字符串字面值是指使用分隔符(", ', ` 等)括起来的字符串部分。集成字符串是指,把从包含“<<标识符”的行的下一行开始,到只包含“标识符”的行的行首之间的内容封装起来之后得到的多行字符串(的字面值)。例如
print <
next line
EOS
等同于下例
print " the string\n next line\n"
集成字符串中的开始标签“<<标识符”具有非常重要的语法意义。您可以使用开始标签,将集成字符串的内容传递给某参数,或者把集成字符串本身当作被调(receiver)使用。
# 在表达式中加入开始标签
# 把集成字符串的内容传递给method的第2个参数
method(arg1, <