红宝石语法

Ruby编程语言的语法与PerlPython的语法大致相似。类和方法定义通过关键字发出信号,而代码块可以通过关键字或括号来定义。与perl相反,变量不具有sigil的前缀。使用时,Sigil会更改变量范围的语义。出于实际目的,表达式陈述之间没有区别。线路断裂很重要,并将其作为声明的结束;半隆可以等效地使用。与Python不同,凹痕并不重要。

与Python和Perl的区别之一是Ruby将其所有实例变量完全私密地保留到类中,并且仅通过访问者方法( attr_writerattr_reader等)将其曝光。与其他语言(例如C ++Java)的“ Getter”和“ Setter”方法不同,Ruby中的访问者方法可以通过元编码来创建一条代码行;但是,还可以以C ++和Java的传统方式创建访问者方法。由于这些方法的调用不需要使用括号,因此在不修改单个调用代码行或必须进行任何重构实现与C#VB.NET属性成员的相似功能的情况下,将实例变量更改为完整函数是微不足道的。

Python的财产描述符相似,但在开发过程中取决于权衡。如果一个人通过使用公开曝光的实例变量开始在Python中,然后更改实现以使用通过属性描述符公开的私有实例变量,则可能需要调整类的内部代码以使用私人变量,而不是公共属性。 Ruby的设计迫使所有实例变量都是私人的,但也提供了一种简单的声明setget方法的方法。这与Ruby中的想法保持一致,即从班级外部从未直接访问班级的内部成员;相反,一个人将消息传递给班级并收到回应。

互动会议

以下示例可以在红宝石外壳(例如Interactive Ruby shell)中运行,也可以保存在文件中,并通过键入ruby <filename>从命令行中运行。

经典Hello World示例:

puts 'Hello World!'

一些基本的红宝石代码:

# Everything, including a literal, is an object, so this works:
-199.abs # => 199
'ice is nice'.length # => 11
'ruby is cool.'.index('u') # => 1
"Nice Day Isn't It?".downcase.split('').uniq.sort.join
# => " '?acdeinsty"

输入:

print 'Please type name >'
name = gets.chomp
puts "Hello #{name}."

转换:

puts 'Give me a number'
number = gets.chomp
puts number.to_i
output_number = number.to_i + 1
puts output_number.to_s + ' is a bigger number.'

字符串

有多种方法可以定义Ruby中的字符串。

以下任务是等效的:

a = "\nThis is a double-quoted string\n"
a = %Q{\nThis is a double-quoted string\n}
a = %{\nThis is a double-quoted string\n}
a = %/\nThis is a double-quoted string\n/
a = <<-BLOCK
This is a double-quoted string
BLOCK

字符串支持可变插值

var = 3.14159
"pi is #{var}"
=> "pi is 3.14159"

以下任务等效并产生原始字符串

a = 'This is a single-quoted string'
a = %q{This is a single-quoted string}

收藏

构造和使用一个数组

a = [3, 'hello', 14.5, 1, 2, [6, 15]]
a[2] # => 14.5
a.[](2) # => 14.5
a.reverse # => [[6, 15], 2, 1, 14.5, 'hello', 3]
a.flatten.uniq # => [3, 'hello', 14.5, 1, 2, 6, 15]

构建和使用关联阵列(在Ruby中,称为A Hash ):

hash = Hash.new # equivalent to hash = {}
hash = { water: 'wet', fire: 'hot' } # makes the previous line redundant as we are now
 # assigning hash to a new, separate hash object
puts hash[:fire] # prints "hot"
hash.each_pair do |key, value| # or: hash.each do |key, value|
 puts "#{key} is #{value}"
end
# returns {:water=>"wet", :fire=>"hot"} and prints:
# water is wet
# fire is hot
hash.delete :water # deletes the pair :water => 'wet' and returns "wet"
hash.delete_if {|key,value| value == 'hot'} # deletes the pair :fire => 'hot' and returns {}

控制结构

如果语句:

# Generate a random number and print whether it's even or odd.
if rand(100).even?
 puts "It's even"
else
 puts "It's odd"
