Ruby的语法和语言特性总结
Ruby是一种解释型、面向对象、动态类型的语言。Ruby采取的策略是在灵活性和运行时安全之间寻找平衡点。随着Rails框架的出现,Ruby也在2006年前后一鸣惊人,同时也指引人们重新找回编程乐趣。尽管从执行速度上说,Ruby谈不上有多高效,但它却能让程序员的编程效率大幅提高。本文将讲述Ruby语言的基础语言特性,包括基本的语法及代码块和类的定义。 1. 基础 >> puts 'hello,world' hello,world => nil >> language = 'Ruby' => "Ruby" >> puts "hello,#{language}" hello,Ruby => nil 以上代码使用puts输出,给变量赋值,并用#{}的语法实现字符串替换。这表明Ruby是解释执行的;变量无需声明即可直接初始化和赋值;每条Ruby代码都会返回某个值;单引号包含的字符串表示它将直接被解释,双引号包含的字符串会引发字符串替换。 1.1 编程模型 Ruby是一门纯面向对象语言,在Ruby中一切皆为对象,可以用“.”调用对象具有的方法,可以通过class和methods方法查看对象的类型及支持的方法,如4.class => Fixnum,7.methods => ["inspect","%","<<","numerator",...],false.class => FalseClass(方括号表示数组)。 1.2 流程控制 条件判断有正常的块形式,也有简单明了的单行形式;除了常见的if语句外,还有unless语句(等价于if not,但可读性更强)。同理,循环也有正常的块形式和单行形式。注意:除了nil和false之外,其他值都代表true,包括0! # 块形式 if x == 4 puts 'This is 4.' end # 单行形式 puts 'This is false.' unless true x = x + 1 while x < 10 # x的结果为10 x = x - 1 until x == 1 # x的结果为1 和其他C家族的语言差不多,Ruby的逻辑运算符and(&&)、or(||)都自带短路功能,若想执行整个表达式,可以用&或| 1.3 鸭子类型 执行4 + 'four'会出现TypeError的错误,说明Ruby是强类型语言,在发生类型冲突时,将得到一个错误。如果把个语句放在def...end函数定义中,则只有在调用函数时才会报错,说明Ruby在运行时而非编译时进行类型检查,这称为动态类型。Ruby的类型系统有自己的潜在优势,即多个类不必继承自相同的父类就能以“多态”的方式使用: a = ['100',100.0] puts a[0].to_i # => 100 puts a[1].to_i # => 100 这就是所谓的“鸭子类型”(duck typing)。数组的第一个元素是String类型,第二个元素是Float类型,但转换成整数用的都是to_i。鸭子类型并不在乎其内在类型是什么,只要一个对象像鸭子一样走路,像鸭子一样嘎嘎叫,那它就是只鸭子。在面向对象设计思想中,有一个重要原则:对接口编码,不对实现编码。如果利用鸭子类型,实现这一原则只需极少的额外工作,就能轻松完成。 1.4 函数 def tell_the_truth true end 每个函数都会返回结果,如果没有显式指定返回值,函数就将退出函数前最后处理的表达式的值返回。函数也是个对象,可以作为参数传给其他函数。 1.5 数组 和Python一样,Ruby的数组也是用中括号来定义,如animals = ['lion','tiger','bear'];负数下标可以返回倒数的元素,如animals[-1] => "bear";通过指定一个Range对象来获取一个区段的元素,如animals[1..2] => ['tiger','bear']。此外,数组元素可以互不相同,多为数组也不过是数组的数组。数组拥有极其丰富的API,可用其实现队列、链表、栈、集合等等。 1.6 散列表 numbers = {2 => 'two',5 => 'five'} stuff = {:array => [1,2,3],:string => 'Hi,mom!'} # stuff[:string] => "Hi,mom!" 散列表可以带任何类型的键,上述代码的stuff的键较为特殊――它是一个符号(symbol),前面带有冒号标识符。符号在给事物和概念命名时很好用,例如两个同值字符串在物理上不同,但相同的符号却是同一物理对象,可以通过反复调用'i am string'.object_id和:symbol.object_id来观察。另外,当散列表用作函数最后一个参数时,大括号可有可无,如tell_the_truth :profession => :lawyer。 2. 面向对象 代码块是没有名字的函数(匿名函数),可以用作参数传递给函数。代码块只占一行时用大括号包起来,占多行是用do/end包起来,可以带若干个参数。 3.times {puts 'hehe'} # 输出3行hehe ['lion','bear'].each {|animal| puts animal} # 输出列表的内容 上面的times实际上是Fixnum类型的方法,要自己实现这样一个方法非常容易: class Fixnum def my_times i = self while i > 0 i = i - 1 yield end end end 3.my_times {puts 'hehe'} # 输出3行hehe 这段代码打开一个现有的类,向其中添加一个自定义的my_times方法,并用yield调用代码块。在Ruby中,代码块不仅可用于循环,还可用于延迟执行,即代码块中的行为只有等到调用相关的yield时才会执行。代码块充斥于Ruby的各种库,小到文件的每一行,大到在集合上进行各种复杂操作,都是由代码块来完成的。 2.2 类 调用一个对象的class方法可以查看其类型,调用superclass可以查看这个类型的父类。下图展示了数字的继承链,其中横向箭头表示右边是左边实例化的对象,纵向箭头表示下边继承于上边。Ruby的一切事物都有一个共同的祖先Object。 最后通过一个完整的实例――定义一棵树,来看下Ruby的类如何定义和使用,该注意的点都写在注释里面了。 class Tree # 定义实例变量,使用attr或attr_accessor关键字,前者定义变量和访问变量的同名getter方法(即只读),后者定义的变量多了同名setter方法(注意这里使用了符号) attr_accessor :children,:node_name # 构造方法(构造方法必须命名为initialize) def initialize(name,children=[]) @node_name = name @children = children end # 遍历所有节点并执行代码块block,注意参数前加一个&表示将代码块作为闭包传递给函数 def visit_all(&block) visit &block children.each {|c| c.visit_all &block} end # 访问一个节点并执行代码块block def visit(&block) block.call self end end ruby_tree = Tree.new("Ruby",[Tree.new("Reia"),Tree.new("MacRuby")]) # 访问一个节点 ruby_tree.visit {|node| puts node.node_name} # 访问整棵树 ruby_tree.visit_all {|node| puts "Node: #{node.node_name}"} 再提一下Ruby的命名规范: (1)类采用CamelCase命名法 3. 模块与混入(Mixin) # 定义模块ToFile module ToFile # 获取文件名 def filename "object_name.txt" end # 创建文件 def to_f File.open(filename,'w') {|f| f.write(to_s)} # 注意这里to_s在其他地方定义! end end # 定义用户类 class Person include ToFile attr_accessor :name def initialize(name) @name = name end def to_s name end end Person.new('matz').to_f # 创建了一个文件object_name.txt,里面包含内容matz 上面的代码很好理解,只是有一点要注意:to_s在模块中使用,在类中实现,但定义模块的时候,实现它的类甚至还没有定义。这正是鸭子类型的精髓所在。写入文件的能力,和Person这个类没有一点关系(一个类就应该做属于它自己的事情),但实际开发又需要把Person类写入文件这种额外功能,这时候mixin就可以轻松胜任这种要求。 Ruby有两个重要的mixin:枚举(enumerable)和比较(comparable)。若想让类可枚举,必须实现each方法;若想让类可比较,必须实现<=>(太空船)操作符(比较a,b两操作数,返回1、0或-1)。Ruby的字符串可以这样比较:'begin' <=> 'end => -1。数组有很多好用的方法: a = [5,3,4,1] a.sort => [1,5] # 整数已通过Fixnum类实现太空船操作符,因此可比较可排序 a.any? {|i| i > 4} => true a.all? {|i| i > 0} => true a.collect {|i| i * 2} => [10,6,8,2] a.select {|i| i % 2 == 0} => [4] a.member?(2) => false a.inject {|product,i| product * i} => 60 # 第一个参数是代码块上一次执行的结果,若不设初始值,则使用列表第一个值作为初始值 4. 元编程(metaprogramming) 4.1 开放类 可以重定义Ruby中的任何类,并给它们扩充任何你想要的方法,甚至能让Ruby完全瘫痪,比如重定义Class.new方法。对于开发类来说,这种权衡主要考虑了自由,有这种重定义任何类或对象的自由,就能写出即为通俗易懂的代码,但也要明白,自由越大、能力越强,担负的责任也越重。 class Numeric def inches self end def feet self * 12.inches end def miles self * 5280.feet end def back self * -1 end def forward self end end 上面的代码通过开放Numeric类,就可以像这样采用最简单的语法实现用英寸表示距离:puts 10.miles.back,puts 2.feet.forward。 4.2 使用method_missing Ruby找不到某个方法时,会调用一个特殊的回调方法method_missing显示诊断信息。通过覆盖这个特殊方法,可以实现一些非常有趣且强大的功能。下面这个示例展示了如何用简洁的语法来实现罗马数字。 class Roman # 覆盖self.method_missing方法 def self.method_missing name,*args roman = name.to_s roman.gsub!("IV","IIII") roman.gsub!("IX","VIIII") roman.gsub!("XL","XXXX") roman.gsub!("XC","LXXXX") (roman.count("I") + roman.count("V") * 5 + roman.count("X") * 10 + roman.count("L") * 50 + roman.count("C") * 100) end end puts Roman.III # => 3 puts Roman.XII # => 12 我们没有给Roman类定义什么实际的方法,但已经可以Roman类来表示任何罗马数字!其原理就是在没有找到定义方法时,把方法名称和参数传给method_missing执行。首先调用to_s把方法名转为字符串,然后将罗马数字“左减”特殊形式转换为“右加”形式(更容易计数),最后统计各个符号的个数和加权。 当然,如此强有力的工具也有其代价:类调试起来会更加困难,因为Ruby再也不会告诉你找不到某个方法。因此method_missing是一把双刃剑,它确实可以让语法大大简化,但是要以人为地加强程序的健壮性为前提。 4.3 使用模块 Ruby最流行的元编程方式,非模块莫属。下面的代码讲述如何用模块的方式扩展一个可以读取csv文件的类。 module ActsAsCsv # 只要某个模块被另一模块include,就会调用被include模块的included方法 def self.included(base) base.extend ClassMethods end module ClassMethods def acts_as_csv include InstanceMethods end end module InstanceMethods attr_accessor :headers,:csv_contents def initialize read end def read @csv_contents = [] filename = self.class.to_s.downcase + '.txt' file = File.new(filename) @headers = file.gets.chomp.split(',') # String的chomp方法去除字符串末尾的回车换行符 file.each do |row| @csv_contents << row.chomp.split(',') end end end end # end of module ActsAsCsv class RubyCsv # 没有继承,可以自由添加 include ActsAsCsv acts_as_csv end m = RubyCsv.new puts m.headers.inspect puts m.csv_contents.inspect 上述代码中RubyCsv包含了ActsAsCsv,所以ActsAsCsv的included方法中,base就指RubyCsv,ActsAsCsv模块给RubyCsv类添加了唯一一个类方法acts_as_csv,这个方法又打开RubyCsv类,并在类中包含了所有实例方法。如此这般,就写了一个会写程序的程序(通过模块来动态添加类方法)。 一些出色的Ruby框架,如Builder和ActiveRecord,都会为了改善可读性而特别依赖元编程。借助元编程的威力,可以做到尽量缩短正确的Ruby语法与日常用于之间的距离。注意一切都是为了提升代码可读性而服务。 5. 总结 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |