ruby参考手册VIII

6. 类、模块

* 6.1 重定义类时,是否会覆盖原来的定义?
* 6.2 有类变量吗?
* 6.3 什么是类的实例变量?
* 6.4 什么是特殊方法?
* 6.5 什么是类方法?
* 6.6 什么是特殊类?
* 6.7 什么是模块函数?
* 6.8 类和模块有什么区别?
* 6.9 模块可以生成子类吗?
* 6.10 在类定义中定义类方法 和 在顶层中定义类方法 之间有什么不同?
* 6.11 load和require有什么不同?
* 6.12 include和extend有什么不同?
* 6.13 self是什么?
* 6.14 MatchData中的begin、end分别返回什么?
* 6.15 如何使用类名来获得类?

6.1 重定义类时,是否会覆盖原来的定义?

重定义同一个类时,意味着对原有定义进行补充,不会覆盖原来的定义。而重定义方法时,则会覆盖原有定义。
6.2 有类变量吗?

从1.6版本开始出现类变量。类变量名前都带有`@@'。

class Foo
@@F = 0
def foo
@@F += 1
print @@F, "\n"
end
end

在1.4以前的版本中,使用容器类(Array、Hash等)来代替类变量。

class Foo
F = [0]
def foo
F[0] += 1
print F[0], "\n"
end
end

6.3 什么是类的实例变量?

class Foo
@a = 123 # (1)
def foo
p @a # (2) ... 123でなくnilになる。
end
end

(1)是类的实例变量、(2)是通常的实例变量。(2)属于Foo类的实例,而(1)属于类对象Foo(Class的实例)。

不能在实例方法中直接访问类的实例变量。

因此上面的实例变量尚未初始化,其值为nil。
6.4 什么是特殊方法?

特殊方法是指某实例所特有的方法。

通常这么使用。

foo = Foo.new
def foo.hello
print "Hello\n"
end
foo.hello

若想向类中添加方法,却又不想生成子类时,可以使用特殊方法。

Java程序员可能会觉得它很像inner class。
6.5 什么是类方法?

类方法就是指类的特殊方法。上面我们提到,特殊方法是归属于某对象的方法,那么这里所说的“类的特殊方法”将作何解释呢?其实Ruby中有一个“元类”的概念。它是Class类的实例,同时又被所有的类所共有。类方法就定义在这个元类中。

从形式上看,将类名当作被调的方法就是类方法。

下面,我们就在Class类的Foo实例中添加一个特殊方法。

class Foo
def Foo.test
print "this is foo\n"
end
end

像这样来调用它。

Foo.test

您有没有注意到什么?

对了,这就是所谓的类方法。

当然了,Class中所定义的方法也可以用作类方法。
6.6 什么是特殊类?

上面我们提到了特殊方法。

简单说来,在Ruby中可以向对象(实例)中添加方法。

进一步讲,如果您想像操作类那样来操作对象时,该怎么办呢?

可千万别说您没这么想过(^^;

使用特殊类的话,您就可以做到这一点。

class Foo
def hello
print "hello.\n"
end
end

foo = Foo.new
foo.hello

#=> hello.

class << foo
attr :name, true
def hello
print "hello. I'm ", @name, ".\n"
end
end

foo.name = "Tom"
foo.hello

#=> hello. I'm Tom.

多棒呀。

好了,现在要问问题了

问题 如果不使用private_class_method的话,能不能将类方法变成private呢?

解答: 类方法就是类的特殊方法。

所以我们可以耍个小花招,这么处理它。

class Foo
# ...
end

class << Foo
def class_method
print "class method\n"
end
private :class_method
end
Foo.class_method #=> Error

定义特殊方法时,既可以像上面这样在特殊类中加以定义,也可以直接使用def obj.method来完成定义。

另外,您还可以在模块中定义特殊方法(而且是private方法)。
6.7 什么是模块函数?

定义模块函数时,它们既是模块的特殊方法,同时又是private类型。例如,您既可以这样

Math.sqrt(2)

来调用它,又可以这样

include Math
sqrt(2)

使用include来调用它,的确十分方便。

若您想把某方法定义成模块函数时,可以在模块定义中添加如下代码

module_function :method_name

即可完成定义。
6.8 类和模块有什么区别?

模块不能生成实例,而类不能被include。
6.9 模块可以生成子类吗?

在类(模块)中include模块之后,就可实现类似于多重继承的Mix-in功能。它与父类子类之间的这种直接继承关系有所不同,内含模块的类与该模块之间存在is_a?的关系。
6.10 在类定义中定义类方法 和 在顶层中定义类方法 之间有什么不同?

您在前者中可以直接使用常数,而在后者中则必须指定类名才行。
6.11 load和require有什么不同?

load只能加载用Ruby编写的源文件(*.rb)。

require还可以加载*.o文件。另外,一旦require某文件之后,即使再次require也不会进行加载。

加载路径也有所不同。
6.12 include和extend有什么不同?

include负责将module插入到类(模块)中,这样就能以函数的形式来调用方法;而extend负责将module插入到对象(实例)中,这样就添加了特殊方法。
6.13 self是什么?

self是指执行方法的主体本身。在函数形式的方法中,是把self当作被调的。
6.14 MatchData中的begin、end分别返回什么?

它们作用于$~,分别返回$0、$1等原来字符串的开始位置和终止位置。请参考展开标签中的例子。
6.15 如何使用类名来获得类?

如果我有classname = "SomeClass"时,如何生成SomeClass类的实例呢?主要有两个解决方法。

1. eval(classname).new
2. Object.const_get(classname).new

第1种方法既简单又可以处理嵌套类(Net::HTTP等),但如果在CGI环境中滥用它的话,将十分危险。

而第2种方法却又无法处理嵌套类的问题。但如果进行以下处理的话,就可以处理嵌套类了。

# 如果只考虑Ruby 1.8之后的版本的话,可以这样
c = classname.split(/::/).inject(Object) {|c,name| c.const_get(name) }
c.new

# 若想兼顾Ruby 1.6的话,则需要
c = Object
classname.split(/::/).each do |name|
c = c.const_get(name)
end
c.new


7. 内部库

* 7.1 instance_methods(true)返回什么?
* 7.2 为什么rand总是生成相同的随机数?
* 7.3 怎样从0到51中选出5个不重复的随机数呢?
* 7.4 Fixnum、Symbol、true、nil和false这些立即值与引用有什么不同?
* 7.5 nil和false有什么不同?
* 7.6 为什么读入文件并修改之后, 原文件依然没有变化?
* 7.7 怎样覆盖同名文件?
* 7.8 写文件后拷贝该文件,但所得副本并不完整,请问原因何在?
* 7.9 在管道中将字符串传给less后, 为什么看不到结果?
* 7.10 无法引用的File对象将会何去何从?
* 7.11 怎样手动关闭文件?
* 7.12 如何按照更新时间的新旧顺序来排列文件?
* 7.13 如何获取文件中单词的出现频度?
* 7.14 为什么条件表达式中的空字符串表示true呢?
* 7.15 如何按照字典顺序来排列英文字符串数组?
* 7.16 "abcd"[0]会返回什么?
* 7.17 怎么把tab变成space?
* 7.18 如何对反斜线进行转义操作?
* 7.19 sub和sub!的区别在哪里?
* 7.20 \Z匹配什么?
* 7.21 范围对象中的..和...有什么不同?
* 7.22 有函数指针吗?
* 7.23 线程和进程fork有何异同?
* 7.24 如何使用Marshal?
* 7.25 Ruby有异常处理语句吗?
* 7.26 如何使用trap?
* 7.27 如何统计文件的行数?
* 7.28 怎样把数组转化为哈希表?
* 7.29 将字符串变为Array时可以使用%w(...),那么将字符串变为Hash时能不能如法炮制呢?
* 7.30 为何无法捕捉NameError异常呢?
* 7.31 为什么有succ却没有prev呢

7.1 instance_methods(true)返回什么?

klass.instance_methods只返回某klass类(或模块)中所定义的实例方法, 而klass.instance_methods(true)则会返回所有的实例方法, 包括从超类中继承来的实例方法。(但仅限于public方法)

private_instance_methods与protected_instance_methods的参数的意义相同。
7.2 为什么rand总是生成相同的随机数?

在ruby 1.4.2以前的版本中, 每次执行程序时rand都会生成相同的随机数。为了生成不同的随机数, 每次都必须使用srand来给出不同的随机数种子。若不带参数地调用srand的话, 它将使用当时的时间作为种子, 因此可以产生出不同的随机数。
7.3 怎样从0到51中选出5个不重复的随机数呢?

下面的方法可以从0到n中选出m个随机数,并以数组的形式返回结果。

def sample(n, m)
if m.zero?
[]
else
s = sample(n-1, m-1)
t = rand(n+1)
s.concat s.include?(t) ? [n] : [t]
end
end

若不使用递归的话, 可以写成这样。

def sample(n, m)
s = []
((n-m)...n).each do |j|
t = rand(j+2)
s.concat s.include?(t) ? [j+1] : [t]
end
s
end

7.4 Fixnum、Symbol、true、nil和false这些立即值与引用有什么不同?

不能定义特殊方法。

另外, 表示同一个数字的Fixnum的实例通常都是相同的, 所以定义实例变量时, 它们的所指也是相同的。
7.5 nil和false有什么不同?

对nil.methods - false.methods和false.methods - nil.methods执行情况的差异就说明了nil和false所带方法的不同。

若某方法返回一个布尔值时,通常会返回true或false; 除此以外,方法通常返回值或nil。

通常情况下, 带?的方法会返回布尔值, 当然也有例外(在标准配置中只有nonzero?)。
7.6 为什么读入文件并修改之后, 原文件依然没有变化?

open("example", "r+").readlines.each_with_index{|l, i|
l[0,0] = (i+1).to_s + ": "}

上面的代码并不能将行号添加到example中。这是因为该代码并没有修改原文件, 而只是修改了readlines读入的字符串而已。必须将修改后的内容写入文件才行。

io = open("example", "r+")
ary = io.readlines
ary.each_with_index{|l, i| l[0,0] = (i+1).to_s + ": "}
io.rewind
io.print ary
io.close

此时因为文件增大了, 所以没有出现什么问题. 如果文件变小的话,就必须将下列代码

io.flush
io.truncate(io.pos)

嵌入到io.close之前才行.
7.7 怎样覆盖同名文件?

使用命令行选项-i或者将内部变量$-i设为""之后, 就可以覆盖同名文件了。

您可以使用下列代码。

$ ruby -i -ne 'print "#$.: #$_"' example

若想保留原来的文件时, 可以-i.bak这样。
7.8 写文件后拷贝该文件,但所得副本并不完整,请问原因何在?

open('file', 'w').print "This is a file.\n"
system 'cp file copy'

上述代码之所以出错,是因为拷贝文件时有一部分内容尚未被flush进file中所致。因此,请先close然后再进行拷贝。

f = open('file', 'w')
f.print "This is a file.\n"
f.close
system "cp file copy"

7.9 在管道中将字符串传给less后, 为什么看不到结果?

f = open '|less', 'w'
f.print "abc\n"

上述代码会立刻结束,您无法看到less的显示结果。添加一个close,以便等待less完成显示。

f = open '|less', 'w'
f.print "abc\n"
f.close

第一行也可以写成f = IO.popen 'less', 'w'。
7.10 无法引用的File对象将会何去何从?

open("file").read中的File是无法引用的, 它会被后来的垃圾回收器所close和销毁.
7.11 怎样手动关闭文件?

GC会自动关闭不再使用的File对象. 若您想自己关闭文件的话, 可以从下面4种方法中任选其一(按照代码长度排列)。

1.

File.foreach('filename') {|line| print line }

2.

File.readlines('filename').each {|line| print line }

3.

File.open('filename') {|f|
f.each {|line| print line }
}

4.

begin
f = File.open('filename')
f.each {|line| print line }
ensure
f.close if f
end

7.12 如何按照更新时间的新旧顺序来排列文件?

Dir.glob("*").collect{|f| [File.mtime(f), f] }.
sort{|a,b| b[0]<=>a[0] }.collect{|e| e[1] }

上述代码将会按照更新时间的新旧顺序来排列当前目录中(除了"."、".."以外)的文件, 并以数组的形式返回结果. 若想按照相反顺序来排序的话, 就可以去掉sort后面的块。

Dir.glob("*").sort{|a,b| File.mtime(b)<=>File.mtime(a)}

尽管这样可以完成排序, 但是每次都要访问文件来获取更新时间, 因而排序过程较慢。

为解决这个问题, 可以先生成一个对象, 它负责将每个文件的更新时间存入高速缓存, 这样就可以提高处理速度。

cache = {}
def cache.mtime(x)
self[x] ||= File.mtime(x)
end
Dir.glob("*").sort{|a,b| cache.mtime(b) <=> cache.mtime(a)}
cache = nil

另外,在ruby 1.7中提供了Enumerable#sort_by方法, 使用它可以轻松完成上述任务。

Dir.glob("*").sort_by {|f| File.mtime(f)}.reverse

7.13 如何获取文件中单词的出现频度?

将哈希表的默认值设为0之后, 可以这样处理。

freq = Hash.new(0)
open("file").read.scan(/\w+/){|w| freq[w] += 1}
freq.keys.sort.each {|k| print k, "--", freq[k], "\n"}

7.14 为什么条件表达式中的空字符串表示true呢?

在Ruby中,只有nil和false表示假, 其他的都表示真。若想判断某字符串是否为空时, 可以将它与""作比较,或者使用empty?, 或者将其length与0作比较 即可得出结果。
7.15 如何按照字典顺序来排列英文字符串数组?

您可以这样排序

ary.collect{|f| [f.downcase, f]}.sort.collect{|e| e[1]}

若downcase形式相等时, 则按照原来字符串来排序。
7.16 "abcd"[0]会返回什么?

它将返回字符a的代码97(Fixnum)。若想验证它与a是否一致时,可以使用?a进行比较。
7.17 怎么把tab变成space?

常用方法如下。

# 非破坏性的方法
def expand_tab( str )
str.gsub(/([^\t]{8})|([^\t]*)\t/n) { [$+].pack("A8") }
end

# 破坏性的方法
def expand_tab!( str )
1 while str.sub!(/(^[^\t]*)\t(\t*)/) { $1 + ' ' * (8-$1.size%8+8*$2.size) }
end

# 破坏性的方法 (2)
def expand_tab!( str )
1 while str.sub!(/\t(\t*)/) {' ' * (8-$~.begin(0)%8+8*$1.size) }
end

7.18 如何对反斜线进行转义操作?

在正则表达式中, Regexp.quote('\\')就可以实现转义。

若使用gsub的话, 在gsub(/\\/, '\\\\')中,进行句法分析时会将替换字符串变为'\\', 而实际替换还会将其解释成'\', 所以必须写成gsub(/\\/, '\\\\\\')才行。若使用\&来表示匹配字符串的话, 则可以写成gsub(/\\/,'\&\&')。

另外, 若使用形如gsub(/\\/){'\\\\'}的块的话, 就只会进行一次转义, 因此可以得到预期的结果。
7.19 sub和sub!的区别在哪里?

sub不会改变被调的状态。首先,它会拷贝字符串, 然后在副本中进行替换操作,并返回结果(若没有必要替换时,就直接返回副本)。

而sub!则会改变被调本身。若没有进行替换的必要时, 将返回nil。

像sub!这种会修改被调本身的方法叫做破坏性的方法。在Ruby中, 若同时存在同名的破坏性方法和非破坏性方法时, 通常在破坏性方法名后添加!。

def foo(str)
str = str.sub(/foo/, "baz")
end

obj = "foo"
foo(obj)
print obj
#=> "foo"

def foo(str)
str = str.sub!(/foo/, "baz")
end

foo(obj)
print obj
#=> "baz"

像sub!这样的破坏性的方法可能会带来非预期的效果, 请慎用。
7.20 \Z匹配什么?

当字符串末尾字符不是\n时,\Z匹配于字符串的末尾字符;当字符串的末尾字符是\n时,它匹配于换行之前的部分。

若想让它一直匹配于末尾字符,而不理会\n的问题时,应该使用\z。
7.21 范围对象中的..和...有什么不同?

..包含终点,而...不包含终点。
7.22 有函数指针吗?

若使用Proc.new、proc、lambda生成了Proc对象的话,就可以将其用作函数指针。

另外,Method和UnboundMethod对象也很类似于函数指针。
7.23 线程和进程fork有何异同?

线程和进程fork分别具有以下特点:

* 进程间不能共享内存
* 线程和进程的切换都是不定时的
* Ruby线程可运行于任何OS
* 若Ruby线程发生阻塞的话,整个都会停顿下来(non preemptive)
* Ruby线程不会引发系统颠簸(thrashing)(*)

*系统颠簸是指,当进程或线程过多时,因处理它们的切换问题而消耗了过多的系统时间,所导致的系统无效。

通常不应该混用进程fork和线程。

因为Ruby线程采用分时机制,所以使用进程后并不会加快处理速度。由于Ruby线程是用户线程,因此无法享受多处理机的好处。
7.24 如何使用Marshal?

它可以将对象转变为字节串(即serialize)。您可以将对象存入文件,以后再将其还原,或者通过网络把它发送出去。例如,您可以这样把obj对象转为字节串

Marshal.dump(obj)

该方法返回字符串,因此可以将其存入文件。

File.open('filename', 'w') {|f|
f.write Marshal.dump(obj)
}

因为经常需要进行上述操作,所以Ruby提供了下列简便方法。

Marshal.dump(obj, io)

io表示可写入的IO对象。另外使用上述方法处理较大的对象时,就省去了生成巨大字符串的麻烦。

另一方面,您可以像下面这样来还原对象。首先是使用字符串进行还原的例子。

obj = Marshal.load(str)

下面的是直接使用IO对象进行还原。

obj = Marshal.load(io)

7.25 Ruby有异常处理语句吗?

有。通常使用下列语句。

begin
(发生异常之前所处理的事务)
rescue (异常类)
(发生异常时的处理)
else
(没有异常时的处理)
ensure
(必须处理的事务)
end

在begin语句中,若发生异常就会执行rescue部分。若没有异常就执行else部分。另外,不管有没有异常,都必须执行ensure部分。rescue,else和ensure部分都是可以省略的。若rescue后面没有给出异常类,就使用StandardError作为其默认值。此时将捕捉StandardError的子类。

该语句的值取决于执行ensure部分之前的值。

另外,您可以使用全局变量$!来获得最后发生的异常,使用$!.class来了解该异常的种类。
7.26 如何使用trap?

下面的代码在捕捉到SIGPIPE信号时会执行块的内容(随即引发异常)。

trap("PIPE") {raise "SIGPIPE"}

7.27 如何统计文件的行数?

若假定文件末尾也有个换行符的话,下面的方法可能是最简单的。

open("filename").read.count("\n")

7.28 怎样把数组转化为哈希表?

您可以这样把数组array转化为哈希表

h = Hash[*array]

此时array中的奇数元素将成为哈希表h的键,而偶数元素将成为相对应的值。数组array中的元素个数必须是偶数。

array = [1,2,3,4]
h = Hash[*array]
p h

=> {1=>2, 3=>4}

数组array前面的"*"表示展开参数。
7.29 将字符串变为Array时可以使用%w(...),那么将字符串变为Hash时能不能如法炮制呢?

您不能像生成Array那样来直接生成Hash,但您可以这样

p h = Hash[*%w(1 foo 2 bar)]

=> {"1"=>"foo", "2"=>"bar"}

先生成Array,然后将其转化为Hash。(但此时哈希表的键和值都被限定为字符串)
7.30 为何无法捕捉NameError异常呢?

在1.4以前的版本中,下列代码中的rescue部分都可以正常运行,可是到了1.6版本就不行了。

begin
foo
rescue
puts "TRAP : #$!"
end

-:2: undefined local variable or method `foo' for #<Object:0x401bece0> (NameError)
ruby 1.6.7 (2002-03-20) [i586-linux]

