Ruby 快速入门

学编程就好比学游泳,再好的理论也不如一头扎下水,扑腾着呼吸新鲜空气管用。这个系列的目的,就是帮助自己跳入水中,这一次我们来学 Ruby。


更新记录

  • 2016.03.30: 初稿

写在前面

学习一门新语言,很多时候最大的困难是从什么地方开始,之后走怎么样的学习路径。网上关于 Ruby 的资料很多,不同的版本,不同的学习路径,新手很容易迷茫,这也正是 Ruby 之旅系列想要解决的问题。

获得任何新技能的第一步,是先别想着独立解决什么,而是重复一边前人已竟之事,这是最快的方法。每门语言都有自己的一套惯用方法,各有所长,各有所短。通过学习不同的编程语言,你会明白,哪门语言最适宜解决自己当下关注的问题。

精于禅宗的大师会告诉你,拉丁语学得越好,数学也就越好。编程也同样如此。通过研究逻辑式编程或函数式编程,你能领悟到面向对象编程的精华;通过学习汇编语言,你能更透彻地理解函数式编程。

学习新语言的时候,一定要弄清楚以下几个问题:

  • 语言的类型模型是什么?
    • 强类型/弱类型,静态类型/动态类型
    • 类型模型会改变问题的处理方式,控制语言的运行方式
  • 语言的编程范型是什么?
    • 面向对象、函数式、过程式等等
  • 怎样和语言交互?
    • 编译、解释
  • 语言的判断结构(decision construct)和核心数据结构是什么?
  • 哪些核心特性让这门语言与众不同?

作为快速入门课程,接下来先会简单体验一下 Ruby,然后进入理论介绍的部分,最后再通过习题进行巩固。

简介

  • 由松本行弘(Yukihiro Matsumoto)大约在 1993 年发明
  • 脚本语言、解释型、面向对象、动态类型

现在我的机器上安装的 ruby 版本是 2.1,可以使用下面的命令来查询:

$ ruby -v
ruby 2.1.4p265 (2014-10-27 revision 48166) [x86_64-darwin14.0]

具体的安装不做太多介绍,在官方网站查阅不同平台的相关资料即可,下面是一个简单的例子

irb(main):001:0> properties = ['good', 'bad', 'clever', 'stupid']
=> ["good", "bad", "clever", "stupid"]
irb(main):002:0> properties.each {|property| puts "I am #{property}."}
I am good.
I am bad.
I am clever.
I am stupid.
=> ["good", "bad", "clever", "stupid"]

留意一下 ruby 是如何进行遍历及值替换的,之后我们也会深入了解。需要注意的是,这种写起来方便的语言,一般来说效率都不会太高。但是开发效率与执行效率常常是鱼与熊掌,很多时候不得不进行选择。

我们再来看一个长一点的例子:

irb(main):001:0> puts 'hello, wdx'
hello, wdx
=> nil
irb(main):002:0> friend = 'Snake'
=> "Snake"
irb(main):003:0> puts "hello, #{friend}"
hello, Snake
=> nil
irb(main):004:0> friend = 'Queit'
=> "Queit"
irb(main):005:0> puts "hello, #{friend}"
hello, Queit
=> nil

这里我们可以看到以下几点:

  • 不用声明变量
  • 每条 ruby 代码都会返回某个值
  • 单引号表示直接解释
  • 双引号包含的字符串会进行字符串替换

从 Python 到 Ruby

Python 是另一个很好的通用编程语言。从 Python 到 Ruby,你会发现 Ruby 需要学习更多一点的语法(资料来源[9])

相似

