Ruby的语法和语言特性总结_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
  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. 总结
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就困难得多。

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索ruby
, 语法
特性
ruby语法、ruby基本语法、ruby语法糖、ruby erb语法、ruby rdoc 语法,以便于您获取更多的相关知识。

时间: 2024-09-17 05:00:31

Ruby的语法和语言特性总结_ruby专题的相关文章

Ruby中一些基本语法知识点的罗列汇总_ruby专题

 让我们写一个简单的ruby程序.所有Ruby源文件将以扩展名.rb.因此,把下面的源代码在一个test.rb文件. #!/usr/bin/ruby -w puts "Hello, Ruby!"; 在这里,假定您已经安装有Ruby解释器,可以在/usr/bin目录找到.现在尝试运行此程序如下: $ ruby test.rb 这将产生以下结果: Hello, Ruby! 通过以上实例,我们已经看到了一个简单的Ruby程序,现在让我们来看看有关Ruby语法的几个基本概念: Ruby程序中的

Windows下ruby语言安装教程_ruby专题

第一步:下载安装文件 官网下载地址:http://rubyinstaller.org/downloads 第二步: 双击安装 在安装的时候,请勾选Add Ruby executables to your PATH这个选项,添加环境变量,不然以后使用编译软件的时候会提示找不到ruby环境 第三步: 检测安装完成否

用Ruby实现一个单元测试框架的教程_ruby专题

在去年的YOW Melbourne开发者大会上,我参加了一些研习班.这些研习班由@coreyhaines和 @rains负责,因此TDD(测试驱动开发)成为了主要讨论的内容.通常这不是一个问题,但是令人沮丧的是(考虑到这是2010年举办的开发者大会),那时上网还不是很方便,我刚装上linux的笔记本无法下载Rspec.幸运的是几周前,我决定自己写一个单元测试框架(因为我有这个能力:)),接着我就有了一个可用的测试框架,问题解决了.但是,这让我想到一个问题,最少可以用多少代码写成一个可用的单元测试

ruby环境中自动编译sass教程_ruby专题

sass为CSS的赋予了动态语言的特性,如变量.继承.运算.函数等,更方便CSS的编写和维护! sass安装 因为sass依赖于ruby环境,所以装sass之前先确认装了ruby.安装ruby http://www.jb51.net/article/61501.htm 安装完ruby之后,在开始菜单中,找到刚才我们安装的ruby,打开Start Command Prompt with Ruby 然后直接在命令行中输入 复制代码 代码如下: gem install sass 按回车键确认,等待一段

Ruby的运算符和语句优先级介绍_ruby专题

Ruby 是一种表达能力很强的语言,这得意于它异常丰富的运算符和语法糖,虽然 Ruby 一直把最小惊讶原则作为它的哲学之一,但还是常常看到让人惊讶不已,难于理解的代码,这可能是因为对它运算符和语句优先级理解不透导致,今天就和大家聊一聊 Ruby 运算符和语句的优先级. 先看一句简单的代码,猜一猜它的输出是什么. 复制代码 代码如下:   puts {}.class 很多人一定以为结果是 Hash,但实事上结果是空,不信可以在 irb 里试一试. 再看一段代码. 复制代码 代码如下: puts "

进一步深入Ruby中的类与对象概念_ruby专题

Ruby是纯面向对象的语言,所有项目似乎要Ruby中为一个对象.Ruby中的每个值是一个对象,即使是最原始的东西:字符串,数字甚至true和false.即使是一个类本身是一个对象,它是Class类的一个实例.本章将通过所有功能涉及到Ruby的面向对象. 类是用来指定对象的形式,它结合了数据表示和方法操纵这些数据,转换成一个整齐的包.在一个类的数据和方法,被称为类的成员.Ruby类的定义: 定义一个类,定义的数据类型的草图. 这实际上并不定义任何数据,但它定义的类名字的意思什么,即是什么类的对象将

举例理解Ruby on Rails的页面缓存机制_ruby专题

有了页面缓存,Rails 就可以不再介入.在某种程度上,这是件好事,因为您的确可以获得优秀的性能.Rails 只需创建 HTML 页面,将其放入目录,之后,就可以置之于脑后.从那时起,就由应用服务器管理这些页面,且页面进入应用服务器无需任何循环.从性能的角度而言,页面缓存真是天赐之福. 我也钟爱页面缓存,Rails 使之简单利落.只需使用一行代码就可以启用缓存.如果再加入一些代码,就能通过简单地删除文件操作或使用 Rails 较高层的 API 终止缓存.这里存在一个问题.并不是每个网站都能使用页

Ruby对比Python的优势和劣势_ruby专题

Ruby 和 Python 太相似了,取舍大部分都是个人喜好上的原因.比如我就觉得 Python 的 "There is only one way to do it." 比 Ruby 的 "There are many ways to do it." 要好,这不光是考虑团队协作的问题,更重要的是自己能很快明白自己三个月前写的没有任何注释的代码是在干什么.当然也有很多人觉得自由和灵活要比可读性来的重要,所以我说这个是个人喜好的原因. 客观上的 Ruby 比 Pytho

Ruby的面向对象编程的基础教程_ruby专题

Ruby 是纯面向对象的语言,Ruby 中的一切都是以对象的形式出现.Ruby 中的每个值都是一个对象,即使是最原始的东西:字符串.数字,甚至连 true 和 false 都是对象.类本身也是一个对象,是 Class 类的一个实例.本章将向您讲解所有与 Ruby 面向对象相关的主要功能. 类用于指定对象的形式,它结合了数据表示法和方法,把数据整理成一个整齐的包.类中的数据和方法被称为类的成员.Ruby 类定义 当您定义一个类时,您实际是定义了一个数据类型的蓝图.这实际上并没有定义任何的数据,而是