end

块和迭代器

创建代码块的两个语法:

{ puts 'Hello, World!' } # note the braces
# or:
do
 puts 'Hello, World!'
end

代码块可以作为可选块参数传递给方法。许多内置方法都有这样的论点:

File.open('file.txt', 'w') do |file| # 'w' denotes "write mode"
 file.puts 'Wrote some text.'
end # file is automatically closed here
File.readlines('file.txt').each do |line|
 puts line
end
# => Wrote some text.

参数通过一个块是封闭

# In an object instance variable (denoted with '@'), remember a block.
def remember(&a_block)
 @block = a_block
end
# Invoke the preceding method, giving it a block that takes a name.
remember {|name| puts "Hello, #{name}!"}
# Call the closure (note that this happens not to close over any free variables):
@block.call('Jon') # => "Hello, Jon!"

创建一个匿名功能

proc {|arg| puts arg}
Proc.new {|arg| puts arg}
lambda {|arg| puts arg}
->(arg) {puts arg} # introduced in Ruby 1.9

从一种方法中返回关闭

def create_set_and_get(initial_value=0) # note the default value of 0
 closure_value = initial_value
 [ Proc.new {|x| closure_value = x}, Proc.new { closure_value } ]
end
setter, getter = create_set_and_get # returns two values
setter.call(21)
getter.call # => 21
# Parameter variables can also be used as a binding for the closure,
# so the preceding can be rewritten as:
def create_set_and_get(closure_value=0)
 [ proc {|x| closure_value = x } , proc { closure_value } ]
end

将程序控制的流动流到在通话时提供的块:

def use_hello
 yield "hello"
end
# Invoke the preceding method, passing it a block.
use_hello {|string| puts string} # => 'hello'

使用块迭代枚举和数组:

array = [1, 'hi', 3.14]
array.each {|item| puts item }
# prints:
# 1
# 'hi'
# 3.14
array.each_index {|index| puts "#{index}: #{array[index]}" }
# prints:
# 0: 1
# 1: 'hi'
# 2: 3.14
# The following uses a (a..b) Range
(3..6).each {|num| puts num }
# prints:
# 3
# 4
# 5
# 6
# The following uses a (a...b) Range
(3...6).each {|num| puts num }
# prints:
# 3
# 4
# 5

诸如inject方法可以同时接受参数和块。 inject方法在列表的每个成员上迭代,在保留聚合的同时,在其上执行一些功能。这类似于功能编程语言中的foldl函数。例如:

[1,3,5].inject(10) {|sum, element| sum + element} # => 19

在第一个通过时,块将10(注入的参数)作为sum ,为element 1(数组的第一个元素)。这将返回11,然后在下一个通行证上成为sum 。将其添加到3处获得14,然后将其添加到第三次通过的5,最终返回19。

使用枚举和一个块将数字1到10平方(使用一个范围)进行平衡:

(1..10).collect {|x| x*x} # => [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

或在每个项目上调用一种方法( mapcollect的同义词):

(1..5).map(&:to_f) # => [1.0, 2.0, 3.0, 4.0, 5.0]

课程