Ruby 与 Python 一样的地方

  • 有交互式命令解释器(叫做 irb
  • 可以在命令行阅读文档(使用 ri 命令代替 Python 的 pydoc
  • 没有特殊的行终结符(除了通常的换行符)
  • 与 Python 的三个引号类似,字符串字面量可以跨越多行
  • 方括号用于列表,大括号用于字典(Ruby 中叫做 哈希)
  • 数组操作相同(数组相加,会得到一个长数组;a3 = [a1, a2] 会得到一个二维数组)
  • 对象是强、动态类型
  • 一切皆是对象,变量只是对象的引用
  • 尽管关键字有些许不同,但异常处理方式是一致的
  • 拥有嵌入式文档工具(Ruby 的工具叫 rdoc

相反

Ruby 与 Python 不同的地方

  • 字符串是可变类型
  • 可以新建常量(无意改变的变量)
  • 有一些强制习惯用法(例如:类名以大写字母开头,变量以小写字母开头)
  • 只有一种列表容器,且是可变类型
  • 双引号字符串可以转义(像 \t),有特殊的“替代表达式”语法(不用像 “字符串” + “相” + “加”这样,允许插入 Ruby 表达式结果到其他字符串)
  • 单引号字符串与 Python 的 r”原始字符串” 一样
  • 没有什么“新式”或者“旧式”类定义写法。只有一种写法( Python 3 以上的版本没有这个问题,但不能向下兼容 Python 2)
  • 不能直接访问属性。但在 Ruby 中,一切皆方法调用
  • 方法调用的括号是可选的
  • 有 public、private、protected 三种强制的访问控制类型,不像 Python 里面是用变量名加下划线表示。
  • “混入(mixins)”替代多重继承。
  • 你可以增加或修改内置类的方法。俩语言都允许你随时打开、修改类,但 Python 无法修改内置类,Ruby 无此限制。
  • true 和 false 代替 True 和 False(nil 代替 None)。
  • 判断真值时,只有 false 和 nil 当做假值。其余一切皆为真(包括 0、0.0、 “” 和 [])。
  • elsif 代替了 elif
  • require 替代 import。其他情况,使用相同。
  • 通常,代码之上(而不是之下的字符文档)的注释用来生成文档。
  • 虽然有很多语法糖需要记忆、学习,这也使得 Ruby 非常有趣且有效率。
  • 变量设置后无法取消(类似 Python 的 del 声明)。你可以将其重置为nil 让GC回收旧的内容,但它仍然存在于符号表中。

路线图

我的学习计划比较简单粗暴,打算按照如下顺序把网上资料过一遍:

  1. 笨办法学 Ruby
  2. Programming Ruby: The Pragmatic Programmer’s Guide
  3. Ruby Programming
  4. Ruby User’s Guide

然后开始熟悉相关的库,以及 Ruby on Rails 的各种套路

  • The Well-Grounded Rubyist - 基础
  • Eloquent Ruby - 基础
  • Effective Ruby: 48 Specific Ways to Write Better Ruby - 最佳实践
  • The Ruby Way: Solutions and Techniques in Ruby Programming - 具体应
  • Practical Object-Oriented Design in Ruby: An Agile Primer - OOP实践与技巧
  • Design Patterns in Ruby - 设计模式运用
  • Metaprogramming Ruby - 元编程
  • Ruby Under a Microscope: An Illustrated Guide to Ruby Internals - 内部实现
  • Ruby on Rails Tutorial: Learn Web Development with Rails - ROR基础
  • Crafting Rails 4 Applications: Expert Practices for Everyday Rails Development - ROR高级

接下来我们简单了解一下编程模型,并通过习题进一步巩固。

编程模型

Ruby 是一门纯面向对象语言。从下面的例子就可以看出来:

irb(main):001:0> 233
=> 233
irb(main):002:0> 233.class
=> Fixnum
irb(main):003:0> 233 + 233
=> 466
irb(main):004:0> 233.methods
=> [:to_s, :inspect, :-@, :+, :-, :*, :/, :div, :%, :modulo, :divmod, :fdiv, :**, :abs, :magnitude, :==, :===, :<=>, :>, :>=, :<, :<=, :~, :&, :|, :^, :[], :<<, :>>, :to_f, :size, :bit_length, :zero?, :odd?, :even?, :succ, :integer?, :upto, :downto, :times, :next, :pred, :chr, :ord, :to_i, :to_int, :floor, :ceil, :truncate, :round, :gcd, :lcm, :gcdlcm, :numerator, :denominator, :to_r, :rationalize, :singleton_method_added, :coerce, :i, :+@, :eql?, :remainder, :real?, :nonzero?, :step, :quo, :to_c, :real, :imaginary, :imag, :abs2, :arg, :angle, :phase, :rectangular, :rect, :polar, :conjugate, :conj, :between?, :nil?, :=~, :!~, :hash, :class, :singleton_class, :clone, :dup, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :freeze, :frozen?, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variables, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :send, :public_send, :respond_to?, :extend, :display, :method, :public_method, :singleton_method, :define_singleton_method, :object_id, :to_enum, :enum_for, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__, :__id__]

看出来了吗,ruby 中一切皆为对象,比方说数字就是 Fixnum 类型的对象,我们也可以用 . 来调用对象的各种方法。

判断

我们同样可以通过例子来研究研究。

irb(main):001:0> x = 4
=> 4
irb(main):002:0> x < 5
=> true
irb(main):003:0> x <= 4
=> true
irb(main):004:0> x > 4
=> false
irb(main):005:0> false.class
=> FalseClass
irb(main):006:0> true.class
=> TrueClass
irb(main):007:0> puts 'This is false' unless x == 4
=> nil
irb(main):008:0> puts 'This is true' if x == 4
This is true
=> nil
irb(main):009:0> if x == 4
irb(main):010:1> puts 'This is true'
irb(main):011:1> end
This is true
=> nil
irb(main):012:0> unless x == 4
irb(main):013:1> puts 'This is false'
irb(main):014:1> else
irb(main):015:1* puts 'This is true'
irb(main):016:1> end
This is true
=> nil
irb(main):017:0> puts 'This is true' if not true
=> nil
irb(main):018:0> puts 'This is ture' if !true
=> nil

比较什么的和其他语言大同小异,条件判断的话,主要是 ifunless(当然 !not 都可以用,但是建议用 unless 比较清晰),在 Ruby 的语法中可以直接一行搞定判断一句,这样至少写起来很清晰。

除了 nilfalse 之外,其他值都代表 true,注意!0 也是 true

循环语句可以使用 whileuntil,比较简单,这里直接看例子,需要注意的是,Ruby 中没种对戏那个都有自己的相等的概念,对于数字来说,值相等就是相等。

irb(main):001:0> x = 2
=> 2
irb(main):002:0> x = x + 1 while x < 10
=> nil
irb(main):003:0> x
=> 10
irb(main):004:0> x = x - 1 until x == 1
=> nil
irb(main):005:0> x
=> 1
irb(main):006:0> while x < 10
irb(main):007:1> x = x + 1
irb(main):008:1> puts x
irb(main):009:1> end
2
3
4
5
6
7
8
9
10
=> nil

逻辑运算符和 C++/Java 有少许不同,and(&&) 是逻辑与,or(||) 是逻辑或,都是短路求值的,如果想要整个表达式都执行的话,使用 &| 来比较,具体为

irb(main):001:0> true and false
=> false
irb(main):002:0> true or false
=> true
irb(main):003:0> false && false
=> false
irb(main):004:0> true && this_will_cause_an_error
NameError: undefined local variable or method `this_will_cause_an_error' for main:Object
from (irb):4
from /usr/local/bin/irb:11:in `<main>'
irb(main):005:0> false && this_will_not_cause_an_error
=> false
irb(main):006:0> true or this_will_not_cause_an_error
=> true
irb(main):007:0> true || this_will_not_cause_an_error
=> true
irb(main):008:0> true | this_will_cause_an_error
NameError: undefined local variable or method `this_will_cause_an_error' for main:Object
from (irb):8
from /usr/local/bin/irb:11:in `<main>'
irb(main):009:0> true | false
=> true

基本来说还是一目了然的,这里不再赘述

鸭子类型

Ruby 是强类型语言,直到真正执行代码时,才进行类型检查,称为『动态类型』,但是这也带来了一些好处:多个类不必继承自相同父类,就能以相同方式使用,如:

irb(main):001:0> i = 0
=> 0
irb(main):002:0> a = ['100', 100.0]
=> ["100", 100.0]
irb(main):003:0> while i < 2
irb(main):004:1> puts a[i].to_i
irb(main):005:1> i = i + 1
irb(main):006:1> end
100
100
=> nil

这其实就是面向对象设计思想中的重要原则:对接口编码。举个例子,对象若有 pushpop,那么就可以当做栈来使用。

函数

函数的定义也和简单,并且,函数也是一个对象

def tell_the_truth
true
end

数组

同样先来看实例

irb(main):001:0> animals = ['lions', 'tigers', 'bears']
=> ["lions", "tigers", "bears"]
irb(main):002:0> puts animals
lions
tigers
bears
=> nil
irb(main):003:0> animals[2]
=> "bears"
irb(main):004:0> animals[3]
=> nil
irb(main):005:0> animals[-1]
=> "bears"
irb(main):006:0> animals[0..2]
=> ["lions", "tigers", "bears"]
irb(main):007:0> (0..1).class
=> Range

具体部分比较好明白,唯一需要注意的是 0..1 其实是个对象。另外 []Array 类的方法,所以 [][]= 其实是语法糖。想要用的话,必须先在变量里放一个空数组。数组元素不必具有相同类型。

散列表

其实就是键值对存储,来看例子

irb(main):001:0> numbers = {1 => 'one', 2 => 'two'}
=> {1=>"one", 2=>"two"}
irb(main):002:0> numbers[1]
=> "one"
irb(main):003:0> stuff = {:array => [1, 2, 3], :string => 'Hi, mom!'}
=> {:array=>[1, 2, 3], :string=>"Hi, mom!"}
irb(main):004:0> stuff[:string]
=> "Hi, mom!"

这里尤其需要注意 stuff 这个散列表,我们使用了 symbol 的概念,就是变量名称前面加个冒号。这样做的意义在于,相同的 symbol 会指向相同的物理对象,比如下面的例子,两次 'string' 的 id 值不一样,但是 :string 的值是一样的

irb(main):005:0> 'string'.object_id
=> 70186296639880
irb(main):006:0> 'string'.object_id
=> 70186296271240
irb(main):007:0> :string.object_id
=> 156648
irb(main):008:0> :string.object_id
=> 156648

散列表的一个应用就是模拟命名参数(就是不同的参数有不同的名字,这样可以根据名字来引用,而不是根据预订好的位置类进行引用),例如:

irb(main):001:0> def tell_the_truth(options={})
irb(main):002:1> if options[:profession] == :lawyer
irb(main):003:2> 'oh you are a laywer'
irb(main):004:2> else
irb(main):005:2* 'who you are'
irb(main):006:2> end
irb(main):007:1> end
=> :tell_the_truth
irb(main):008:0> tell_the_truth
=> "who you are"
irb(main):009:0> tell_the_truth :profession => :lawyer
=> "oh you are a laywer"
  • options 表示可选参数,如果不传入,默认就是空散列表
  • 散列表用作最后一个参数的时候可以省略大括号

代码块和 yield

代码块就是匿名函数,可以作为参数传递,比如说

irb(main):001:0> 3.times {puts 'great day!'}
great day!
great day!
great day!
=> 3
irb(main):002:0> animals = ['lions', 'tigers', 'bears', 'rabbits']
=> ["lions", "tigers", "bears", "rabbits"]
irb(main):003:0> animals.each {|a| puts a}
lions
tigers
bears
rabbits
=> ["lions", "tigers", "bears", "rabbits"]
irb(main):004:0> animals.each do |a| \
irb(main):005:1* puts a end
lions
tigers
bears
rabbits
=> ["lions", "tigers", "bears", "rabbits"]

代码块只占一行时用 { / },多行的话则使用 do / end。如果我们要实现自己的 times 函数要怎么做呢?其实也很简单,如下所示:

irb(main):001:0> class Fixnum
irb(main):002:1> def my_times
irb(main):003:2> i = self
irb(main):004:2> while i > 0
irb(main):005:3> i = i - 1
irb(main):006:3> yield
irb(main):007:3> end
irb(main):008:2> end
irb(main):009:1> end
=> :my_times
irb(main):010:0> 3.my_times {puts 'my own times!'}
my own times!
my own times!
my own times!
=> nil

因为代码块可以作为参数,于是类似于函数指针,可以更加灵活进行调用。

面向对象

主要是类和模块,这里设计的知识点比较繁杂,可以自行阅读参考链接中的文档,这里不再赘述

优劣

使用 Ruby 可以用一致的方向处理对象,还有各种不同的语法糖,可以有效提高编程的效率,不过这也导致一定的性能损失。另外,ruby 在并发条件下会产生严重问题(资源竞争)

习题

替换字符串某一部分

字符串相关的操作可以在这里查看(其他的类也可以通过这种方式来学习)

irb(main):001:0> title = "This is DaWang."
=> "This is DaWang."
irb(main):002:0> title[0,3] = "That"
=> "That"
irb(main):003:0> title
=> "Thats is DaWang."
irb(main):004:0> title[0,5] = "That"
=> "That"
irb(main):005:0> title
=> "That is DaWang."

这里需要注意字符串的位置索引,具体可以试验一下。

在字符串 Hello, dawang 中,找到 dawang 所在下标

irb(main):001:0> s = 'Hello, dawang'
=> "Hello, dawang"
irb(main):002:0> i = s.index('dawang')
=> 7

打印名字十次,并输出序号

irb(main):001:0> name = 'dawang'
=> "dawang"
irb(main):002:0> i = 0
=> 0
irb(main):003:0> while i < 10
irb(main):004:1> puts "#{i+1} #{name}"
irb(main):005:1> i = i + 1
irb(main):006:1> end
1 dawang
2 dawang
3 dawang
4 dawang
5 dawang
6 dawang
7 dawang
8 dawang
9 dawang
10 dawang
=> nil

从文件运行 Ruby 程序

类似 python,直接 ruby filename 即可,如:

# dawang at wdxtub.local in ~/Desktop [8:47:56]
$ cat test.rb
name = 'dawang'
i = 0
while i < 10
puts "#{i+1} #{name}"
i = i + 1
end
# dawang at wdxtub.local in ~/Desktop [8:48:36]
$ ruby test.rb
1 dawang
2 dawang
3 dawang
4 dawang
5 dawang
6 dawang
7 dawang
8 dawang
9 dawang
10 dawang

生成一个 0~9 的随机数,让用户猜,并告知结果

具体可以直接看代码

# dawang at wdxtub.local in ~/Desktop [9:07:47]
$ cat test.rb
puts 'Please input a number between 0~9: '
numstr = gets
num = numstr.to_i(base=10)
puts 'Generating random number..'
target = rand(10)
puts "The target number is #{target}"
if num == target
puts 'You made it!'
elsif num < target
puts 'Answer Too Small!'
else
puts 'Answer Too Large!'
end
# dawang at wdxtub.local in ~/Desktop [9:07:53]
$ ruby test.rb
Please input a number between 0~9:
5
Generating random number..
The target number is 6
Answer Too Small!

参考资料

  1. Ruby APi
  2. Programming Ruby: The Pragmatic Programmer’s Guide
  3. Ruby 正则表达式
  4. Ruby 区间(range)
  5. 相关文档
  6. Ruby China
  7. Ruby China Wiki
  8. 笨办法学 Ruby
  9. 从 Python 到 Ruby
  10. Ruby Programming
  11. Ruby User’s Guide
  12. awesome ruby
捧个钱场?