Ruby基础笔记

Table of Contents

1 说明

为自己整理的一份Ruby基础知识笔记,便于平时使用,目前看起来还是杂乱无章的。

参考资料:

  • 《Ruby基础教程》,作者高桥征义,后藤裕藏。
  • 《Ruby元编程》,作者Paolo Perrotta。
  • 以及Ruby官方、Stack Overflow、Ruby China等各大技术社区的讨论内容。

2 准备工具

3 基础

3.1 注释

  • 单行注释符:#
  • 多行注释:
=begin
注释内容
=end

3.2 打印输出

puts "hello world"
print "hello world"

print和puts区别:

print/printf默认不会打印换行符,puts默认会打印换行符。

p方法:

用于区分输出对象,字符串对象会自动加上双引号来区分:

> p "hello world"
"hello world"
=> "hello world"

3.3 常量变量

3.3.1 变量

变量的分类:

  • 局部变量,以英文字母或_开头;
  • 全局变量,以$开头;
  • 实例变量,以@开头;
  • 类变量,以@@开头;
  • 伪变量,nil、true、false、self。
3.3.1.1 赋值
a = 1

a, b, c = 1, 2, 3

a, b, c = 1, 2 # c会被忽略,赋值为nil

# 加“*”表示把未分配的值赋值给变量:

a, b, *c = 1, 2, 3, 4, 5, 6 # => a=1, b=2, c=[3, 4, 5, 6]

a, *b, c = 1, 2, 3, 4, 5 # => a=1, b=[2, 3, 4], c=5

# 数组解构

a, b = [1, 2]
a, = [100, 200] # a=10
a, b, c = [1, [2, 3], 4] # a=1, b=[2, 3], c=4
3.3.1.2 引用

在字符串中引用变量的值:

> msg = "hello, world"
=> "hello, world"

> "#{msg}"
=> "hello, world"

如果要把“#{msg}”表示成字符串,需要加单引号:

'#{msg}'
=> "\#{msg}"
3.3.1.3 特殊变量

__FILE__:本身的文件名。

$0:当前运行的文件名:

if __FILE__ == $0
  main
end

等价于Python的:

if __name__ == '__main__':
    main()

3.3.2 常量

Ruby规定全部大写字母的为常量,且定义后不能被修改:

> A = 1
=> 1
> A = 2
(pry):2: warning: already initialized constant A
(pry):1: warning: previous definition of A was here
=> 2

常量的引用路径:

# coding: utf-8

Y = 'root'

# 根据“路径”来引用常量
module M
    Y = 'self'
    puts Y
    puts ::Y
end

puts M::Y

3.4 字符串

双引号或单引号之间的内容是字符串:

"hello world"
'hello world'

两者区别在于单引号的内容不会被转义:

> print 'hi\n'
=> hi\n

3.4.1 常用操作

to_i,转成数字:

> "1".to_i
=> 1

判断空字符串:

> ''.empty?
=> true
> 'a'.empty?
=> false

%Q,添加转移字符:

