简介:假设您是一名C++开发人员,您需要使用Ruby快速执行一些原型设计。当您拿起一本Ruby参考书籍(比如 Pickaxe)或浏览Ruby网站时,会看到一些熟悉的构造,比如类声明、线程支持和异常处理。正当您认为自己了解Ruby的工作原理之时,您意识到了,您Ruby代码中的并发机制与Boost线程工作原理不一样,catch和throw也与它们看上去的大不相同,而且其他人在其Ruby脚本中各处使用了名为self的关键词。欢迎来到Ruby的世界中!

如果您是一名C++程序员且需要在Ruby环境中工作,那么您会有一些功课要做。本文讨论了Ruby新手可能会误解的六个Ruby特性,特别是当他或她来自一个类似但又不太相同的环境,比如 C++:

  • Ruby类层次结构
  • Ruby中的单例方法
  • self关键词
  • method_missing方法
  • 异常处理
  • 线程

注意:本文中所有的代码均进行测试,且基于Ruby版本1.8.7。

Ruby中的类层次结构

Ruby中的类层次结构会很棘手。创建一个Cat类型的类并开始探讨其层次结构(参见清单1)。

清单1.Ruby中的隐式类层次结构

 
  1. irb(main):092:0> class Cat  
  2. irb(main):093:1> end 
  3. => nil  
  4.  
  5. irb(main):087:0> c = Cat.new 
  6. => #<Cat:0x2bacb68> 
  7. irb(main):088:0> c.class 
  8. => Cat  
  9. irb(main):089:0> c.class.superclass 
  10. => Object  
  11. irb(main):090:0> c.class.superclass.superclass 
  12. => nil  
  13. irb(main):091:0> c.class.superclass.superclass.superclass  
  14. NoMethodError: undefined method `superclass' for nil:NilClass  
  15.         from (irb):91  
  16.         from :0 

Ruby中的所有对象(甚至用户定义的对象)都是Object类的后代,这在清单1中清晰可见。这与C++是鲜明的对比。这一点也不像普通数据类型,例如C/C++ int或double。清单2显示了整数1的类层次结构。

 
  1. irb(main):100:0> 1.class 
  2. => Fixnum 
  3. irb(main):101:0> 1.class.superclass  
  4. => Integer 
  5. irb(main):102:0> 1.class.superclass.superclass  
  6. => Numeric 
  7. irb(main):103:0> 1.class.superclass.superclass.superclass  
  8. => Object 

到目前为止一切顺利。现在您知道了类本身是Class类型的对象。而Class最终派生自Object,如清单3中所示使用Ruby内置的String类。

清单3.类的类层次结构

 
  1. irb(main):100:0> String.class 
  2. => Class 
  3. irb(main):101:0> String.class.superclass  
  4. => Module 
  5. irb(main):102:0> String.class.superclass.superclass  
  6. => Object 

Module是Class的基类,但是使用它时有一点要注意,即您不能直接实例化用户定义的Module对象。如果您不想深入Ruby内部,最好考虑与C++命名空间有类似特征的Module:您可以定义您自己的方法、常量、等等。您在Class中包含了一个Module,以及voilà,Module的所有元素现在会魔法般地成为Class的元素。清单4提供了一个示例。

清单4.Module不能进行直接实例化,并且只能与类一同使用

 
  1. irb(main):020:0> module MyModule  
  2. irb(main):021:1> def hello  
  3. irb(main):022:2> puts "Hello World" 
  4. irb(main):023:2> end 
  5. irb(main):024:1> end 
  6. irb(main):025:0> test = MyModule.new 
  7. NoMethodError: undefined method `newfor MyModule:Module 
  8.         from (irb):25  
  9. irb(main):026:0> class MyClass  
  10. irb(main):027:1> include MyModule  
  11. irb(main):028:1> end 
  12. => MyClass  
  13. irb(main):029:0> test = MyClass.new 
  14. => #<MyClass:0x2c18bc8>  
  15. irb(main):030:0> test.hello  
  16. Hello World   
  17. => nil 

下面再重申一下重点:当您使用Ruby编写c=Cat.new时,c是派生自Object的Cat类型的一个对象。Cat类是Class类型的一个对象,Class派生自Module,而Module又派生自Object。因此该对象及其类型都是有效的Ruby对象。