以下代码定义了一个名为Person类。除了initialize ,通常要创建新对象的通常构造函数,它具有两种方法:一个方法覆盖<=>比较操作员(因此Array#sort可以按年龄进行排序),另一个可以覆盖to_s方法(因此, Kernel#puts pots可以格式化其输出)。在这里, attr_reader是Ruby中元编程的一个示例: attr_accessor定义了实例变量的getter和setter方法,但仅attr_reader仅getter方法。方法中的最后一个评估的语句是其返回值,从而允许省略明确的return语句。

class Person
 attr_reader :name, :age
 def initialize(name, age)
 @name, @age = name, age
 end
 def <=>(person) # the comparison operator for sorting
 @age <=> person.age
 end
 def to_s
 "#{@name} (#{@age})"
 end
end
group = [
 Person.new("Bob", 33),
 Person.new("Chris", 16),
 Person.new("Ash", 23)
]
puts group.sort.reverse

前面的代码以相反的年龄顺序打印三个名称:

Bob (33)
Ash (23)
Chris (16)

Person是一个常数,是对Class对象的引用。

打开课程

在Ruby中,从未关闭类:可以始终将方法添加到现有类中。这适用于所有类,包括标准的内置类。所需要做的只是打开现有类的类定义,并将指定的新内容添加到现有内容中。在标准库的Time类中添加新方法的一个简单示例:

# re-open Ruby's Time class
class Time
 def yesterday
   self - 86400
 end
end
today = Time.now # => 2013-09-03 16:09:37 +0300
yesterday = today.yesterday # => 2013-09-02 16:09:37 +0300

将方法添加到先前定义的类中通常称为猴子点。如果鲁ck进行,这种做法可能会导致两种行为碰撞,随后出乎意料的结果和代码可伸缩性问题。

由于Ruby 2.0,可以通过将贴片的范围限制为代码基库的特定区域,从而使用 改进来减少猴子斑点的潜在负面后果。

# re-open Ruby's Time class
module RelativeTimeExtensions
 refine Time do
   def half_a_day_ago
     self - 43200
   end
 end
end
module MyModule
 class MyClass
   # Allow the refinement to be used
   using RelativeTimeExtensions
   def window
     Time.now.half_a_day_ago
   end
 end
end

例外

提出了一个例外,并通过raise电话:

raise

可以将可选消息添加到例外:

raise "This is a message"

程序员也可以指定例外:

raise ArgumentError, "Illegal arguments!"

另外,可以将异常实例传递给raise方法:

raise ArgumentError.new("Illegal arguments!")

在提出一个具有多个参数的构造函数的自定义异常类的实例时,最后一个结构很有用:

class ParseError < Exception
 def initialize(input, line, pos)
 super "Could not parse '#{input}' at line #{line}, position #{pos}"
 end
end
raise ParseError.new("Foo", 3, 9)

rescue条款处理例外。这样的子句可以捕获从StandardError继承的异常。其他流量控制关键字在处理else时可以使用并ensure

begin
 # do something
rescue
 # handle exception
else
 # do this if no exception was raised
ensure
 # do this whether or not an exception was raised
end

试图通过简单的救援条款捕获所有例外是一个普遍的错误。要捕获所有例外,必须写下:

begin
 # do something
rescue Exception
 # Exception handling code here.
 # Don't write only "rescue"; that only catches StandardError, a subclass of Exception.
end

或捕获特定的例外:

begin
 # do something
rescue RuntimeError
 # handle only RuntimeError and its subclasses
end

还可以指定为处理程序条款提供异常对象:

begin
 # do something
rescue RuntimeError => e
 # handling, possibly involving e, such as "puts e.to_s"
end

另外,最新的例外存储在Magic Global $!

也可以抓到几个例外:

begin
 # do something
rescue RuntimeError, Timeout::Error => e
 # handling, possibly involving e
end

元图

Ruby代码可以在运行时修改其自身结构的各个方面,这些方面将以更僵化的语言(例如类和方法定义)固定。这种元编程可用于编写更多简洁的代码并有效地扩展语言。

例如,以下Ruby代码基于颜色列表为内置String类生成新方法。该方法将字符串的内容包装在带有相应颜色样式的HTML标签上。

COLORS = { black: "000",
 red: "f00",
 green: "0f0",
 yellow: "ff0",
 blue: "00f",
 magenta: "f0f",
 cyan: "0ff",
 white: "fff" }
class String
 COLORS.each do |color,code|
 define_method "in_#{color}" do
 "<span style=\"color: ##{code}\">#{self}</span>"
 end
 end
end

然后可以像这样使用生成的方法:

"Hello, World!".in_blue
 => "<span style=\"color: #00f\">Hello, World!</span>"

要以许多其他语言实现等效词,程序员必须单独编写每种方法( in_blackin_redin_green等)。

Ruby Metagrogricming的其他一些可能用途包括:

  • 拦截和修改方法调用
  • 实施新的继承模型
  • 从参数动态生成类
  • 自动对象序列化
  • 互动帮助和调试