原文 http://blog.sloger.info/programing/basic-grammar-in-ruby-part-1-obje
很早就想学习除了 Perl 以外的另一门脚本语言,自然而然最近比较流行的就是 Python 和 Ruby,之前也曾纠结过要学哪一个。也曾每种语言都浏览一下基本语法,发现 Ruby 与 Perl 在一些方面挺像,再加上群里有人说学一门语法糖多的语言对以后有好处,于是便决定学习 Ruby 了。
本文本来是比较 Ruby 与 Perl 的语法的,结果发现越写越长,于是干脆不比较了,只是在一些地方提到了 Perl 的做法。
Ruby 与 Perl 有很多相似的地方。包括但不限于:
$
+ 一个标点符号的预定义全局变量- 相同的命令行参数
- 嵌入语言的正则引擎
演示均在 irb 中进行,普通行表示输入, # 开头的行表示输出,#=> 开头的行表示返回值
Ruby 中一切皆是对象
1.class
#=> Fixnum
1.1.class
#=> Float
4200000000.class
#=> Bignum
[].class
#=> Array
{}.class
#=> Hash
/1/.class
#=> Regexp
nil.class
#=> NilClass
Kernel.class
#=> Module
Module.class
#=> Class
Class.class
#=> Class
Blocks
为什么把 block 放在最前面?因为 block 在 ruby 中的地位相当重要,后面要讲的内容全都涉及到了 block
Ruby中, { }
和 do end
之间的代码集合构成一个 block。
调用方法时可以添加一个 block ,方法中可以使用 yield 来执行关联的 block。
{ }
比 do end
的优先级高,通常的做法是,单行用 { }
,多行用 do end
。
Methods
方法定义
Ruby 中使用 def
+ 方法名称 + 可选的参数列表 + 方法体 + end
定义方法。
# 建议:有参数时添加括号,无参数时省略括号
def Method_1(arg1, arg2)
"#{arg1.capitalize} #{arg2.capitalize}"
end
#=> :Method_1
Method_1("hello", "ruby")
#=> "Hello Ruby"
# 方法的参数可以有默认的取值
def Method_2(arg1 = "Hello", arg2 = "ruby")
"#{arg1} #{argv2}"
end
#=> :Method_2
def
的返回值是方法名对应的 Symbols
,以后不再列出。
方法可以随意的重新定义,而不会出现其它语言中的方法重定义错误。
def welcome
"Hello Ruby"
end
#=> :welcome
welcome
#=> "Hello Ruby"
def welcome
“Hello Ruby ” * 2
end
#=> :welcome
welcome
#=> "Hello Ruby Hello Ruby "
特殊的方法名
Ruby 方法可以以三种标点符号结尾
?
: 表示询问的方法,返回布尔值!
: 危险的方法,或者会修改调用者本身 (并不适用于所有情况)=
: 出现在类里,以=
结尾的方法可以出现在赋值语句的左边
可变参数方法
def varargs(arg1, arg2, *rest)
"Args: #{arg1}, #{arg2}, #{rest.join(', ')}"
end
*rest
表示将实参列表中除了 arg1
和 arg2
之外的其余参数收集成为一个 Array
并赋值给 rest
。
Method with Block
def welcome(greeting = "Hello Ruby")
if block_given?
yield
else
greeting
end
end
welcome
#=> "Hello Ruby"
welcome { "Welcome to the Ruby world" }
#=> "Welcome to the Ruby world"
block_given?
用于判断调用方法时是否提供了关联的Block, 与 Perl 中的 wantarray
有异曲同工之处。
yield
用来调用调用方法时提供的Block,可与方法调用一样给 yield
添加参数。
&
修饰符
- 如果方法的最后一个形参前有
&
修饰符,调用方法时将寻找一个 block 并将其转为一个Proc
对象并赋值给该参数。
def welcome(&proc)
proc.call unless proc.nil?
end
#=> :welcome
welcome
#=> nil
welcome { puts "Hello Ruby" }
# Hello Ruby
#=> nil
- 也可以使用
lambda
显式地将一个 block 转换成为Proc
对象。
def welcome(proc)
proc.call unless proc.nil?
end
welcome(lambda { puts "Hello Ruby" })
# Hello Ruby
=> nil
- 如果调用方法的最后一个实参前有
&
修饰符,则认为其是一个Proc
对象,将其从实参中删除并转换为 block。
def welcome
yield if block_given?
end
#=> :welcome
welcome
#=> nil
greeting = lambda { puts "Hello Ruby" }
#=> #<Proc:0x94e2290@(pry):166 (lambda)>
welcome &greeting
#Hello Ruby
#=> nil
收集散列表
不说了,看吧
def hash_collect(arg1, rest)
puts "#{arg1} #{rest}"
end
hash_collect("f", { 'A' => 'x', 'B' => 'y'})
# f {"A"=>"x", "B"=>"y"}
# => nil
hash_collect("f", 'A' => 'x', 'B' => 'y')
# f {"A"=>"x", "B"=>"y"}
# => nil
hash_collect("f", :A => :x, :B => :y)
# f {:A=>:x, :B=>:y}
#=> nil
Array
Array的定义
直接初始化
a = [1, 2, 4, 6, 8, "asd"]
#=> [1, 2, 4, 6, 8, "asd"]
lang = %w( perl python ruby java lisp )
#=> ["perl", "python", "ruby", "java", "lisp"]
显式使用 Array.new
Array.new
有3种参数形式
# size 为大小, obj为填充的对象
Array.new(size = 0, obj = nil)
# 用另一个 Array 初始化一个 Array
Array.new(another_array)
# 定义一个大小为 size 的 Array,并使用 index 调用 block,使用其返回值填充 Array
Array.new(size) { |index| block }
比如:
Array.new(5, "Ruby")
#=> ["Ruby", "Ruby", "Ruby", "Ruby", "Ruby"]
Array.new(Array.new(5, "Ruby"))
#=> ["Ruby", "Ruby", "Ruby", "Ruby", "Ruby"]
Array.new(5) { |index| index ** 2 }
#=> [0, 1, 4, 9, 16]
*
修饰符
*
用于等式的左边(方法的形参)时,将等式的右边(方法的实参)中剩余的部分收集成一个 Array。*
用于等式的右边(方法的实参)时,将等式的右边(方法的实参)中对应的变量当成一个 Array 并分解。
a = (1 .. 5).to_a
#=> [1, 2, 3, 4, 5]
# b == 1, c == 2
b, c = a
#=> [1, 2, 3, 4, 5]
# b == 1, c == [2, 3, 4, 5]
b, *c = a
#=> [1, 2, 3, 4, 5]
# b == 99, c == [1, 2, 3, 4, 5]
b, c = 99, a
#=> [99, [1, 2, 3, 4, 5]]
# b == 99, c == [[1, 2, 3, 4, 5]]
b, *c = 99, a
#=> [99, [1, 2, 3, 4, 5]]
# b == 99, c == 1
b, c = 99, *a
#=> [99, 1, 2, 3, 4, 5]
# b == 99, c == [1, 2, 3, 4, 5]
b, *c = 99, *a
#=> [99, 1, 2, 3, 4, 5]
Array 的访问
Ruby 可以使用 []
访问 Array 中的元素, []
有3中参数形式:
lang[index]
&lang.slice(index)
lang[start, length]
&lang.slice(start, length)
lang[range]
&lang.slice(range)
举例:
lang[0]
#=> "perl"
# 访问不存在的元素
lang[100]
#=> nil
# 逆序访问
lang[-1]
#=> "lisp"
# 直接赋值
lang[6] = "php"
#=> "php"
# 自动填充
lang
#=> ["perl", "python", "ruby", "java", "lisp", nil, "php"]
lang[3, 4]
#=> ["java", "lisp", nil, "php"]
lang[2..5]
#=> ["ruby", "java", "lisp", nil]
Array 运算
# 相加
%w( 1 1 2 3 4 ) + %w( 2 4 5 )
#=> ["1", "1", "2", "3", "4", "2", "4", "5"]
# 相减
%w( 1 1 2 3 4 ) - %w( 2 4 5 )
#=> ["1", "1", "3"]
# 乘以 int 表示重复
%w(1 2 3 4) * 2
#=> ["1", "2", "3", "4", "1", "2", "3", "4"]
# 乘以 string 表示 join
%w(1 2 3 4) * ", "
#=> "1, 2, 3, 4"
# 相与并去重
%w(1 1 2 3 4) & %w( 2 4 5 )
#=> ["2", "4"]
# 后入 ...
[1, 2] << "c" << "d" << [3, 4]
#=> [1, 2, "c", "d", [3, 4]]
# 相等 大小相等 每个元素也相等
%w(1 1 2 3 4) == %w( 2 4 5 )
#=> false
其它常用的 Array 方法
# 清空 lang
lang.clear
# 去重
%w(1 1 2 3 4).uniq
#=> ["1", "2", "3", "4"]
lang.join(', ')
#=> "perl, python, ruby, lisp, php"
lang.size
#=> 5
lang.length
#=> 5
lang.empty?
#=> false
lang.include?('python')
#=> true
lang.include?('java')
#=> false
# 转置
lang.reverse
#=> ["php", "lisp", "ruby", "python", "perl"]
lang.reverse!
lang.sort
#=> ["lisp", "perl", "php", "python", "ruby"]
lang.sort!
# 删除指定元素
lang.delete('java')
#=> "java"
lang
#=> ["perl", "python", "ruby", "lisp", "php"]
Array 迭代器
# 返回第一个符合要求的元素
[1, 2, 3, 5, 7, 8].find { |index| index > 6 }
#=> 7
lang.each { |l| puts l.capitalize }
# Perl
# Python
# Ruby
# Lisp
# Php
#=> ["perl", "python", "ruby", "lisp", "php"]
# 使用 lang 的每个元素调用 block,以 block 的返回值生成新的 Array
lang.collect { |l| l.to_s.capitalize }
#=> ["Perl", "Python", "Ruby", "Java", "Lisp", "Php"]
# 同上,但是直接修改 lang
lang.collect! { |l| l.to_s.capitalize }
# map 同 collect
lang.map { |l| l.to_s.capitalize }
lang.map! { |l| l.to_s.capitalize }
# 去除 lang 中的 nil 元素
lang.compact
#=> ["perl", "python", "ruby", "java", "lisp", "php"]
# 直接修改自身
lang.compact!
# 使用上一次迭代的返回值和下一个元素调用block,一个可选的参数用于指定初始的迭代值
[1, 2, 3, 5, 7, 8].inject { |sum, ele| sum + ele }
#=> 26
[1, 2, 3, 5, 7, 8].inject { |fab, ele| fab * ele }
#=> 1680
更多 Array API 参阅 Ruby-Doc Array
Range
Range 的定义
..
表示闭区间...
表示左闭右开区间
1 .. 5
#=> 1..5
1 ... 5
#=> 1...5
(1 .. 5).to_a
#=> [1, 2, 3, 4, 5]
(1 ... 5).to_a
#=> [1, 2, 3, 4]
Range 做为布尔值
在 Range A .. B
中,A 之前为 false
, A 到 B 为 true
,B 之后为 false
。
Range 成员方法
a = 1 .. 5
=> 1..5
a === 4
#=> true
a.include? 4
#=> true
a === 'z'
#=> false
a.include? 'z'
#=> false
a.max
#=> 5
a.min
#=> 1
a.each { |e| print "#{e} " }
# 1 2 3 4 5 #=> 1..5
a.reject { |e| e > 3 }
#=> [1, 2, 3]
更多 Range API 参阅 Ruby-Doc Range
Hash
Hash 定义
直接定义
像 Perl 一样,Ruby 使用大括号 + “胖箭头” ( =>
) 定义 Hash
hash = { 'a' => 'x', 'b' => 'y', 'c' =>'z' }
#=> {"a"=>"x", "b"=>"y", "c"=>"z"}
hash = { :a => 'x', :b => 'y', :c =>'z' }
#=> {:a=>"x", :b=>"y", :c=>"z"}
显示使用构造函数
像 Array 一样,Hash 的构造函数也有三种形式,含义与 Array.new
相同
new
new(obj)
new {|hash, key| block }
Hash 访问
与 Perl 不同的是,Hash 使用 []
访问,而非 {}
。
hash[:a]
#=> "x"
hash[:b]
#=> "y"
hash[:d]
#=> nil
Hash 常用方法
# 长度
hash.length
#=> 3
# 清空。这个就不演示了
hash.clear
# 是否为空
hash.empty?
#=> false
# 删除。如果给了 block,则不存在键时会把参数传递给 block。
# 成功时返回被删除的值,失败时返回 nil。
hash.delete(:d)
#=> nil
hash.delete(:d) {|key| puts "#{key} not found" }
# d not found
#=> nil
# 获取值对应的键
hash.key('y')
#=> :b
hash.key('m')
#=> nil
# 是否存在键
hash.key?(:a)
#=> true
hash.key?(:d)
#=> false
# 返回所有的键
hash.keys
#=> [:a, :b, :c]
# 转为 Array
hash.to_a
#=> [[:a, "x"], [:b, "y"], [:c, "z"]]
Hash 迭代器
hash.each {|key, value| block }
# 同上
hash.each_pair {|key, value| block }
hash.each_key {|key| block }
hash.each_value {|value| block }
# 删除掉 block 返回 true 的键值
hash.reject {|key, value| block}
更多 Hash API 参阅 Ruby-Doc Hash
Regexp
正则表达式的语法不再介绍了 ~~~
Pattern 的定义
Ruby 像 Perl 一样正则表达式引擎是直接嵌入语言的。
# 直接赋值
a = //
# perl 中的 qr/ /
b = %r()
# 面向对象做法
b = Regexp.new()
匹配
类似 Perl,Ruby 中有 =~
表示匹配, !~
表示不匹配。
$` # 匹配之前的部分
$& # 匹配的部分
$' # 匹配之后的部分
$1 $2 $3 $4 # 捕获组
\1 \2 \3 \4 # 反向引用
替换
Ruby 中没有 Perl 中的 s/pattern/string/
替换。而是使用 string 的 sub
和 gsub
方法。
sub
和 gsub
有两种形式:
sub(pattern, string)
: 表示用 string 替换 pattern。sub(pattern) { |match| block }
: 表示将匹配部分传递给 block,并用其返回值替换 pattern。
gsub
同 sub
。
Regexp 对象
re = %r(\b\d+\b)
#=> /\b\d+\b/
re.class
#=> Regexp
# 匹配的结果是一个 MatchData 对象
md = re.match('abcd 1234 ABCD')
#=> #<MatchData "1234">
md.class
#=> MatchData
# 访问
md[0] # $&
#=> "1234"
md[1] # $1
#=> nil
md.pre_match # $`
#=> "abcd "
md.post_match # $'
#=> " ABCD"
与嵌入语言的正则表达式相关变量相比,面向对象的匹配可以同时保存多组 MatchData 对象。
$
$&
$'
$1
$2
等都是从线程局部变量 $~
中获取的,因此可以通过修改 $~
的值来访问不同的匹配结果
$~
默认总是最后一个匹配的 MatchData
对象。
md1 = re.match('linux 0987 unix')
#=> #<MatchData "0987">
md1.object_id
#=> 78126770
# $~ 是最后一次匹配结果的 MatchData 对象的引用
$~
#=> #<MatchData "0987">
$~.class
#=> MatchData
$~.object_id
#=> 78126770
"#$` #$& #$'"
#=> "linux 0987 unix"
# 一次新的匹配
md2 = re.match('perl 3256 ruby')
#=> #<MatchData "3256">
md2.object_id
#=> 78163110
"#$`-#$&-#$'"
#=> "linux -0987- unix"
# 默认引用最后一次匹配的结果
$~
#=> #<MatchData "3256">
"#$`-#$&-#$'"
#=> "perl -3256- ruby"
# 修改 $~ 访问上一次匹配结果
$~ = md1
#=> #<MatchData "0987">
"#$`-#$&-#$'"
=> "linux -0987- unix"
# 修改回来,访问最后一次匹配
$~ = md2
#=> #<MatchData "3256">
"#$`-#$&-#$'"
#=> "perl -3256- ruby"
Class
定义类和实例
class Person
def initialize(name = "", age = 0, gender = "male")
@name = name
@age = age
@gender = gender
end
end
#=> :initialize
p = Person.new("Ruby", 19)
# 返回 p.inspect 的返回值
#=> #<Person:0x8d8a90c @name="Ruby", @age=19, @gender="male">
p.to_s
#=> "#<Person:0x8d8a90c>"
p.inspect
#=> "#<Person:0x8d8a90c @name=\"Ruby\", @age=19, @gender=\"male\">"
class
的返回值是定义的最后一个方法名对应的 Symbols
。
访问类成员
方法使用 .
访问,常量使用 ::
访问。
重写类方法
Ruby 中的类可以像方法一样重新打开并添加方法。
# 重新打开并添加成员方法
class Person
def inspect
puts self.to_s
end
def to_s
"#{self.class}: #@name, #@age, #@gender"
end
end
#=> :to_s
p
# p.inspect
# Person: Ruby, 19, male
#=> nil
p.to_s
#=> "Person: Ruby, 19, male"
实例特定的方法
p = Person.new
#=> #<Person:0x890cf10>
def p.say
puts "Hello Ruby"
end
#=> :say
p.say
# Hello Ruby
#=> nil
p2 = Person.new
#=> #<Person:0x8b26b20>
p2.say
# NoMethodError: undefined method `say' for #<Person:0x8b26b20>
# from (irb):10
# from /usr/bin/irb:11:in `<main>'
类变量,类方法
class Person
# 类变量
@@num = 0
# 类方法,同 def Person.empty?
def self.empty?
@@num == 0
end
def initialize(name = "", age = 0, gender = "male")
@name, @age, @gender = name, age, gender
@@num += 1
end
end
#=> :initialize
Person.empty?
#=> true
Person.new
#Person: , 0, male
#=>
Person.empty?
#=> false
继承
class Student < Person
def initialize(name = "", age = 0, gender = "male", scole = 0)
super(name, age, gender) # 同 java super 用法
@scole = scole
end
def to_s
super + " (#@scole)" # 非构造函数也能用
end
end
#=> :to_s
s = Student.new("Perl", 29, "male", 90)
#=> "Student: Perl, 29, male"
s.to_s
#=> "Student: Perl, 29, male (90)"
super()
与 super
的区别:
super()
表示不加参数调用超类同名方法super
表示已调用此方法的参数调用超类同名方法
attr
Ruby 中的 attr_reader
& attr_writer
& attr_accessor
用来添加 setter & getter
class Student; end
#=> nil
# 使用反射查看类不包括继承来的实例方法
Student.instance_methods false
#=> []
class Student
attr_reader :name
attr_writer :age
attr_accessor :gender
end
#=> nil
# 自动添加了 setter getter
Student.instance_methods false
#=> [:name, :age=, :gender, :gender=]
cattr
同 attr,但是添加的是类方法,而非实例方法
Student.methods false
#=> []
require 'active_support/all'
#=> true
class Student
cattr_reader :total
cattr_accessor :school
end
#=> [:school]
Student.methods false
#=> [:total, :school, :school=]
打开内置类
Ruby 中可以打开内置的类并添加,修改方法。
array = (1 .. 5).to_a
#=> [1, 2, 3, 4, 5]
array.fabs if array.methods.include? 'fabs'
#=> nil
class Array
# 阶乘
def fact
inject { |fab, ele| fab * ele }
end
end
#=> :fact
array.fact
#=> 120
也可以在改变内置类的方法时,创建一个别名。
# 修改内置的 Fixnum 类
class Fixnum
alias old_plus +
def +(other)
old_plus(other) * 2
end
end
1 + 1
#=> 4
1.old_plus 1
#=> 2
访问权限
Ruby 中有三个方法: public
protected
private
可以像 C++ 那样控制成员的访问权限。
class Access
# 默认为 public
def pub_m; end
private
def pri_m; end
protected
def pro_m; end
public
def pub_m2; end
end
Access.public_instance_methods false
#=> [:pub_m, :pub_m2]
Access.private_instance_methods false
#=> [:pri_m]
Access.protected_instance_methods false
#=> [:pro_m]
也可以使用方法名作为参数传递给这三个函数。
class Control
def pub_m; end
def pro_m; end
def pri_m; end
# 要放到方法定义之后,ruby需要看到方法的定义
public :pub_m
private :pri_m
protected :pro_m
end
Control.public_instance_methods false
#=> [:pub_m]
Control.protected_instance_methods false
#=> [:pro_m]
Control.private_instance_methods false
#=> [:pri_m]
Modules
模块就像一个 namespace
一个模块定义了一个 namespace,这样可以防止变量名、方法名污染
Modules 定义
module Modules
def Modules.m_1
puts "Method Modules.m_1"
end
def self.m_2
puts "Method self.m_2"
end
def m_3
puts "Method m_3"
end
end
Modules.m_1
# Method Modules.m_1
#=> nil
如果要引用其它的模块,使用 require 'Module_Name'
。
Mixin: 模块又像一个 Interface
Mixin 容许一个 class
inlcude
一个 module
。
class Class_M
include Modules
end
# 只有实例方法被 Mixin 了
Class_M.instance_methods
#=> [:m_3, :nil? ... ]
Class_M.new.m_3
# Method m_3
改变被 Mixin 的模块之后,所有 include
该模块的类都会发生变化.
module Modules
def m_3
puts "#{self.class}: Method m_3"
end
end
# 注意 self.class 是 Class_M 而非 Modules
Class_M.new.m_3
# Class_M: Method m_3
可以使用 Some_Class.included_modules
来获取类 include
的模块。
Class_M.included_modules
#=> [Modules, Kernel]
可以看到除了 Modules
之外,还有一个 Kernel
模块。
Kernel Modules
其实 Kernel
是 Object
inlucde
的,而所有对象的最终 superclass
都是 Object
,于是所有的类就都 include
Kernel
了。
Object.included_modules
#=> [Kernel]
Object.superclass
#=> BasicObject
BasicObject.included_modules
#=> []
Ruby 中大部分表现的像是语言内置的方法其实都是定义在 Kernel
中的,比如:
Kernel.methods
#=> [:sprintf, :format, :Integer, :Float, :String, :Array, :Hash, :warn, :raise, :fail, :global_variables, :__method__, :__callee__, :__dir__, :eval, :local_variables, :iterator?, :block_given?, :catch, :throw, :loop, :trace_var, :untrace_var, :at_exit, :syscall, :open, :printf, :print, :putc, :puts, :gets, :readline, :select, :readlines, :`, :p, :test, :srand, :rand, :trap, :exec, :fork, :exit!, :system, :spawn, :sleep, :exit, :abort, :load, :require, :require_relative, :autoload, :autoload?, :proc, :lambda, :binding, :caller, :caller_locations, :Rational, :Complex, :set_trace_func, :freeze, :===, :==, :<=>, :<, :<=, :>, :>=, :to_s, :inspect, :included_modules, :include?, :name, :ancestors, :instance_methods, :public_instance_methods, :protected_instance_methods, :private_instance_methods, :constants, :const_get, :const_set, :const_defined?, :const_missing, :class_variables, :remove_class_variable, :class_variable_get, :class_variable_set, :class_variable_defined?, :public_constant, :private_constant, :singleton_class?, :include, :prepend, :module_exec, :class_exec, :module_eval, :class_eval, :method_defined?, :public_method_defined?, :private_method_defined?, :protected_method_defined?, :public_class_method, :private_class_method, :instance_method, :public_instance_method, :nil?, :=~, :!~, :eql?, :hash, :class, :singleton_class, :clone, :dup, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :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__]
Mixin 的应用
当编写自定义的类时,可以 Mixin 一些内置的 Module。
Comparable
: Mixin 这个模块,再定义<=>
方法,就免费获得了<
<=
==
>=
>
和between?
方法。Enumerable
: Mixin 这个模块,再定义each
迭代器,就获得了map
include?
find_all
等迭代器,若果再定义<=>
方法,还可以获得min
max
sort
等方法。
方法的查找
调用方法时会按照一定的顺序查找方法:
对象直属类 > 类 Mixin 的 Module > 类的超类 > 累的超类 Mixin 的 Module > ...
如果一个类 Mixin 了多个模块,则按照 Mixin 的反向顺序查找,即最后一个 include
的 Module 会最先被查找。
实际上是按照一个 Array 查找的:
Class_M.ancestors
#=> [Class_M, Modules, Object, Kernel, BasicObject]