这是因为NameError异常类不再是StandardError的子类所致(若不指定异常类时,rescue将只负责捕捉StandardError旗下的异常类)。此时,必须显式地指定NameError才行。

begin
foo
rescue NameError
puts "**TRAP** : #$!"
end

ruby 1.6.7 (2002-03-20) [i586-linux]
**TRAP** : undefined local variable or method `foo' for #<Object:0x401bece0>

可是到了1.7版本时,NameError类又重新成为StandardError的子类。请您参考异常类中的类层次构造来了解默认情况下所能捕捉的异常的范围。
7.31 为什么有succ却没有prev呢?

虽然定义Integer#prev并不困难,但却没有succ实用。

另一方面,定义String#prev则比较困难。例如

'09'.succ == '9'.succ #=> true

因为有时succ前和后的字符串并不是一一对应的,所以str.prev.succ == str.succ.prev未必成立。


8. 扩展库

* 8.1 如何使用交互式Ruby?
* 8.2 有调试器吗?
* 8.3 怎样在Ruby中使用以C写成的库?
* 8.4 有Tcl/Tk的接口吗?
* 8.5 为什么我的Tk不管用?
* 8.6 有gtk+、xforms的接口吗?
* 8.7 进行日期计算时需要注意哪些问题?

8.1 如何使用交互式Ruby?

您可以像下面这样来使用irb脚本

$ irb
irb(main):001:0> 1 + 2 * 3
7
irb(main):002:0> if true
irb(main):003:1> :true
irb(main):004:1> else
irb(main):005:1* :false
irb(main):006:1> end
:true
irb(main):007:0>

它就是交互式的Ruby。

详细用法请参考irb.rb。

若您使用的是二进制包形式的ruby的话,irb可能在其他包里。请您查阅各发布版本的信息。
8.2 有调试器吗?

有的。请您这样启动Ruby。详情请参考debug.rb。

ruby -r debug your_script

8.3 怎样在Ruby中使用以C写成的库?

Ruby就是用C写成的,而且Ruby提供了完备的工具,使您可以使用以C写成的库。只要您编写出C库和Ruby间的接口就行。

首先请阅读Ruby的解包目录中的[ruby-src:README.EXT.ja]。该文档可以解答您所提出的有关Ruby的问题。

其次,您可以在ext/的子目录中找到实例。最后,您还可以参考ftp网站上contrib中的资料。
8.4 有Tcl/Tk的接口吗?

在标准配置的扩展库中,提供了Tcl/Tk的接口。一个是在ext/tcltklib/lib中。使用require "tcltk"语句进行加载之后,就可以进行编程来把Tcl脚本置入Ruby脚本之中。

另一个是在ext/tk/中。使用require "tk"进行加载之后,就可以使用类似Ruby的方式来实现Tk的GUI。
8.5 为什么我的Tk不管用?

大半是因为您使用的Tk的版本太老了。请将其替换为较新的版本即可。
8.6 有gtk+、xforms的接口吗?

请参考[RAA:Ruby/GTK], [RAA:Ruby/Forms]。
8.7 进行日期计算时需要注意哪些问题?

Time对象的有效表示范围是从1970年1月1日起到2038年1月19日止。请使用标准附加库date.rb。另外,请参考[ruby-src:sample/cal.rb]。


10. 日语字符的处理

* 10.1 若包含汉字的脚本输出乱码或无法正常运行时,该如何处理?
* 10.2 选项-K和$KCODE有什么不同?
* 10.3 可以使用日语标识符吗?
* 10.4 如何从包含日语字符的字符串中依次抽出1个字符?
* 10.5 tr("あ","a")运作不正常,应如何处置?
* 10.6 如何对平假名进行排序?
* 10.7 如何用空白来替代SJIS中从84BF到889F之间的系统相关代码?
* 10.8 如何进行全角-半角字符的变换?
* 10.9 关于半角假名的问题
* 10.10 怎样从包含日语字符的字符串中抽出n字节的内容?
* 10.11 怎么让日语文本在第n个字处换行?

10.1 若包含汉字的脚本输出乱码或无法正常运行时,该如何处理?

在ruby 1.6以后的版本中,默认情况下不会对汉字代码进行特殊的解释。若想处理汉字时,必须使用ruby -Ke等来设置$KCODE。

若想在Windows上使用SJIS时,应该设为ruby -Ks;若想在UNIX系列OS上使用EUC时,应该设为ruby -Ke。

另外,若在脚本首行中添加如下代码时

#! ruby -Ks

就可以将设定的选项置入脚本之中。恐怕这就是最常用的解决方法。
10.2 选项-K和$KCODE有什么不同?

产生效果的时机不同。

例如,在SJIS编码文件中出现下列代码的话

$KCODE = 'SJIS'
s = "表"

在设定$KCODE的值时,脚本的解析过程已经结束。(因为$KCODE的默认值是"NONE")所以字符串没有被看作是多字节内容。

如果使用选项-K来指定汉字编码的话,则在读入脚本之前已经生效,所以解析脚本时汉字代码会被正确识别。

另外,若包含汉字代码的脚本出现问题时,多半是因为JIS编码的汉字代码中包含与反斜杠("\")相同的代码所致。
10.3 可以使用日语标识符吗?

只要正确设置了-K选项,就可以使用日语标识符。以日语汉字开头的变量名相当于以小写字母开头的变量名。但它的可移植性较低,我们不推荐您这样做。

在Hash中,也可以使用日语标识符,而且比较安全。

var = {'变量' => '值'}
var['变量'] = 1

10.4 如何从包含日语字符的字符串中依次抽出1个字符?

设定$KCODE之后,使用split(//)或scan(/./)即可。
10.5 tr("あ","a")运作不正常,应如何处置?

内部的String#tr在进行变换时,是以字节为单位的。在添加了require "jcode"之后,将以日语字符为单位进行处理。 (请参考jcode.rb)
10.6 如何对平假名进行排序?

下例是对平假名进行排序的例子(忽略了浊音、半浊音、拗音和拨音)。

require "jcode"

a = "ぁぃぅぇぉがぎぐげござじずぜぞだぢづでど" \
"ばびぶべぼぱぴぷぺぽゃゅょっゎ"
b = "あいうえおかきくけこさしすせそたちつてと" \
"はひふへほはひふへほやゆよつわ"

ary = %w(ふー ばー ばず)

p ary.sort
p ary.collect{|l| [l.tr(a,b), l]}.sort.collect!{|e| e[1]}

# => ["ばー", "ばず", "ふー"]
# ["ばー", "ばず", "ふー"]

另外,在1.7以后的版本中可以使用Enumerable#sort_by来这样改写最后一行

p ary.sort_by {|l| l.tr(a,b)}

即可。
10.7 如何用空白来替代SJIS中从84BF到889F之间的系统相关代码?

尽管您可以使用正则表达式中表示范围的[あ-ん],但直接写出系统相关字符会影响程序的可读性。但也不能写成

gsub(/[\x84\xbf-\x88\x9f]/s, ' ')

这样。此时应该使用如下的小技巧

gsub(Regexp.compile("[\x84\xbf-\x88\x9f]", nil, 's'), ' ')

gsub(/#{"[\x84\xbf-\x88\x9f]"}/s, ' ')

用数值来表示2字节的代码,并将其替换为空白字符。(其实并非替换成空白字符,而是("〓"))
10.8 如何进行全角-半角字符的变换?

标准方法是使用nkf.so库或jcode.rb库进行变换。另外,还可以使用[RAA:Kakasi]库进行变换。

请参考[ruby-list:10505], [ruby-list:25839], [ruby-list:31238], [ruby-list:31240], [ruby-list:31508]等等
10.9 关于半角假名的问题

Ruby不支持半角假名。

# 在下例中,请将"ア"看作半角假名
ruby -Ks -e 'p "あア"'
=> "あ\261"

据说现在开发中的M17N版ruby就不会出现这种问题。
10.10 怎样从包含日语字符的字符串中抽出n字节的内容?

抽出多字节字符时,可能会将字符一分为二。此时,若正确设定了$KCODE的话,/./就不会匹配拆散的字符了。应对其加以充分利用

$KCODE = "e"
p /./ =~ "あ"[0,1] # => nil

# 注: 如果它并不是汉字的构成要素的话,就会进行匹配。

p /./ =~ "\xff" # => 0

下例中定义了一个jleft方法,它会从字符串左侧起抽出至多len字节的内容。

class String
def jleft(len)
return "" if len <= 0

str = self[0,len]
if /.\z/ !~ str
str[-1,1] = ''
end
str
end
end

$KCODE = 'e'
s = "あいうえお"
for i in -2 .. s.size+2
p [i, s.jleft(i)]
end

=> [-2, ""]
[-1, ""]
[0, ""]
[1, ""]
[2, "あ"]
[3, "あ"]
[4, "あい"]
[5, "あい"]
[6, "あいう"]
[7, "あいう"]
[8, "あいうえ"]
[9, "あいうえ"]
[10, "あいうえお"]
[11, "あいうえお"]
[12, "あいうえお"]

若限定使用EUC编码时,可以使用下面的方法。

class String
def jleft(len)
return "" if len <= 0

str = self[0, len]

if str.count("\xa1-\xfe") % 2 == 1
str[-1, 1] = ''
end
str
end
end

注:上述方法都不支持3字节字符。
10.11 怎么让日语文本在第n个字处换行?

可以使用NKF的-f选项。

require 'nkf'
p NKF.nkf("-ef11", "あいうえお、かきくけこ")

但是NKF会自动进行断字处理和空格调整,使我们无法进行精确控制。

若想自己来实现的话,可以使用10.10的方法。

class String
def jfold(len)
return "" if len <= 0

right = self.delete("\r\n")
while right and not right.empty?
left, right = right.unpack("a#{len} a*")

if /.\z/ !~ left
right[0,0] = left[-1,1]
left[-1,1] = ''
end
yield left
end
end
end

"あいうえお、かきくけこ".jfold(11) {|s|
puts s
}
# => あいうえお
、かきくけ


实际上,它是每过n字节就进行一次folding,但并未考虑到TAB的位置问题。与nkf比较起来,它的速度非常慢。

11 Ruby的处理系统

* 11.1 能不能编译Ruby脚本呢?
* 11.2 有没有Java VM版的Ruby?
* 11.3 除了original Ruby之外,就没有其他版本吗?
* 11.4 有没有Ruby用的indent?
* 11.5 有没有使用本地线程的Ruby?
* 11.6 GC实在是太慢了,怎么办才好?
* 11.7 有没有Mac版的Ruby?

11.1 能不能编译Ruby脚本呢?

RAA中提供了面向x86的Just In Time编译器,但它只是个试验性的产品,对应于1.5版本,且不具实用性。
11.2 有没有Java VM版的Ruby?

JRuby( <URL:http://jruby.sf.net/> )尚处于研发阶段。
11.3 除了original Ruby之外,就没有其他版本吗?

目前还没有。
11.4 有没有Ruby用的indent?

没有。Ruby脚本的解析是非常麻烦的。但随着Ruby用户数量的激增,将来有望解决这个问题。
11.5 有没有使用本地线程的Ruby?

本地线程(native thread)与现在的GC(垃圾回收...自动内存回收)格格不入,所以没有将其纳入Ruby。同时,在解释器和基本库中有一些线程非安全的部分,必须先对它们作出修改才行。
11.6 GC实在是太慢了,怎么办才好?

现在,GC已经成为影响Ruby解释器性能的最大的瓶颈,特别是在一些大型程序中,这种影响显得越发明显。它已成为开发下一个稳定版时必须解决的首要问题。那时可能会使用新的GC算法。但在此之前,您可以:1.默默忍耐;2.减少生成对象的数量;3.若必须生成大量的对象时,暂时关闭GC...等等。

但在普通的程序中,GC并非如此可怕。您不要动不动就怀疑GC不好,而应该首先检查是不是别的地方影响了速度。
11.7 有没有Mac版的Ruby?

若您的系统是MacOS X的话,您可以在RAA中找到.pkg格式的Ruby。以前的版本可能会支持MacOS 9平台,但现在的版本则不行了。若没有人愿意为支持老系统而进行研发的话,那就不好办了...
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值