cv.cm

编码 · 编程语言

Ruby 入门教程(零基础)

阅读约 7 分钟

简短回答

Ruby 是一门动态的面向对象脚本语言,读写起来令人愉快。把 puts "Hello, world" 保存为 hello.rb,然后运行 ruby hello.rb。Ruby 中一切皆对象,连数字和 nil 也不例外。核心概念包括:用 def 定义的方法、数组和哈希、传给 each 和 map 等迭代器的块、类和模块,以及用 begin/rescue 处理的异常。

打开正则测试

Ruby 是什么,用来做什么

Ruby 由日本的松本行弘(Yukihiro Matsumoto,人称“Matz”)创建,1995 年首次发布,目标是让程序员快乐。它因 Ruby on Rails 而广为人知,GitHub 和 Shopify 等网站背后就是这个 Web 框架。Ruby 用于 Web 应用、脚本与自动化,以及 Homebrew 和 CocoaPods 等开发者工具。它的标准实现是 CRuby(也称 MRI)。

安装 Ruby 并运行 Hello, world

macOS 和许多 Linux 系统自带 Ruby,但版本往往很旧;请用 rbenv、asdf 或 mise 等版本管理器安装当前的 Ruby 3,在 Windows 上则使用 RubyInstaller。用 ruby -v 检查。

puts "Hello, world"
ruby hello.rb
# Hello, world
irb          # interactive Ruby shell

puts 输出一个值并换行。在含义清楚时,方法参数两边的圆括号可以省略。

变量与基本类型

变量无需声明,可以保存任意对象。按惯例,名称使用 snake_case。常量以大写字母开头。

类示例说明
Integer42, 2**100任意大小
Float3.14双精度
String"hi #{name}", 'hi'可变;双引号会插值
Symbol:name不可变的标识符,常用作哈希的键
true, falsetrue只有 false 和 nil 为假
NilClassnil没有值
Array, Hash[1, 2], {a: 1}内置集合
name = "Ada"
count = 7
puts "#{name} has #{count} items"   # Ada has 7 items
puts count / 2, count / 2.0         # 3, then 3.5
puts "12".to_i + 1                  # 13
puts 5.class, nil.to_a.inspect      # Integer, then []
puts 0 ? "0 is truthy" : "falsy"    # 0 is truthy

流程控制

score = 72
if score >= 90
  puts "A"
elsif score >= 70
  puts "B"          # B
else
  puts "C"
end

puts "pass" unless score < 50    # statement modifier

3.times { |i| print i, " " }     # 0 1 2
puts

n = 10
n /= 2 while n > 1
puts n                           # 1

kind = case "SAT"
       when "SAT", "SUN" then "weekend"
       else "weekday"
       end
puts kind                        # weekend

代码块以 end 结束,而不是花括号。Ruby 代码通常用 2 个空格缩进。

方法

方法返回最后一个表达式的值,所以通常不需要写 return。按惯例,返回布尔值的方法以 ? 结尾,原地修改接收者的方法以 ! 结尾。

def greet(name, greeting: "Hello")
  "#{greeting}, #{name}!"
end

def total(*nums)
  nums.sum
end

puts greet("Lin")                   # Hello, Lin!
puts greet("Lin", greeting: "Hi")   # Hi, Lin!
puts total(1, 2, 3)                 # 6
puts 4.even?, "abc".upcase          # true, then ABC

s = "hello"
s.upcase!          # modifies s in place
puts s             # HELLO

数组与哈希

nums = [3, 1, 2]
nums << 5
p nums.sort, nums.first, nums.last   # [1, 2, 3, 5] then 3 then 5
p nums.sort[1..2]                    # [2, 3]

ages = { "ann" => 31, "bob" => 25 }
ages["cy"] = 40
ages.each { |person, age| puts "#{person} is #{age}" }
p ages.fetch("zed", 0)               # 0

user = { name: "Ann", city: "Oslo" } # symbol keys
p user[:name]                        # "Ann"

p 输出值的 inspect 形式,它会显示引号和方括号,便于调试。

标志性特性:块与迭代器

块是传给方法的一段代码,单行时写作 { ... },跨行时写作 do ... end。Ruby 的迭代主要通过块完成,而不是 for 循环,Enumerable 的方法让数据转换写起来很简短。