单例方法和可编辑类

现在,看一下单例方法。假设您想使用C++建模类似于人类社会的东西。那么您会如何做呢?定义一个名为Human的类,然后定义数百万的Human对象?这更像是在建模一个呆板的社会;每个人必须具惟一的特征。Ruby的单例方法在这里就派上了用场,如清单5所示。

清单5.Ruby中的单例方法

 
  1. irb(main):113:0> y = Human.new 
  2. => #<Human:0x319b6f0>  
  3. irb(main):114:0> def y.paint  
  4. irb(main):115:1> puts "Can paint" 
  5. irb(main):116:1> end 
  6. => nil 
  7. irb(main):117:0> y.paint  
  8. Can paint  
  9. => nil 
  10. irb(main):118:0> z = Human.new 
  11. => #<Human:0x3153fc0>  
  12. irb(main):119:0> z.paint  
  13. NoMethodError: undefined method `paint' for #<Human:0x3153fc0>  
  14.         from (irb):119 

Ruby中的单例方法是仅与特定对象关联的方法,不能用于一般的类。它们的前缀是对象名称。在清单5中,paint方法特定于y对象,而且仅限于y对象;z.paint导致一个“方法未定义”错误。您可以调用singleton_methods来查明一个对象中的单例方法列表:

 
  1. irb(main):120:0> y.singleton_methods  
  2. => ["paint"

不过在Ruby中有另一种定义单例方法的方式。看看清单6中的代码。

清单6.创建单例方法的另一种方式

 
  1. irb(main):113:0> y = Human.new 
  2. => #<Human:0x319b6f0>  
  3. irb(main):114:0> class << y  
  4. irb(main):115:1> def sing  
  5. irb(main):116:1> puts "Can sing" 
  6. irb(main):117:1> end 
  7. irb(main):118:1>end 
  8. => nil 
  9. irb(main):117:0> y.sing  
  10. Can sing  
  11. => nil 

清单5还开创了新的可能性,可以添加新方法到用户定义的类和内置的Ruby现有类,比如String。这在C++中是不可能实现的,除非您能够访问您使用的类的源代码。再次观察String类(清单7)。

清单7.Ruby允许您修改一个现有的类

 
  1. irb(main):035:0> y = String.new("racecar")  
  2. => "racecar" 
  3. irb(main):036:0> y.methods.grep(/palindrome/)  
  4. => [ ]  
  5. irb(main):037:0> class String 
  6. irb(main):038:1> def palindrome?  
  7. irb(main):039:2> self == self.reverse  
  8. irb(main):040:2> end 
  9. irb(main):041:1> end 
  10. irb(main):050:0> y.palindrome?  
  11. => true 

清单7清楚地展示了如何编辑一个现有的Ruby类来添加您自行选择的方法。这里,我添加了palindrom方法到String类。因此Ruby类在运行时是可编辑的(一个强大的属性)。

现在您对Ruby的类层次结构和单例有了一定的认识,接下来我们来看self。注意,在定义palindrome方法时我使用了self。

发现self

self关键词的最常见用法可能就是在Ruby类中声明一个静态方法,如清单8所示。

清单8.使用self声明类的静态方法

 
  1. class SelfTest  
  2.    def self.test  
  3.       puts "Hello World with self!" 
  4.    end 
  5. end 
  6.  
  7. class SelfTest2  
  8.    def test  
  9.       puts "This is not a class static method" 
  10.    end 
  11. end 
  12.  
  13. SelfTest.test  
  14. SelfTest2.test 

从清单8的输出中可以看到(如清单9所示),没有对象您无法调用非静态方法。该行为类似于C++。

清单9.在没有对象的情况下调用非静态方法时会出错

 
  1. irb(main):087:0> SelfTest.test  
  2. Hello World with self!  
  3. => nil 
  4. irb(main):088:0> SelfTest2.test  
  5. NoMethodError: undefined method 'test' for SelfTest2:Class 
  6.         from (irb):88 

在探讨self更深奥的用途和含义之前,注意您也可以通过在方法名称前面加上类名来在Ruby中定义一个静态方法:

 
  1. class TestMe  
  2.    def TestMe.test  
  3.        puts "Yet another static member function" 
  4.    end 
  5. end 
  6.  
  7. TestMe.test  # works fine 

清单10提供了self的一个更有趣但不太容易找到的用法。

清单10.使用元类来声明静态方法

 
  1. class MyTest  
  2.    class << self   
  3.      def test  
  4.         puts "This is a class static method" 
  5.      end 
  6.    end 
  7. end 
  8.  
  9. MyTest.test   # works fine  

该段代码以一种稍微不同的方式将test定义为一个类静态方法。要了解究竟发生了什么,您需要看一下class<<self语法的一些细节。class<<self…end创建一个元类。在方法查找链中,在访问对象的基类之前先搜索该对象的元类。如果您在元类中定义一个方法,可以在类上调用该方法。这类似于C++中静态方法的概念。

可以访问一个元类吗?是的:只需从class<<self…end内返回self。注意,在一个Ruby类声明中,您没有义务仅给出方法定义。清单11显示了元类。

清单11.理解元类

 
  1. irb(main):198:0> class MyTest  
  2. irb(main):199:1> end 
  3. => nil 
  4. irb(main):200:0> y = MyTest.new 
  5. => #< MyTest:0x2d43fe0>  
  6. irb(main):201:0> z = class MyTest  
  7. irb(main):202:1> class << self 
  8. irb(main):203:2> self 
  9. irb(main):204:2> end 
  10. irb(main):205:1> end 
  11. => #<Class: MyTest >   
  12. irb(main):206:0> z.class 
  13. => Class 
  14. irb(main):207:0> y.class 
  15. => MyTest 

回到清单7的代码,您会看到palindrome被定义为self==self.reverse。在该上下文中,self与C++没有什么区别。C++和Ruby中的方法都需要一个操作对象,以修改或提取状态信息。self是指这里的这个对象。注意,可以通过附加self前缀来选择性地调用公共方法,指明方法付诸作用的对象,如清单12所示。

清单12.使用self调用方法

 
  1. irb(main):094:0> class SelfTest3  
  2. irb(main):095:1> def foo  
  3. irb(main):096:2> self.bar()  
  4. irb(main):097:2> end 
  5. irb(main):098:1> def bar  
  6. irb(main):099:2> puts "Testing Self" 
  7. irb(main):100:2> end 
  8. irb(main):101:1> end 
  9. => nil 
  10. irb(main):102:0> test = SelfTest3.new 
  11. => #<SelfTest3:0x2d15750>  
  12. irb(main):103:0> test.foo  
  13. Testing Self   
  14. => nil 

在Ruby中您无法通过附加self关键词前缀来调用私有方法。对于一名C++开发人员,这可能会有点混淆。清单13中的代码明确表示,self不能用于私有方法:对私有方法的调用只能针对隐式对象。

#p#

清单13.self不能用于私有方法调用

 
  1. irb(main):110:0> class SelfTest4  
  2. irb(main):111:1> def method1  
  3. irb(main):112:2> self.method2  
  4. irb(main):113:2> end 
  5. irb(main):114:1> def method3  
  6. irb(main):115:2> method2  
  7. irb(main):116:2> end 
  8. irb(main):117:1> private  
  9. irb(main):118:1> def method2  
  10. irb(main):119:2> puts "Inside private method" 
  11. irb(main):120:2> end 
  12. irb(main):121:1> end 
  13. => nil 
  14. irb(main):122:0> y = SelfTest4.new 
  15. => #<SelfTest4:0x2c13d80>  
  16. irb(main):123:0> y.method1  
  17. NoMethodError: private method `method2' called for #<SelfTest4:0x2c13d80>  
  18.         from (irb):112:in `method1'  
  19. irb(main):124:0> y.method3  
  20. Inside private method   
  21. => nil 

由于Ruby中的一切都是对象,当在irb提示符上调用self时您会得到以下结果:

 
  1. irb(main):104:0> self 
  2. => main  
  3. irb(main):105:0> self.class 
  4. => Object 

一启动irb,Ruby解释器就为您创建主对象。这一主对象在Ruby相关的文章中也被称为顶层上下文。

关于self就介绍这么多了。下面我们接着来看动态方法和method_missing方法。

揭秘method_missing

看一下清单14中的Ruby代码。

清单14.运行中的method_missing

 
  1. irb(main):135:0> class Test  
  2. irb(main):136:1> def method_missing(method, *args)  
  3. irb(main):137:2> puts "Method: #{method} Args: (#{args.join(', ')})" 
  4. irb(main):138:2> end 
  5. irb(main):139:1> end 
  6. => nil 
  7. irb(main):140:0> t = Test.new 
  8. => #<Test:0x2c7b850>  
  9. irb(main):141:0> t.f(23)  
  10. Method: f Args: (23)  
  11. => nil 

显然,如果voodoo是您喜欢的,那么清单14会给您这个恩典。这里发生什么了呢?我们创建了一个Test类型的对象,然后调用了t.f,以23作为参数。但是Test没有以f作为方法,您应当会得到一个NoMethodError或类似的错误消息。Ruby在这里做了一件很棒的事情:您的方法调用被阻截并由 method_missing处理。method_missing的第一个参数是缺失的方法名,在本例中是f。第二个(也是最后一个)参数是*args,该参数捕获传递给f的参数。您可以在何处使用像这样的参数呢?在众多选项之中,您可以轻松地将方法调用转发到一个包含的Module或一个组件对象,而不为顶级类中的每个调用显式提供一个包装应用程序编程接口。

在清单15中查看更多voodoo。

清单15.使用send方法将参数传递给一个例程

 
  1. irb(main):142:0> class Test  
  2. irb(main):143:1> def method1(s, y)  
  3. irb(main):144:2> puts "S: #{s} Y: #{y}" 
  4. irb(main):145:2> end 
  5. irb(main):146:1> end 
  6. => nil 
  7. irb(main):147:0>t = Test.new 
  8. irb(main):148:0> t.send(:method1, 23, 12)  
  9. S: 23 Y: 12  
  10. => nil 

在清单15中,class Test有一个名为method1的方法被定义。但是,这里没有直接调用方法,而是发出对send方法的调用。send是Object类的一个公共方法,因此可用于Test(记住,所有类都派生自Object)。send方法的第一个参数是表示方法名称的一个符号和字符串。send方法可以做到哪些您通常无法做到的事情?您可以使用send方法访问一个类的私有方法。当然,对于这是否是一个好特性仍然颇具争议。看一下清单16中的代码。

清单16.访问类私有方法

 
  1. irb(main):258:0> class SendTest  
  2. irb(main):259:1> private  
  3. irb(main):260:1> def hello  
  4. irb(main):261:2> puts "Saying Hello privately" 
  5. irb(main):262:2> end 
  6. irb(main):263:1> end 
  7. => nil 
  8. irb(main):264:0> y = SendTest.new 
  9. => #< SendTest:0x2cc52c0>  
  10. irb(main):265:0> y.hello  
  11. NoMethodError: private method `hello' called for #< SendTest:0x2cc52c0>  
  12.         from (irb):265  
  13. irb(main):266:0> y.send(:hello)  
  14. Saying Hello privately  
  15. => nil 

