Ruby的语法和语言特性总结
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Ruby的语法和语⾔特性总结
Ruby是⼀种解释型、⾯向对象、动态类型的语⾔。
Ruby采取的策略是在灵活性和运⾏时安全之间寻找平衡点。
随着Rails框架的出
现,Ruby也在2006年前后⼀鸣惊⼈,同时也指引⼈们重新找回编程乐趣。
尽管从执⾏速度上说,Ruby谈不上有多⾼效,但它却能让程序员的编程效率⼤幅提⾼。
本⽂将讲述Ruby语⾔的基础语⾔特性,包括基本的语法及代码块和类的定义。
1. 基础
在Ruby交互命令⾏中输⼊以下命令(>>为命令⾏提⽰符,=>为返回值;下⽂将把=>符号和语句写在⼀⾏内表明其返回值):
>> 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. ⾯向对象
2.1 代码块
代码块是没有名字的函数(匿名函数),可以⽤作参数传递给函数。
代码块只占⼀⾏时⽤⼤括号包起来,占多⾏是⽤do/end包起来,可以带若⼲个参数。
3.times {puts 'hehe'} # 输出3⾏hehe
['lion', 'tiger', '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命名法
(2)实例变量(⼀个对象有⼀个值)前必须加上@,类变量(⼀个类有⼀个值)前必须加上@@
(3)变量和⽅法名全⼩写⽤下划线命名法,如underscore_style
(4)常量采⽤全⼤写下划线命名法,如ALL_CAPS_STYLE
(5)⽤于逻辑测试的函数和⽅法⼀般要加上问号,如if test?
3. 模块与混⼊(Mixin)
⾯向对象语⾔利⽤继承,将⾏为传播到相似的对象上。
若⼀个对象像继承多种⾏为,⼀种做法是⽤多继承,如C++;Java采⽤接⼝解决这⼀问题,Ruby采⽤模块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, 3, 4, 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
def forward
self
end
end
上⾯的代码通过开放Numeric类,就可以像这样采⽤最简单的语法实现⽤英⼨表⽰距离:puts es.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
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. 总结
Ruby的纯⾯向对象可以让你⽤⼀致的⽅式来处理对象。
鸭⼦类型根据对象可提供的⽅法,⽽不是对象的继承层次,实现了更切合实际的多态设计。
Ruby的模块和开放类,使程序员能把⾏为紧密结合到语法上,⼤⼤超越了类中定义的传统⽅法和实例变量。
核⼼优势:
(1)优雅的语法和强⼤的灵活性
(2)脚本:Ruby是⼀门梦幻般的脚本语⾔,可以出⾊地完成许多任务。
Ruby许多语法糖可以⼤幅提⾼⽣产效率,各种各样的库和gem(Ruby包)可以满⾜绝⼤多数⽇常需要。
(3)Web开发:很多⼈学Ruby最终就是为了⽤Ruby on Rails框架来进⾏Web开发。
作为⼀个极其成功的MVC框架,其有着⼴泛的社区⽀持及优雅的语法。
Twitter最初就是⽤Ruby实现的,借助Ruby⽆⽐强⼤的⽣产⼒,可以快速地开发出⼀个可推向市场的合格产品。
不⾜之处:
(1)性能:这是Ruby的最⼤弱点。
随着时代的发展,Ruby的速度确实是越来越快。
当然,Ruby是创建⽬的为了改善程序员的体验,在对性能要求不⾼的应⽤场景下,性能换来⽣产效率的⼤幅提升⽆疑是值得的。
(2)并发和⾯向对象编程:⾯向对象是建⽴在状态包装⼀系列⾏为的基础上,但通常状态是会改变的。
程序中存在并发时,这种编程策略就会引发严重问题。
(3)类型安全:静态类型可提供⼀整套⼯具,可以更轻松地构造语法树,也因此能实现各种IDE。
对Ruby这种动态类型语⾔来说,实现IDE就困难得多。