nums = [1, 2, 3, 4, 5]
p nums.map { |n| n * n }               # [1, 4, 9, 16, 25]
p nums.select(&:even?)                 # [2, 4]
p nums.reduce(0) { |sum, n| sum + n }  # 15
p nums.each_slice(2).to_a              # [[1, 2], [3, 4], [5]]
p %w[pear fig apple].sort_by(&:length) # ["fig", "pear", "apple"]

def twice
  yield
  yield
end
twice { puts "hi" }   # prints hi two times

&:even? 是 { |n| n.even? } 的简写。你自己的方法可以接收块,并用 yield 调用它。

类与模块

module Greeter
  def hello
    "Hello from #{name}"
  end
end

class Account
  include Greeter          # mix in module methods
  attr_reader :name, :balance

  def initialize(name)
    @name = name           # @ marks an instance variable
    @balance = 0
  end

  def deposit(amount)
    raise ArgumentError, "amount must be positive" unless amount.positive?
    @balance += amount
    self
  end

  def to_s = "#{name}: #{balance}"
end

acc = Account.new("Ann")
acc.deposit(30).deposit(20)
puts acc        # Ann: 50
puts acc.hello  # Hello from Ann

attr_reader 会生成 getter 方法。模块把方法组织在一起,可以混入许多类,Ruby 用它代替多重继承。单行的 def to_s = ... 写法需要 Ruby 3.0 或更高版本。

错误处理

def parse_age(text)
  age = Integer(text)       # raises ArgumentError on bad input
  raise ArgumentError, "negative age" if age.negative?
  age
end

begin
  puts parse_age("41")      # 41
  puts parse_age("abc")
rescue ArgumentError => e
  puts "error: #{e.message}"  # error: invalid value for Integer(): "abc"
ensure
  puts "done"
end

rescue 捕获异常,ensure 总会执行。在方法内部,可以直接写 rescue 而不必写 begin。

一个完整的小程序:词频统计

path = ARGV.fetch(0) { abort "usage: ruby words.rb FILE" }

begin
  text = File.read(path).downcase
rescue Errno::ENOENT
  abort "file not found: #{path}"
end

counts = text.scan(/[a-z']+/).tally
counts.max_by(5) { |_word, n| n }.each do |word, n|
  puts format("%5d  %s", n, word)
end

用 ruby words.rb notes.txt 运行。tally 把相同的元素计数到一个哈希中,max_by(5) 返回计数最高的五个条目。

新手常见错误

  • 忘记用 end 结束 if、def、class 或 do 块。
  • 混用字符串键和符号键:h["name"] 与 h[:name] 是不同的键。
  • 使用单引号却期望 #{} 插值生效。
  • 调用了 ! 方法,却没意识到它修改了原对象。
  • 使用系统自带的旧版 Ruby,而不是用版本管理器安装的当前版本。

接下来学什么

从 ruby-lang.org 的文档页面开始,其中包括快速入门“Ruby in Twenty Minutes”,核心类库和标准库的参考请查阅 docs.ruby-lang.org。学习用 Bundler 和 gem 管理依赖,用 RSpec 或 Minitest 写测试;如果想开发 Web 应用,再学习 Ruby on Rails。

常见问题

Ruby 现在还有人用吗?

有。Ruby on Rails 支撑着许多成熟的 Web 产品,包括 GitHub 和 Shopify,Ruby 在脚本和开发者工具中也很常见。新项目中它不如 Python 或 JavaScript 常见,但仍在积极开发。

Ruby 和 Ruby on Rails 有什么区别?

Ruby 是编程语言。Rails 是用 Ruby 编写的 Web 框架,提供路由、数据库访问和页面模板。

Ruby 中的符号(symbol)是什么?

:name 这样的符号是不可变的唯一标识符。同一个符号始终是同一个对象,因此用作哈希的键和选项名称时很高效。

初学者学 Ruby 还是 Python?

两者都是语法易读的优秀入门语言。Python 的应用范围更广,尤其在数据科学领域;Ruby 表达力很强,搭配 Rails 做 Web 开发很有优势。

全部编码教程