Throw和catch并非表面那样

如果您像我一样具有C++工作背景,且试图编写异常安全代码,那么在看到Ruby有throw和catch关键词时会开始感到异常亲切。遗憾的是,throw和catch在Ruby中的含义完全不同。

Ruby通常使用begin…rescue块处理异常。清单17提供了一个示例。

清单17.Ruby中的异常处理

 
  1. begin 
  2.   f = File.open("ruby.txt")  
  3.   # .. continue file processing   
  4. rescue ex => Exception 
  5.   # .. handle errors, if any   
  6. ensure 
  7.   f.close unless f.nil?  
  8.   # always execute the code in ensure block   
  9. end 

在清单17中,如果在试图打开文件时出错(可能是缺少文件或文件权限方面的问题),rescue块中的代码会运行。ensure块中的代码始终运行,不管是否有任何异常引发。注意,rescue块后面是否紧跟ensure块是可选的。另外,如果必须显式地抛出一个异常,那么语法是raise<MyException>。如果您选择拥有您自己的异常类,可能会希望从Ruby内置的Exception类派生出相同的类,以利用现有方法。

Ruby中的catch和throw代码块实际上不是异常处理:您可以使用throw修改程序流程。清单18显示了一个使用throw和catch的示例。

清单18.Ruby中的Throw和catch

 
  1. irb(main):185:0> catch :label do 
  2. irb(main):186:1* puts "This will print" 
  3. irb(main):187:1> throw :label 
  4. irb(main):188:1> puts "This will not print" 
  5. irb(main):189:1> end 
  6. This will print  
  7. => nil 

