Ruby程序语言入门

原文:http://ihower.tw/rails3/ruby.html


Ruby程序语言入门


Actually, I’m trying to make Ruby natural, not simple. Ruby is simple in appearance, but is very complex inside, just like our human body. - Matz, Ruby 發明人

Ruby 是个美丽、灵巧而且方便又实用的程序语言,而 Ruby on Rails 正是 Ruby 程序语言爆发成长的催化剂。在了解 Ruby on Rails 的程序之前,学习 Ruby程序语言 是最重要的基础功課之一,我们在這一章节将快速带过一些基本的語法,网络上也有 Ruby Taiwan社区 所翻译的文章可以一读:

免费的英文资源就更多了,请参考附录  生态圈


各种Ruby实现

除了用C語言实现的官方版Ruby(又叫做CRubyMRI, Matz’s Ruby Interpreter)http://ruby-lang.org/,也有其他不同实现的Ruby环境。這些实现都以RubySpec作为其语法的标准:

  • JRuby http://jruby.org/是由Java实现的Ruby,运行在高效能、支援系統執行緒及有非常多函数库的Java虚拟机(JVM)上。JRuby算是目前Ruby要开发跨平台(WindowsMacLinux)桌面软件最好的选择。
  • MacRuby http://www.macruby.org/是由Objective-C实现的 Ruby,運作在Mac OS X操作系统上。强项在于开发Mac原生桌面软件,未來有机会可以运行在iOS上,甚至成为Apple官方程序语言之一。
  • Rubinuis http://rubini.us/是用C++RubyLLVM编译器技术实现的Ruby VM,可以在Mac OS XDebian/UbuntuFreeBSDWindows上执行。LLVM可以說是当代代最重要的编译器架构,拥有各种编译器最佳优化技术。给給Ruby带來多少效能改善幅度,值得关注。
  • Ruby Enterprise Edition(简称REEhttp://www.rubyenterpriseedition.com/是个非常流行的CRuby 1.8分支版本,特別适合用來搭配Ruby on RailsPhusion Passenger使用,相对于Ruby 1.8.7可以增加效能和节省内存使用量。
  • IronRuby http://ironruby.net/是由.NET实现Ruby,使用了Dynamic Language Runtime技术

IRB(Interactive Ruby)

IRB是一個交互的Ruby环境,可以让我们练习,做些简单的实验。请输入irb就會进入交互模式:

$ irb
irb: Interactive Ruby
irb(main):001:0>
irb(main):001:0> 1 + 1
=> 2

irb之中,每行執行完Ruby都會自動幫你puts輸出結果。

不过,一旦程序稍微复杂一点,还是打开文字文本编辑器吧。让我们编辑一个文件hello.rbRuby脚本后缀的默认是.rb,內容如下:

puts "Hello, World!!"

保存文件后,输入:

$ ruby hello.rb

就会执行这个脚本,它会在屏幕上输出出Hello, World!!

程序语言分类

根据需不需要事先声明变量类型,我們可以分類出静态类型(Static typing)与动态类型(Dynamic typing)程式語言,前者例如JavaCC++,後者例如RubyPerlPythonPHP。根据会不会隐形自动转换类型,又可以分類出强类型(Strong typing)與弱类型(Weak typing),前者例如RubyPerlPythonJava,后者例如PHPCC++是弱分型(注:原文就是这样,不过貌似,java,C,C++应该是强类型语言)。让我们举个例子吧:

/* PHP */
$i = 1;
echo "Value is " + $i
# 1

/* C */
int a = 5;
float b = a;

以上的PHPC会地自动转换类型,但是以下的Ruby程式会检查型別不相配而发生錯誤,这一点PHP过來的朋友要特別注意。

# Ruby
i=1
puts "Value is " + i

#TypeError: can't convert Fixnum into String
#	from (irb):2:in `+'
#	from (irb):2

另外,通常动态类型的程序语言多半也是解释型(interpreted)程序語言,也就是不需要事先编译,通过解释器(interpreter)执行即可,当然Ruby也不例外。相對的,编译(compiled)语言則是事先编译成执行文件才行执行。总结以上,Ruby是个动态强类型的解释程序语言。

整数Integer

任何整数都是Fixnum对象

5
-205
9999999999
0

完整的Fixnum API请参考Ruby doc文件。

浮点数Float

中间带有点号的就是浮点数Float对象

54.321
0.001
-12.312
0.0

浮点数四则运算范例如下:

puts 1.0 + 2.0
puts 2.0 * 3.0
puts 5.0 - 8.0
puts 9.0 / 2.0

# 3.0
# 6.0
# -3.0
# 4.5

要注意的是,整数四则运算結果,也會是整数:

puts 1 + 2
puts 2 * 3
puts 5 - 8
puts 9 / 2

# 3
# 6
# -3
# 4

以下是一個更复杂的四则运算例子:

puts 5 * (12-8) + -15
puts 98 + (59872 / (13*8)) * -52

完整的Float API请参Ruby doc文件。

字串String

使用单引号或双引号括起来的就是String对象

puts 'Hello, world!'
puts ''
puts 'Good-bye.'

字串相加可以使用加号,要注意的是字串不能直接跟数字相加,会发生异常错误:

puts 'I like ' + 'apple pie.'
puts 'You\'re smart!'

puts '12' + 12 
#<TypeError: can't convert Fixnum into String>

更多字串方法示范:

var1 = 'stop'
var2 = 'foobar'
var3 = "aAbBcC"

puts var1.reverse # 'pots'
puts var2.length # 6
puts var3.upcase
puts var3.downcase

完整的String API请参Ruby doc文件。

Ruby完全地物件導向

你可能已经注意到,在Ruby里每样东西都是对象,包括字串和数字。所有的方法都是对对象的调用,你不會看到全局函式,例如PHPstrlen("test")用法,在Ruby中是"test".length

# 输出 "UPPER"
puts "upper".upcase

# 输出 -5 的绝对值
puts -5.abs

# 输出 Fixnum 类别
puts 99.class

# 输出 "Ruby Rocks!" 五次
5.times do
  puts "Ruby Rocks!"
end

局部变量Local Variable

局部变量使用小写开头,习惯单字之间以下划线_來分隔。示例如下:

composer = 'Mozart'
puts composer + ' was "da bomb", in his day.'

my_composer = 'Beethoven'
puts 'But I prefer ' + my_composer + ', personally.'

类型转换Conversions

刚刚提到数字和字符串不能直接相加,你必須使用to_s(转成字串)、to_i(转成整数)或to_f(转成浮点数)來手动转换,示例如下:

var1 = 2
var2 = '5'

puts var1.to_s + var2 # 25
puts var1 + var2.to_i # 7

puts 9.to_f / 2 # 4.5

常量Constant

大写开头的为常量,示例如下:

Foo = 1
Foo = 2 # (irb):3: warning: already initialized constant Foo

RUBY_PLATFORM # => "x86_64-darwin10.7.0"
ENV # => { "PATH" => "....", "LC_ALL" => "zh_TW.UTF-8" }

空值nil

表示未设定值、未定义的状态:

nil # nil
nil.class # NilClass

nil.nil? # true
42.nil? # false

nil == nil # true
false == nil # false

注释

Ruby偏好一律使用单行注释:

# this is a comment line
# this is a comment line

多行注释比较少見:

=begin
    This is a comment line
    This is a comment line
=end

字串符号Symbols

Symbol一种唯一且不会变动的识别名称,用冒号开头:

:this_is_a_symbol

为什么不就用字串呢?這是因为使用Symbol可以获得执行上的效率,相同名称的Symbol不会再重复创建对象,示例如下:

puts "foobar".object_id      # 输出 2151854740
puts "foobar".object_id      # 输出 2151830100
puts :foobar.object_id       # 输出 577768
puts :foobar.object_id       # 输出 577768

object_id方法会返回Ruby內部的内存保存的编号,你会发现相同內容的字串,也会是不同的新对象,但是Symbol不会。这种特性让Symbol的主要用途是当做Hash的鍵,一会就会介绍到。

数组Array

使用中括号,索引从0开始。注意到数组中的元素是不限同一类別,想放什么都可以:

a = [ 1, "cat", 3.14 ]

puts a[0] # 输出 1
puts a.size # 输出 3

a[2] = nil
puts a.inspect # 输出 [1, "cat", nil]
a[99] # nil

inspect方法会将对象转成适合給人看的字串

如果读取一个沒有赋值的数组元素,预设值是nil。更多数组方法示例:

colors = ["red", "blue"]

colors.push("black")
colors << "white"
puts colors.join(", ") # red, blue, black, white

colors.pop
puts colors.last #black

完整的Array API请参考Ruby doc文件。

Hash

Hash是一种鍵值对(Key-Value)的数据结构,虽然你可以使用任何对象当作Key,但是通常我们使用Symbol当Key。例如:

config = { :foo => 123, :bar => 456 }
puts config["foo"] # 输出 123
config["nothing"] # 是 nil

Ruby 1.9支持新的语法,比較简洁:

config = { foo: 123, bar: 456 } # 等同于 { :foo => 123, :bar => 456 }

如果读取一个不存在的值,例如上述范例的nothing,返回值是nil

完整的Hash API請參考Ruby doc文件。

注意到Ruby 1.8Hash不像Array,你无法预测里面的鍵值顺序。例如输入{ :a => 1, :b=> 2, :c => 3 }.merge( :d => 4 )會输出成{:a=>1, :d=>4, :b=>2, :c=>3}。這個问题在Ruby 1.9获得修正,会依照你給的顺序输出成{:a=>1, :b=>2, :c=>3, :d=>4}

如果你想确保无论Ruby 1.81.9都要用有顺序的Hash,在Rails环境中可以用ActiveSupport::OrderedHash

流程控制Flow Control

让我们來看看一些流程控制:

比较方法
puts 1 > 2 # 大于
puts 1 < 2 # 小于
puts 5 >= 5 # 大于等于
puts 5 <= 4 # 小于等于
puts 1 == 1 # 等于
puts 2 != 1 # 不等于

puts ( 2 > 1 ) && ( 2 > 3 ) # 和
puts ( 2 > 1 ) || ( 2 > 3 ) # 或
控制结构If

else if写成elsif

total = 26000

if total > 100000
  puts "large account"
elsif total > 25000
  puts "medium account"
else
  puts "small account"
end

另外如果要执行的if程式只有一行,可以將if放到行末即可:

puts "greater than ten" if total > 10
三元运算子

三元运算子expression ? true_expresion : false_expression可以让我們处理简易的if else条件,例如以下的程式:

x = 3
if x > 3
  y = "foo"
else
  y = "bar"
end

改用三元運算子之后,可以缩减程序行数:

x = 3
y = ( x > 3 )? "foo" : "bar"
控制结构Case
case name
    when "John"
      puts "Howdy John!"
    when "Ryan"
      puts "Whatz up Ryan!"
    else
      puts "Hi #{name}!"
end
循环 while, loop, until, next and break

while用法范例:

i=0
while ( i < 10 )
  i += 1
  next if i % 2 == 0 #跳過偶数
end

until用法示例:

i = 0
i += 1 until i > 10
puts i
# 輸出 11

loop用法示例:

i = 0
loop do 
  i += 1
  break if i > 10 # 中断循环
end

不过你很快就会发现写Ruby很少用到whileuntilloop,我們会使用迭代器。

真或假

记住,只有falsenil是假,其他都為真。

puts "not execute" if nil
puts "not execute" if false

puts "execute" if true # 輸出 execute
puts "execute" if “” # 輸出 execute (和JavaScript不同)
puts "execute" if 0 # 輸出 execute (和C不同)
puts "execute" if 1 # 輸出 execute
puts "execute" if "foo" # 輸出 execute
puts "execute" if Array.new # 輸出 execute

正则表达式Regular Expressions

于Perl类似的语法,使用=~

# 抓出手手机号码 
phone = "123-456-7890"
if phone =~ /(\d{3})-(\d{3})-(\d{4})/
  ext  = $1
  city = $2
  num  = $3
end

方法定义Methods

使用def开头end結尾來定义一个方法:

def say_hello(name)
  result = "Hi, " + name
  return result
end

puts say_hello('ihower')
# 輸出 Hi, ihower

方法中的return是可以省略的,Ruby就会返回最后一行运算的值。上述方法可以改写成:

def say_hello(name)
  "Hi, " + name
end

调用方法时,括号也是可以省略的,例如:

say_hello 'ihower'

不过,除了一些方法惯例不加之外(例如putsRails中的redirect_torender方法),绝大部分的情況加上括号比较无疑虑。

?!的惯例

方法名称可以用?!结尾,前者表示会回传Boolean值,后者暗示会有某种副作用(side-effect)。示例如下:

array=[2,1,3]

array.empty? # false
array.sort # [1,2,3]

array.inspect # [2,1,3]

array.sort! # [1,2,3]
array.inspect # [1,2,3]

类Classes

Ruby的类其实也是一种常数,所以也是大写开头,使用new方法可以建立出对象,例如之前所学的字串、数组和散列,也可以用以下方式建立:

color_string = String.new 
color_string = "" # 等同

color_array = Array.new
color_array = [] # 等同

color_hash = Hash.new
color_hash = {} # 等同

time  = Time.new # 內建的時間類別
puts time

來看看如何自定类:

class Person # 大写开头的常数

    def initialize(name) # 构造函数
        @name = name # 对象变量
    end

    def say(word)
        puts "#{word}, #{@name}" # 字串相加
    end

end

p1 = Person.new("ihower")
p2 = Person.new("ihover")

p1.say("Hello") # 輸出 Hello, ihower
p2.say("Hello") # 輸出 Hello, ihover

注意到引号里的字串可以使用#{var}來做字串嵌入,这比使用+相加字串可以更有效率。

除了对象方法与对象变量,Ruby也有属于类的方法和变量:

class Person
    
    @@name = “ihower” # 类变量
    
    def self.say # 类方法
        puts @@name
    end
end

Person.say # 輸出 ihower
数据封装

所有的对象变量(@开头)、类变量(@@开头),都是封裝在类內部的,类別外无法存取:

class Person
    def initialize(name)
        @name = name
    end
end

p = Person.new('ihower')
p.name 						# 出現 NoMethodError 錯誤
p.name = 'peny'  		    # 出現 NoMethodError 錯誤

为了可以存取到@name,我们必须定义方法:

class Person

   def initialize(name)
    @name = name
   end

   def name
     @name
   end

   def name=(name)
     @name = name
   end
end

p = Person.new('ihower')
p.name 
=> "ihower"
p.name="peny"
=> "peny"

Class定义范围內也可以执行程式

跟其他程式語言不太一樣,Ruby的类内部也可以執行程序,例如以下:

class Demo
    puts "foobar"
end

当你载入这个类的时候,就会执行puts "foobar"输出foobar。会放在这里执行,主要的用途是來做Meta-programming。例如,上述定义对象变量的存取方法实在太常见了,因此Ruby提供了attr_accessorattr_writerattr_reader类方法可以直接定义這些方法。上述的程式可以改写成:

class Person
  attr_accessor :name
end

p = Person.new('ihower')
p.name 
=> "ihower"
p.name="peny"
=> "peny"    

这里的attr_accessor其实就是一個类的方法。

方法封装

类中的方法默认是public的,声明为privateprotected的话,该行以下的方法就会套用:

class MyClass
    
    def public_method
    end
    
    private
    
    def private_method_1
    end

    def private_method_2
    end
    
    protected

    def protected_method
    end
end

Rubyprivateprotected定义和其他程序语言不同,都是可以在整个继承体系內调用。兩著差別在于private只有不指定接受者(receiver)時才可以调用,你甚至不能打self.private_method_1,默认一定就是self当private方法的接受者。而protected方法除了可以被一个类或子类的对象调用,也可以让另一个相同类的对象來当做接受者。

在面向对象的术语中,object.call_method的意思是object收到执行call_method的指令,也就是objectcall_method方法的接受者(receiver)。因此,你甚至可以改写成object.__send__(:call_method)

Class 继承

Ruby使用小于<符号代表类别继承:

class Pet
    attr_accessor :name, :age
    
    def say(word)
        puts "Say: #{word}"
    end
end

class Cat < Pet
    def say(word)
        puts "Meow~"
        super
    end
end

class Dog < Pet
    def say(word, person)
        puts "Bark at #{person}!"
        super(word)
    end
end

Cat.new.say("Hi")
Dog.new.say("Hi", "ihower")

输出

Meow~
Say: Hi
Bark at ihower!
Say: Hi

這个范例中,CatDog子类覆盖了Pet say方法,其中的super是用來调用被覆盖掉的Pet say方法。另外,沒有括号的super和有括號的super()是有差異的,前者Ruby会自动将所有参数都代带进去來调用父类的方法,後者則是自己指定参数。此例中如果Dog say裡只寫super,則会发生wrong number of arguments的错误,這是因为Ruby会传say("Hi", "ihower")给Pet say而发生错误。

循环遍历与迭代器Iterator

不同於while循环用法,Ruby习惯使用迭代器(Iterator)來遍历循环,例如each是一個数组的方法,它會遍历其中的元素,其中的do .... endeach方法的參數,称为匿名方法(code block)。范例程序如下:

languages = ['Ruby', 'Javascript', 'Perl']
languages.each do |lang|
    puts "I love #{lang}!"
end
# I Love Ruby!
# I Love Javascript!
# I Love Perl!

其中两个竖线|中间lang被称作Block variable区域变量,每次迭代都会被设定成不同元素。其他迭代器范例如:

# 反覆三次
3.times do
    puts 'Good Job!'
end
# Good Job!
# Good Job!
# Good Job!

# 從一數到九
1.upto(9) { |x| puts x }

# 多一個索引临时变量
languages = ['Ruby', 'Javascript', 'Perl']
languages.each_with_index do |lang, i|
    puts "#{i}, I love #{lang}!"
end
# 0, I Love Ruby!
# 1, I Love Javascript!
# 2, I Love Perl!

(Code block)的形式除了do ... end,也可以改用大括号。通常单行会用大括號,多行會用do ... end的形式。

3.times { puts "Hello" }

透过迭代器,我們就比較少用到whileuntilfor等循环語法了。

其他迭代方式范例
# 迭代並造出另一個陣列
a = [ "a", "b", "c", "d" ]
b = a.map {|x| x + "!" }
puts b.inspect

# 結果是 ["a!", "b!", "c!", "d!"]

# 找出符合條件的值
b = [1,2,3].find_all{ |x| x % 2 == 0 }
b.inspect
# 結果是 [2]

# 迭代并根据条件刪除
a = [51, 101, 256]
a.delete_if {|x| x >= 100 }
# 結果是 [51]

# 自定义排序
[2,1,3].sort! { |a, b| b <=> a }
# 結果是 [3, 2, 1]

# 計算总和
(5..10).inject {|sum, n| sum + n }

# 找出最长字串find the longest word
longest = ["cat", "sheep", "bear"].inject do |memo,word|
    ( memo.length > word.length )? memo : word
end

<=>是比较运算符,当两个数字相等返回0,第一個數字較大時返回1,反之返回-1

仅执行一次调用

除了迭代,也有Code block只會執行一次,例如用來打开文件。往常我們在文件处理完毕之后,会使用close方法:

file = File.new("testfile", "r")
# ...处理文件
file.close

改用Code block语法之后Ruby可以在Code block结束后自动关闭文件

File.open("testfile", "r") do |file|
    # ...处理文件
end
# 文件自动关闭

Code block的这个特性不只让你少打close方法,更可以避免你忘记(不然就语法错误了),也有视觉上缩排的好处。

Yield

在方法中使用yield可以执行Code block参数:

# 定义方法
def call_block
    puts "Start"
    yield
    yield
    puts "End"
end

call_block { puts "Blocks are cool!" }
# 輸出
# "Start"
# "Blocks are cool!"
# "Blocks are cool!"
# "End"
帶有參數的Code block
def call_block
    yield(1)
    yield(2)
    yield(3)
end

call_block { |i|
    puts "#{i}: Blocks are cool!"
}
# 輸出
# "1: Blocks are cool!"
# "2: Blocks are cool!"
# "3: Blocks are cool!"
Proc object

可以將Code block明確轉成一個變數:

def call_block(&block)
    block.call(1)
    block.call(2)
    block.call(3)
end

call_block { |i| puts "#{i}: Blocks are cool!" }

# 或是先宣告出 proc object
proc_1 = Proc.new { |i| puts "#{i}: Blocks are cool!" }
proc_2 = lambda { |i| puts "#{i}: Blocks are cool!" }

call_block(&proc_1)
call_block(&proc_2)

# 輸出
# "1: Blocks are cool!"
# "2: Blocks are cool!"
# "3: Blocks are cool!"

传递不定参数

def my_sum(*val)
    val.inject { |sum, v| sum + v }
end

puts my_sum(1,2,3,4) # val 变量就是 [1,2,3,4]
# 輸出 10

其中my_sum方法中的val是一个包含所有参数的数组。

参数尾巴的Hash可以省略{ }

def my_print(a, b, options)
    puts a
    puts b
    puts options[:x]
    puts options[:y]
    puts options[:z]
end

my_print("A", "B", { :x => 123, :z => 456 } )
my_print("A", "B", :x => 123, :z => 456) # 結果相同    
# 輸出 A
# 輸出 B
# 輸出 123
# 輸出 nil
# 輸出 456

异常处理

使用rescue可以將异常救回來:

begin
    puts 10 / 0 # 這会抛出 ZeroDivisionError 的异常
rescue => e
    puts e.class # 如果發生例外會執行 rescue 這一段
ensure
    # 无论有没有发生异常,ensure 這一段都一定会执行
end
# 輸出 ZeroDivisionError

使用raise可以手动触发异常错误:

raise "Not works!!"
# 丟出一個 RuntimeError

# 自行自定例外物件
class MyException < RuntimeError
end

raise MyException

Module

ModuleRuby一个非常好用的功能,它跟Class类非常相似,你會在里面定义方法。只是你不能用new來建立它。它的第一個用途是可以当做Namespace來放一些工具方法:

module MyUtil

    def self.foobar
        puts "foobar"
    end
end

MyUtil.foobar
# 輸出 foobar

另一個更重要的功能是Mixins,可以將一個Module混入类之中,这样这个类就会拥有此Module的方法。这回让我们拆成两个文件,debug.rbfoobar.rb,然後在foobar.rb中用require來引用debug.rb

首先是debug.rb

module Debug
    def who_am_i?
        puts "#{self.class.name}: #{self.inspect}"
    end
end

然後是foobar.rb

require "./debug"
class Foo
    include Debug # 這个动作叫做 Mixin(注:糅合)
end

class Bar
    include Debug
end

f = Foo.new
b = Bar.new
f.who_am_i? # 輸出 Foo: #<Foo:0x00000102829170>
b.who_am_i? # 輸出 Bar: #<Bar:0x00000102825b88>

Ruby使用Module來解決多重繼承的問題,不同類別之間但是擁有相同的方法,就可以改放在Module裡面,然後include它即可。

Metaprogramming用程式寫程式

Metaprogramming是很進階的技巧,這裡示範define_method方法可以動態定義方法:

class Dragon
    define_method(:foo) { puts "bar" }

    ['a','b','c','d','e','f'].each do |x|
        define_method(x) { puts x }
    end
end

dragon = Dragon.new
dragon.foo # 輸出 "bar"
dragon.a # 輸出 "a"
dragon.f # 輸出 "f"
Introspection反射機制

Ruby擁有許多反射方法,可以動態知道物件的資訊:

# 這個物件有什麼方法
Object.methods
=> ["send", "name", "class_eval", "object_id", "new", "singleton_methods", ...]

# 這個物件有這個方法嗎?
Object.respond_to? :name
=> true

其他常見慣例

result ||= a

如果resultnilfalse的話,將a指派給result。以上這段程式等同於

result || ( result = a )

參考投影片


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值