> %Q{your's book}
=> "your's book"

Here Document(来源UNIX的shell写法):

> <<EOF
 | hahah
 | oo
 |  fawef
 | EOF
=> "hahah\noo\n fawef\n"

编码转换:

encode和encode!方法,后者提供破坏性操作。

格式化:

> "%d" % 1
=> "1"
# 控制输出的固定位数
> "%02d" % 1
=> "01"

3.5 数组

3.5.1 创建数组

> a = [1, 2, 3]
=> [1, 2, 3]
> a = [1, 2, 3,] # 多一个逗号也可以

3.5.2 索引

> a[0]
=> 1
> a[10] # 越界访问返回nil,不像Python报出IndexError异常
=> nil

数组还可以按范围索引:

> a = (1..10).to_a # 创建1~10的连续数组
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
> a[1..3] # 取下标1~3的内容
=> [2, 3, 4]
> a[0..3] # 取下标0~3的内容
=> [1, 2, 3, 4]

3.5.3 修改元素

> a = [1, 2, 3]
=> [1, 2, 3]
> a[0] = 100 # 修改下标为0的内容
=> 100
> a
=> [100, 2, 3]
> a << 4 # 追加元素,注意这是有副作用的,会修改原始的数组
=> [100, 2, 3, 4]

3.5.4 遍历数组

调用each方法,each方法接收一个block对象:

> a.each do |i|
*   print i
* end
123111=> [1, 2, 3, nil, nil, nil, nil, nil, nil, nil, 111]

3.5.5 动态数组

如果越界赋值,Ruby会自动填充中间部分:

> a[10] = 111
=> 111
> a
=> [1, 2, 3, nil, nil, nil, nil, nil, nil, nil, 111]

3.5.6 数组大小

> a.size
=> 11

3.5.7 动态调整数组大小

> a = [1, 2, 3]
=> [1, 2, 3]
> a[1, 0] = ['2', '3'] # 把['2', '3']插入到下标1开始的第0个元素后面
=> ["2", "3"]
> a
=> [1, "2", "3", 2, 3]
> a = [1, 2, 3, 4, 5]
=> [1, 2, 3, 4, 5]
> a[2..4] = [1]
=> [1]
irb(main):331:0> a # 从下标2~4的元素已经被替换成另外一个数组[1]
=> [1, 2, 1]

3.5.8 集合操作

3.5.8.1 交集
> [1, 2, 3] & [1, 3, 5]
=> [1, 3]
3.5.8.2 并集
> [1, 2, 3] | [1, 3, 5]
=> [1, 2, 3, 5]
3.5.8.3 集差
> [1, 2, 3] - [1, 3, 5]
=> [2]

3.6 Range

范围对象,可以通过下面三种方式创建:

# 方式1,显示创建方法
Range.new(1, 10) # 元素为1~10
# 方式2
1..10 # 元素为1~10
# 方式3
1...10 # 元素为1~9

除了生成数字范围,还可以生成字母范围:

> ('a'..'h').to_a
=> ["a", "b", "c", "d", "e", "f", "g", "h"]

3.6.1 转成List对象

Range对象调用to_a表示转换成Lis

> (1..10).to_a
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

3.6.2 遍历

(14..31).each do |i|
  puts i
end

3.7 散列表

3.7.1 创建散列表

> {name: "lu4nx", site: "www.shellcodes.org"}
=> {:name=>"lu4nx", :site=>"www.shellcodes.org"}
> {"name": "lu4nx", site: "www.shellcodes.org"} # 表达方式都一样
=> {:name=>"lu4nx", :site=>"www.shellcodes.org"}
> {"name" => "lu4nx", "site" => "www.shellcodes.org"} # 如果希望字符串做散列表,就这样创建
=> {"name"=>"lu4nx", "site"=>"www.shellcodes.org"}

3.7.2 散列表索引

> info = {name: "lu4nx", site: "www.shellcodes.org"}
=> {:name=>"lu4nx", :site=>"www.shellcodes.org"}
> info[:name]
=> "lu4nx"

3.7.3 散列表遍历

> info.each do |k, v|
*   puts k, v
* end
name
lu4nx
site
www.shellcodes.org
=> {:name=>"lu4nx", :site=>"www.shellcodes.org"}

3.8 符号

符号也是对象。符号对象在判断时只用对比是否是一个对象,比字符对比更加有效率。

将转换成字符串对象:

> :a.to_s
=> "a"

字符串也可以转换成符号:

> "a".to_sym
=> :a

3.9

代码块(Block)有两种写法:

do...end

{...}

只是风格上的区别,一般需要跨行,就用do…end,单行的用{…},如:

# 多行代码块
10.times do |i|
  puts i
end

# 单行代码块
10.times {|i| puts i}

3.9.1 定义一个块

def xx(n)
  puts 'hi'
  yield(n)
end

xx(10) do |n|
  puts n
end

个人感觉Ruby的块,看上去很像Lisp的宏。例如,定义一个循环:

def loop1
  while true
    yield
    print "hi"
  end
end

loop1 do
  print 1
  break
end

3.9.2 判断是否有块

def xx(n)
  puts 'hi'
  if block_given?
    yield(n)
  end
end

xx(10) do |n|
  puts n
end

xx(10) # 不带块操作

在块中遇到break会马上中断块执行:

def xx(n)
  puts 'hi'
  if block_given?
    yield(n)
  end
end

xx(10) do |n|
  break
  puts n # 这里不会执行
end

3.9.3 用块隐藏常规处理

如下,实现了类似Python的with open:

File.open('/etc/passwd') do |f|
  f.each_line do |line|
    puts line
  end
end

这里,不用手动调用close关闭文件操作符,因为块里已经实现了。

3.9.4 块对象

块还可以封装成对象,用Proc类:

hello = Proc.new do |name|
  puts name
end

hello.call('lx')

封装成对象后,就可以传递了:

def exec_block(&b)
  b.call
end

exec_block() do puts 'hi' end

&表示接受一个块对象,如果传递了块,值就为块对象,否则为nil。

3.9.5 lambda

等价Proc

hi = lambda do puts 'hi' end

hi.call

3.10 控制结构

3.10.1 逻辑运算

布尔关键字:true和false。

与:&& 或:|| 非:!

3.10.2 条件判断

3.10.2.1 if判断
if ... then
...
end

# 或者:

if ... then
...
elsif ... then
...
else
...
end

注1:上面的then可以省略。

注2:nil和false以外的均为true。

  1. if用作修饰符号
    def is_hello?(msg)
      return true if msg == 'hello'
    end
    
    puts is_hello?('hello')
    puts is_hello?('hello1')
    
3.10.2.2 条件运算
..?..:..

例:

> a = 1
=> 1
> b = 2
=> 2
> c = (a > b) ? true : false
=> false
3.10.2.3 unless

与if的相反。

unless ... then
...
end
3.10.2.4 case

类似C语言的switch。

case 对象
when 值1 then
...
when 值2 then
...
else
...
end

例1,对象类型判断:

a = ["a", 1, nil]

a.each do |i|
  case i
  when String
    puts String
  when Numeric
    puts Numeric
  else
    puts "I don't known"
  end
end

例2,case中使用正则匹配:

f = File.open("/etc/passwd")

f.each_line do |line|
  case line
  when /lu4nx/
    puts line
  when /root/
    puts line
  else
  end
end
3.10.2.5 =

更严格的判断,可用来判断正则是否相等、左边对象是否属于右边的类,如:

> String === 'a'
=> true
> /a/ === 'abc'
=> true

实际上case语句就是用的”===“做判断。

3.10.2.6 判断是否是同一对象

每个对象都有一个object_id或者__id__属性:

> a.object_id
=> 3
> a.__id__
=> 3

用equal?方法判断是否是同一对象:

> a.equal?(a)
=> true
> a.equal?(b)
=> false
3.10.2.7 eql?方法

类似Common Lisp中的eql,用作更严谨的判断,如:

> 1 == 1.0
=> true
> 1.eql?(1.0)
=> false

3.10.3 循环

3.10.3.1 for循环

for陷阱:for不会开辟新的作用域,for中定义的变量对外是可见的。

for i in 1..5 do
  puts i
end

注,关键字”do“可以省略。

3.10.3.2 while循环
while ... do
    ...
end

注:do可以省略。

times方法:

> 10.times do print 'a' end
aaaaaaaaaa=> 10
3.10.3.3 until
i = 100

until i <= 0 do
  i -= 1
end

print i
3.10.3.4 times方法

按次数循环可用数字类型的times方法。

10.times do
  print 'a'
end
3.10.3.5 each方法

遍历序列对象可直接用each方法:

[1, 2, 3].each do |i|
  puts i
end
3.10.3.6 loop,无限循环
loop do
  puts 1
end

无限循环也可以用while true代替:

while true do
  puts 1
end

3.10.4 循环控制

3.10.4.1 break

打断循环流程。

for i in (1..100).to_a
  if i == 49
    break
  end
end
3.10.4.2 next

跳到下一次循环,等价于其他语言的”continue“关键字。

for i in (1..100).to_a
  if i != 49
    next
  else
    print i
  end
end
3.10.4.3 redo

redo会重新跳入循环,这是很少使用的关键字。

i = 0
["Perl", "Python", "Ruby", "Scheme"].each do |lang|
  i += 1

  if i == 3
    redo
  end

  p [i, lang]
end

输出如下:

[1, "Perl"]
[2, "Python"]
[4, "Ruby"]
[5, "Scheme"]

执行到i等于3时,又重新跳入循环,导致i多加了一次1。

3.11 异常处理

begin
  可能会发生异常的代码
rescue => 引用异常对象的变量(不指明对象将异常赋值给$!)
  发生异常后的处理
end

如:

begin
  File.read('/etc/passwd1')
rescue => e
  puts e
end

还可以写成:

begin
  File.read('/etc/passwd1')
rescue
  puts $!
end

通过$@获得最后异常信息:

begin
  File.read('/etc/passwd1')
rescue
  puts $@
end

输出:

x.rb:2:in `read'
x.rb:2:in `<main>'

3.11.1 异常对象

异常对象有几个方法:

begin
  File.read('/etc/passwd1')
rescue
  puts $@.class # 获得异常信息种类
  puts $@.message # 获得异常信息
  puts $@.backtrace # 获得异常信息调用栈
end

3.11.2 ensure

不管是否异常,都会调用ensure块的内容:

begin
  File.read('/etc/passwd1')
rescue
  puts $@
ensure
  puts 'bye.'
end

3.11.3 异常重试

发生异常后,还可以用retry关键字让它重试:

begin
  File.read('/etc/passwd1')
rescue
  puts $@
  sleep(1)
  retry
end

一定要注意如果一直重试失败,会陷入死循环。

3.11.4 rescue修饰符

表达式1 rescue 表达式2

表达式1出现异常后,调用表达式2,如:

> Integer('a') rescue 0
=> 0
> Integer(1) rescue 0
=> 1
> Integer('a')
ArgumentError: invalid value for Integer(): "a"
from (pry):3:in `Integer'

3.11.5 省略begin和end关键字

如果整块代码都在异常处理内,可以省略begin和end:

def xx(s)
  return Integer(s)
rescue
  return 0
end

puts xx('a')
puts xx(1)

3.11.6 捕获多个异常

rescue 异常1 => 变量
rescue 异常2 => 变量

例:

def xx(s)
  return Integer(s)
rescue ArgumentError
  return 0
rescue TypeError
  return -1
end

puts xx('a')
puts xx(1)
puts xx(nil) # => -1

3.11.7 定义新的异常

所有异常都是Exception的子类:

def xx(s)
  return Integer(s)
rescue Exception
  return 0
end

puts xx('a')
puts xx(1)
puts xx(nil) # => 0

如果rescue不指定异常,默认是捕获StandardError异常。

定义新异常:

XXError = Class.new(StandardError) # 表示继承StandardError

3.11.8 抛出异常

raise 异常类
raise 异常类, 消息
raise # 抛出RuntimeError

例如,抛出上面刚定义的新异常XXError:

XXError = Class.new(StandardError)

def xx(s)
  raise XXError
rescue XXError
  return 0
end

puts xx('a') # => 0
puts xx(1) # => 0
puts xx(nil) # => 0

Ruby没有assert关键字,可以用raise … unless组合起来实现类似的功能:

raise "domain is nil" unless domain != nil

3.12 正则表达式

3.12.1 正则匹配

/正则/ =~ "要匹配的字符串"

例:

> /world/ =~ "hello world"
=> 6

如果匹配成功,返回匹配的字符第一次出现的位置(从0开始算),否则返回nil。

正则后加i表示不区分大小写:

> /WORLD/i =~ "hello world"
=> 6

3.12.2 提取数据

> url = "http://www.shellcodes.org/"
> host = url.scan(/http:\/\/([\w\.]+)\//)
=> [["www.shellcodes.org"]]

4 模块

模块提供一个命名空间机制。

4.1 引用模块

4.1.1 require关键字

require "[库名]"

如果引用的文件在当前目录,库名需要加“./”(不需要加.rb):

require './hello'

在模块化的时候,注意变量的作用域。如果被引用文件里定义的变量不是全局变量,在引用模块里是不能引用这个变量的。

实例,引用pp库(可美化打印内置对象的内置库):

require "pp"

v = [{
       name: "lu4nx",
       site: "www.shellcodes.org"
     },
     {
       name: "lu4nx",
       email: "lx_at_shellcodes.org"
     }]

pp v

输出:

[{:name=>"lu4nx", :site=>"www.shellcodes.org"},
 {:name=>"lu4nx", :email=>"lx_at_shellcodes.org"}]

4.1.2 include关键字

导出模块里所有名字,就可以不通过模块名来访问,一般最好不要这么做,否则有重名的风险:

[6] pry(main)> include Math
=> Object
[7] pry(main)> PI
=> 3.141592653589793 # 可以直接访问

4.2 创建模块

# 创建一个模块
module LX
  # 模块常量
  Version = "1.0"

  def say(msg)
    puts msg
  end

  # 获得模块自身信息,保存在self变量中
  def self_object
    self
  end

  # 不这么定义的函数或者类,只能在上下文中引用,
  # 不能被外部引用以“模块名.方法名”引用
  module_function :say
end

4.3 扩展模块

用extend方法可以扩展模块:

module LX
  Version = "1.0"

  def say(msg)
    puts msg
  end
end

s1 = "hi"
s1.extend(LX)
s1.say(s1) # => hi

5

Ruby是完全面向对象的,所有的“类型内置”实质都是类对象,列举如下:

  • 数值:Numeric
  • 字符串:String
  • 数组:Array
  • 散列:Hash
  • 正则表达式:Regexp
  • 文件:File
  • 符号:Symbol

可以通过class方法来查看对象所属类:

> "hello".class
=> String
> 1.class
=> Fixnum
> [1, 2, 3].class
=> Array

类本身也是对象,所以类也有自身的类:

> Array.class
=> Class
> String.class
=> Class

用instance_of?方法可以判断对象是否属于某个类:

> a.instance_of?(Array)
=> true
> a.instance_of?(String)
=> false

5.1 初始化类

调用new方法:

a = Array.new

5.2 类继承

BasicObject是Ruby中所有类的父类。

子类与父类的关系称为 is-a ,根据继承关系反向查对象时,可用is_a?:

> a.is_a?(Object)
=> true

继承一个类,如下:

class NewString < String
  def hi
    puts "hi"
  end
end

s = NewString.new("hi")
puts s
s.hi

BasicObject提供了一个类最低限度的方法,所以可以通过它继承。

可用ancestors方法获得集成关系:

> Integer.ancestors
=> [Integer, Numeric, Comparable, Object, Math, PP::ObjectMixin, Kernel, BasicObject]

用superclass方法获得父类:

> Integer.superclass
=> Numeric

5.3 定义类

示例如下:

# coding: utf-8

class Hello
  def initialize(msg)  # 构造函数
    @msg = msg  # 成员变量
  end

  def show_msg
    puts @msg # 引用成员变量
  end

  def msg # 外部不能直接访问成员变量,需要定义“存取器”
    @msg
  end

  def msg=(new_msg)
    @msg = new_msg
  end
end

hello = Hello.new("hi")
hello.show_msg
puts hello.msg
hello.msg = 'new msg'
hello.show_msg

5.3.1 存取器

上面的例子定义了msg的reader和writer方法,如果有太多成员变量的话,会很不方便。所以Ruby允许定义存取器:

attr_reader :name,只读。
attr_writer :name,只写。
attr_accessor :name,读写,合称accessor。

示例代码:

class Hello
  attr_reader :msg
  attr_writer :msg
  # 实现读写直接用attr_accessor :msg

  def initialize(msg)  # 构造函数
    @msg = msg  # 成员变量
  end

  def show_msg
    puts @msg # 引用成员变量,可以不加@开头
  end
end

hello = Hello.new("hi")
hello.show_msg
puts hello.msg
hello.msg = 'new msg'
hello.show_msg

5.3.2 self变量

self用来引用接收者,上面代码稍改下:

def show_msg
  puts self.msg
end

self可以用来解决方法中有重名的局部变量:

def show_msg
  msg = 'hi' # 这是局部变量
  puts self.msg
end

5.4 定义类方法

类方法就是接收对象是类,而不是实例:

class << Hello
  def say(msg)
    puts msg
  end
end

Hello.say("new hello")

方法调用return返回值,如果省略的话,返回值就是方法最后一条语句执行结果。

Ruby有三种方法:

  • 对象方法;
  • 类方法;

-函数式方法。

对象方法

消息接收者是一个对象,如几乎每个对象都有to_s方法:

> a = [1, 2, 3]
=> [1, 2, 3]
> a.to_s
=> "[1, 2, 3]"

类方法 类似静态方法

> File.read('/etc/issue')
=> "Fedora release 22 (Twenty Two)\nKernel \\r on an \\m (\\l)\n\n"
> File::read('/etc/issue') # 也可以这么调用
=> "Fedora release 22 (Twenty Two)\nKernel \\r on an \\m (\\l)\n\n"

函数式方法 没有对象接收者的方法,外观看上去并不像方法调用,更像是调用函数:

print "hello world"

类方法是可以和对象方法重名的,虽然可以重名,但也可以定义到类中,用self:

class Hello
  attr_reader :msg
  attr_writer :msg
  # 实现读写直接用attr_accessor :msg

  class << self
    def say(msg)
      puts msg
    end
  end

  def initialize(msg)  # 构造函数
    @msg = msg  # 成员变量
  end

  def show_msg
    msg = 'hi'
    puts self.msg # 引用成员变量
  end
end

hello = Hello.new("hi")
hello.show_msg
Hello.say("new hello")

此外,还可以这样定义:

def Hello.say(msg)
    puts msg
end

甚至:

def self.say(msg) # 因为还在class的上下文中,所以能这么定义
  puts msg
end

5.4.1 方法参数

默认参数:

def say(msg='no msg')
  puts msg
end

不定参数:

def foo(*args)
  args
end

print foo(1, 2, 3)

关键字参数:

注:Ruby2.0之后才支持

def foo(msg: nil)
  print msg
end

foo(msg: 'hi')
foo()

分解参数:

通过散列方式传递参数:

foo({msg: 'hi'})

通过数组分解:

def foo(a, b, c)
  print a, b, c
end

foo(*[1, 2, 3])

5.4.2 类中定义常量

示例如下:

class Hello
  DEFAULT_MSG = 'default msg' # 常量定义

  def say(msg)
    if msg == ''
      puts DEFAULT_MSG
    else
      puts msg
    end
  end
end

5.5 类变量

所有实例都共享的变量就是 类变量 ,定义时以@@开头:

class TheI
 @@count = 0

  def count
    puts @@count
  end

  def plus
     @@count += 1
  end
end

the_i1 = TheI.new
the_i1.count
the_i2 = TheI.new
the_i2.count

the_i1.plus
the_i1.count # => 1
the_i2.count # 也是1

5.6 访问权限

  • public:默认;
  • private:无法从外部访问;
  • protected:子类可访问。
class Hi
  def self_known
    puts 'self'
  end

  def you_known
    puts 'you known'
  end

  def i_want_access_self_known
    self_known
  end

  # 设置访问级别
  public :you_known # 默认就是public
  private :self_known
end

hi = Hi.new
hi.you_known
hi.i_want_access_self_known

还可以用更统一的方式定义:

class Hi
  # 以下方法都为private级别
  private
  def self_known
    puts 'self'
  end

  # 以下方法都为public级别
  public
  def you_known
    puts 'you known'
  end

  def i_want_access_self_known
    self_known
  end
end

hi = Hi.new
hi.you_known
hi.i_want_access_self_known

5.7 扩展类

其他语言只允许你读取类的相关信息(这种技术称为”自省“),而Ruby允许你在运行时修改这些信息。Ruby有个很强的特性就是可以对已存在的类进行打开并扩充,一个实际应用的例子,把对象转成YAML格式:

> require 'yaml'
=> true
> [1, 2, 3].to_yaml
=> "---\n- 1\n- 2\n- 3\n"

扩充一个类,首先”进入“这个类,然后为它添加新方法。如下,为String类添加新方法:

class String
  def hi
    puts "hi"
  end
end

s = "hello"
s.hi

5.8 类别名

class C1
  def hello
    puts "hello"
  end
end

class C2 < C1
  alias old_hello hello # 别名化父类的”hello“方法

  # 重定义hello
  def hello
    puts "hello world"
  end
end

c = C2.new
puts c.hello
puts c.old_hello

5.9 删除已定义的方法

类中用undef关键字可以删除指定的方法,示例如下:

class C1
  def hello
    puts "hello"
  end

  def say(msg)
    puts msg
  end
end

class C2 < C1
  alias old_hello hello

  # 重定义hello
  def hello
    puts "hello world"
  end

  # 不要父类中的say方法
  undef say
end

c = C2.new
puts c.hello
puts c.old_hello
c.say('hi')

5.10 单例类

str1 = "Ruby"
str2 = "Ruby"

class << str1
  def say(msg)
    puts msg
  end
end

str1.say("hehe")
str2.say("hehe") # => undefined method `say' for "Ruby":String

5.11 Mix-in

Mix-in从概念上说就是一个类包含了其他类的方法。Ruby是单一继承,但实现了Mix-in,所以也可以实现多重继承。

Mix-in相当于给类添加了实例方法:

module LX
  Version = "1.0"

  def say(msg)
    puts msg
  end
end

class C
  include LX
end

c = C.new
c.say("hehe")

单一继承让类的层次关系更清晰,多重继承会增加类层次的杂性。但是有些情况下还是需要多重继承,比如实现一个Stream类,Read和Write都是它的子类,但是要实现ReadAndWrite,就必须多重继承了。实现代码如下:

class Stream
end

module Reader
  def read
    puts 'read'
  end
end

module Writer
  def write
    puts 'write'
  end
end

class Read < Stream
  include Reader
end

class Write < Stream
  include Writer
end

class ReadAndWrite
  include Reader
  include Writer
end

rw = ReadAndWrite.new
rw.read # => read
rw.write # => write

5.12 refine

猴子补丁会在不知不觉中修改类,所以从Ruby2.0开始增加了refine功能。下面是示例代码:

# refine需要定义一个模块
module StringExt
    refine String do
        def say(msg)
            puts msg
          end
      end
end

using StringExt # 明确使用了using关键字,扩展类才会生效
'hi'.say('hello world')

using的作用域在当前模块结束,如果是顶层(main)中调用using,有效范围直到文件结束。从Ruby2.1开始,也可以让using的作用域仅模块内有效,上面代码修改如下:

# refine需要定义一个模块
module StringExt
    refine String do
        def say(msg)
            puts msg
          end
      end
end

module Say
    using StringExt
    'hi'.say('hello world') # hello world
end

'hi'.say('hello world') # undefined method `say' for "hi":String (NoMethodError)

6 其他

6.1 I/O操作

6.1.1 读写文件

读:

> File.new("/etc/hosts").read

写:

> f = File.new("/tmp/xx", "w")
=> #<File:/tmp/xx>
> f.puts "test"
=> nil
> f.close
=> nil

6.1.2 ARGV数组

保存脚本运行时的参数。注意第0个元素保存的是参数1,而不是脚本文件名。

一个读取文件示例:

filename = ARGV[0]
f = File.open(filename)
puts f.read
f.close

更短小:

filename = ARGV[0]
puts File.read(filename)

还要短小:

puts File.read(ARGV[0])

6.1.3 逐行遍历文件

f = File.open(ARGV[0])
f.each_line do |line|
  puts line
end
f.close

例,一个grep的简单实现:

filename = ARGV[0]
pattern =  Regexp.new(ARGV[1])

f = File.open(filename)
f.each_line do |line|
  if pattern =~ line
    puts line
  end
end

f.close

6.1.4 标准输入

STDIN.each_line do |line|
  puts line
end

6.2 调用系统命令

1、反引号:

> `pwd`
=> "'/home/lu4nx\n"

2、exec函数:

> exec "pwd"
/home/lu4nx

3、system函数:

> system "pwd"
/home/lu4nx
=> true