在清单18中,当代码运行到throw语句时,执行会被中断,解释器开始寻找处理相应符号的一个catch块。在catch块结束的地方继续执行。查看清单19中的throw和catch示例:注意,您可以轻松将catch和throw语句用于各个函数。

清单19.Ruby中的异常处理:嵌套的catch块

 
  1. irb(main):190:0> catch :label do 
  2. irb(main):191:1* catch :label1 do 
  3. irb(main):192:2* puts "This will print" 
  4. irb(main):193:2> throw :label 
  5. irb(main):194:2> puts "This won't print" 
  6. irb(main):195:2> end 
  7. irb(main):196:1> puts "Neither will this print" 
  8. irb(main):197:1> end 
  9. This will print  
  10. => nil 

有些人甚至说,Ruby中对catch和throw的支持将C、goto行为带到一个全新的高度。鉴于函数可以有多个嵌套层,而catch块可能在每一级,goto行为类比似乎有据可循。

Ruby中的线程可以是绿色的

Ruby版本1.8.7不支持真正的并发性。确实不支持。但是您会说,在Ruby中有Thread构造函数。您说的没错。不过这个Thread.new不会在您每次调用同一方法时生成一个真实的操作系统线程。Ruby支持的是绿色线程:Ruby解释器使用单一操作系统线程来处理来自多个应用程序级线程的工作负载。

当某个线程等待一些输入/输出发生时,这一“绿色线程”概念很有用,而且您可以轻松调度一个不同的Ruby线程来充分利用CPU。但是这一构造函数无法使用现代的多核CPU(维基百科提供了一段内容,很好地解释了什么是绿色线程。参见)。

最后这一个示例(参见清单20)证明了这一点。

清单20.Ruby中的多个线程

 
  1. #!/usr/bin/env ruby  
  2.    
  3. def func(id, count)  
  4.   i = 0;  
  5.   while (i < count)  
  6.     puts "Thread #{i} Time: #{Time.now}" 
  7.     sleep(1)  
  8.     i = i + 1  
  9.   end 
  10. end 
  11.    
  12. puts "Started at #{Time.now}" 
  13. thread1 = Thread.new{func(1, 100)}  
  14. thread2 = Thread.new{func(2, 100)}  
  15. thread3 = Thread.new{func(3, 100)}  
  16. thread4 = Thread.new{func(4, 100)}  
  17.  
  18. thread1.join  
  19. thread2.join  
  20. thread3.join  
  21. thread4.join  
  22. puts "Ending at #{Time.now}" 

假设您的Linux®或UNIX®机器上拥有top实用程序,在终端运行代码,获取进程ID,然后再运行top–p<process id>。top启动后,按住Shift-H来列出运行中线程的数量。您应当只能看到一个线程,确认了这一点:Ruby1.8.7中的并发性不过是个神话。

总的看来,绿色线程没有什么坏处。它们在重负荷输入/输出密集型程序中仍然有用,更不用说该方法可能是操作系统间最可移植的一个了。

结束语

本文涵盖了以下多个方面:

Ruby 中类层次结构的概念

  • 单例方法
  • 解释 self 关键词和 method_missing 方法
  • 异常
  • 线程

尽管Ruby不乏特立独行之处,但是使用它进行编程还是挺有趣的,而且其以最少的代码完成大量工作的能力还是很强大的。难怪Twitter这样的大型应用程序会使用Ruby来驾驭其真正的潜力。祝您有个快乐的Ruby编程体验!

参考资料

阅读Programming Ruby:(Dave Thomas,Chad Fowler 和 Andy Hunt;第二版),这是一本Ruby必读书籍,也就是广为人知的Pickaxe图书。

查阅另一个宝贵的Ruby资源[Yukihiro "Matz" Matsumoto(Ruby 的创建者)和David Flanagan,O'Reilly,2008年]。

访问这是一个面向希望学习Ruby的C/C++程序员的一个不错站点。

在维基百科上了解更多有关的解释信息。

观看演示如何用,包括面向初学者的产品安装和设置演示,以及为经验丰富的开发人员提供的高级功能。

在专区寻找为Linux开发人员(包括 Linux 新手入门)准备的更多参考资料,查阅我们最受欢迎的文章和教程。