← 返回目录

第二章:基础语法

变量、类型、集合与控制流

1. 变量与类型

Ruby 是动态类型语言,变量无需声明类型。Ruby 通过命名约定区分变量的作用域:

name = "Alice"           # 局部变量(小写字母或下划线开头)
@age = 30                # 实例变量(属于当前对象)
@@count = 0              # 类变量(属于类及其所有实例)
$debug = true            # 全局变量(整个程序可见,慎用)
MAX_SIZE = 100           # 常量(大写字母开头,修改会警告)

puts name.class          # => String
puts @age.class          # => Integer
puts $debug.class        # => TrueClass
puts MAX_SIZE.class      # => Integer

Ruby 的基本类型都是对象,没有原始类型(primitive)的概念:

42.class              # => Integer
3.14.class            # => Float
"hello".class         # => String
:name.class           # => Symbol
true.class            # => TrueClass
false.class           # => FalseClass
nil.class             # => NilClass

42.even?              # => true(数字也有方法)
-5.abs                # => 5
nil.nil?              # => true
"hello".is_a?(String) # => true

一切皆对象:在 Ruby 中,42niltrue 都是对象实例。42.times { ... } 这种写法完全合法。这是 Ruby 与 Java/PHP 最根本的区别之一。

🔄 变量声明对比

语言 变量声明
Ruby name = "Alice" — 直接赋值,无关键字
Python name = "Alice" — 与 Ruby 相同
PHP $name = "Alice"; — 需要 $ 前缀和分号
Java String name = "Alice"; — 需要类型声明

2. 字符串

Ruby 的字符串功能丰富,支持插值、heredoc 和大量内置方法。

单引号 vs 双引号

name = "Ruby"

puts "Hello, #{name}!"     # => Hello, Ruby!(双引号支持插值)
puts 'Hello, #{name}!'     # => Hello, #{name}!(单引号原样输出)
puts "2 + 3 = #{2 + 3}"    # => 2 + 3 = 5(插值内可以写任意表达式)
puts "换行符:\n第二行"      # 双引号支持转义字符
puts '不转义:\n 原样输出'   # 单引号不处理转义(\' 和 \\ 除外)

Heredoc 多行字符串

html = <<~HTML
  <div class="card">
    <h1>#{name}</h1>
    <p>版本 3.3</p>
  </div>
HTML
# <<~ 会自动去除公共缩进

常用字符串方法

s = "  Hello, Ruby World!  "

s.length            # => 23
s.strip             # => "Hello, Ruby World!"
s.upcase            # => "  HELLO, RUBY WORLD!  "
s.downcase          # => "  hello, ruby world!  "
s.include?("Ruby")  # => true
s.gsub("Ruby", "Crystal")  # => "  Hello, Crystal World!  "
s.split(", ")       # => ["  Hello", "Ruby World!  "]
s.chars             # => [" ", " ", "H", "e", ...]
s.reverse           # => "  !dlroW ybuR ,olleH  "
s.start_with?("  H") # => true
s.freeze            # 冻结后不可修改

Symbol vs String

:name.class         # => Symbol
"name".class        # => String

:name.object_id == :name.object_id     # => true(同名 Symbol 是同一对象)
"name".object_id == "name".object_id   # => false(每次创建新 String 对象)

# Symbol 适合做 Hash 键和标识符,String 适合处理文本数据

冻结字符串字面量:在文件头部添加 # frozen_string_literal: true 魔法注释,所有字符串字面量自动冻结为不可变对象,减少内存分配,提升性能。这是 Ruby 社区的推荐做法。

3. 数组 (Array)

Ruby 数组是有序、可变长的集合,元素类型可以混合。

创建数组

a = [1, "two", 3.0, :four, nil]
b = Array.new(3, 0)        # => [0, 0, 0]
c = Array.new(5) { |i| i * 2 }  # => [0, 2, 4, 6, 8]
words = %w[apple banana cherry]  # => ["apple", "banana", "cherry"]
symbols = %i[get post put delete] # => [:get, :post, :put, :delete]

常用方法

arr = [3, 1, 4, 1, 5, 9, 2, 6]

arr.push(7)         # => [3, 1, 4, 1, 5, 9, 2, 6, 7]
arr << 8            # => [..., 7, 8](<< 等同于 push)
arr.pop             # => 8,移除并返回末尾元素
arr.shift           # => 3,移除并返回首元素
arr.unshift(0)      # 在头部插入

arr = [3, 1, 4, 1, 5, 9, 2, 6]
arr.sort            # => [1, 1, 2, 3, 4, 5, 6, 9]
arr.uniq            # => [3, 1, 4, 5, 9, 2, 6]
arr.flatten         # 展平嵌套数组
arr.compact         # 移除所有 nil 元素
arr.sample          # 随机取一个元素
arr.count           # => 8

函数式操作

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

numbers.map { |n| n ** 2 }           # => [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
numbers.select { |n| n.even? }       # => [2, 4, 6, 8, 10]
numbers.reject { |n| n > 5 }         # => [1, 2, 3, 4, 5]
numbers.reduce(0) { |sum, n| sum + n } # => 55
numbers.reduce(:+)                    # => 55(符号简写)
numbers.each_slice(3).to_a           # => [[1,2,3], [4,5,6], [7,8,9], [10]]
numbers.min                           # => 1
numbers.max                           # => 10
numbers.sum                           # => 55

数组切片

arr = [10, 20, 30, 40, 50]

arr[0]        # => 10
arr[-1]       # => 50(负索引从末尾计算)
arr[1..3]     # => [20, 30, 40](包含末尾)
arr[1...3]    # => [20, 30](不包含末尾)
arr[1, 2]     # => [20, 30](从索引1取2个元素)
arr.first(3)  # => [10, 20, 30]
arr.last(2)   # => [40, 50]

4. 哈希 (Hash)

Hash 是 Ruby 的键值对集合,类似 Python 的 dict、Java 的 HashMap、PHP 的关联数组。

# 旧语法(火箭操作符)
old_style = { "name" => "Ruby", "year" => 1995 }

# 新语法(Symbol 键推荐写法,Ruby 1.9+)
person = { name: "Matz", age: 69, lang: "Ruby" }

# 创建带默认值的 Hash
counter = Hash.new(0)
counter[:apples] += 1   # 不存在的键返回默认值 0,而非 nil

常用方法

h = { name: "Ruby", version: 3.3, typed: false }

h[:name]                  # => "Ruby"
h.fetch(:name)            # => "Ruby"(键不存在时抛异常)
h.fetch(:missing, "N/A")  # => "N/A"(提供默认值)
h.keys                    # => [:name, :version, :typed]
h.values                  # => ["Ruby", 3.3, false]
h.key?(:version)          # => true
h.merge(year: 1995)       # => { name: "Ruby", version: 3.3, typed: false, year: 1995 }

h.each do |key, value|
  puts "#{key}: #{value}"
end

h.map { |k, v| [k, v.to_s] }.to_h
h.select { |k, v| v.is_a?(Numeric) }  # => { version: 3.3 }
h.transform_values(&:to_s)  # => { name: "Ruby", version: "3.3", typed: "false" }

嵌套访问(dig)

data = {
  user: {
    profile: {
      name: "Alice",
      address: { city: "Tokyo" }
    }
  }
}

data[:user][:profile][:name]              # => "Alice"
data.dig(:user, :profile, :address, :city) # => "Tokyo"
data.dig(:user, :settings, :theme)         # => nil(安全访问,不抛异常)

🔄 字典/映射对比

语言 类型 创建示例
Ruby Hash { name: "v" }
Python dict {"name": "v"}
PHP array(关联) ["name" => "v"]
Java HashMap Map.of("name", "v")

5. 控制流

if / elsif / else

score = 85

if score >= 90
  grade = "A"
elsif score >= 80
  grade = "B"
elsif score >= 70
  grade = "C"
else
  grade = "D"
end

puts grade  # => "B"

# if 是表达式,可以直接赋值
grade = if score >= 90 then "A"
         elsif score >= 80 then "B"
         else "C"
         end

unless 与后缀条件

logged_in = false

unless logged_in
  puts "请先登录"
end

# 后缀条件 —— Ruby 的标志性语法糖
puts "欢迎回来" if logged_in
puts "请先登录" unless logged_in

# 三元运算符
status = logged_in ? "在线" : "离线"

case / when

lang = "Ruby"

case lang
when "Ruby"
  puts "优雅的脚本语言"
when "Python", "PHP"
  puts "流行的脚本语言"
when /^Java/
  puts "以 Java 开头的语言"
else
  puts "其他语言"
end

# case 也是表达式
desc = case lang
       when "Ruby" then "💎 优雅"
       when "Python" then "🐍 简洁"
       else "未知"
       end

模式匹配(Ruby 3.x)

data = { name: "Alice", role: "admin", scores: [95, 88, 72] }

case data
in { name: String => name, role: "admin" }
  puts "管理员:#{name}"
in { name: String => name, scores: [Integer => first, *] }
  puts "#{name} 的第一次成绩:#{first}"
end

# 数组模式匹配
case [1, 2, 3]
in [Integer => a, Integer => b, Integer => c] if a < b
  puts "递增序列:#{a}, #{b}, #{c}"
end

case...in vs case...when:case...when 使用 === 操作符匹配,case...in 是 Ruby 3.0 引入的结构化模式匹配,可以解构嵌套数据结构,功能更强大。

循环

# while
i = 0
while i < 5
  puts i
  i += 1
end

# until(while 的反义词)
j = 10
until j <= 0
  j -= 3
end

# for..in(较少使用,Ruby 社区偏好 each)
for n in [10, 20, 30]
  puts n
end

# Ruby 惯用写法
5.times { |i| puts i }           # 0, 1, 2, 3, 4
1.upto(5) { |i| puts i }        # 1, 2, 3, 4, 5
10.downto(1) { |i| puts i }     # 10, 9, ..., 1
(1..10).step(2) { |i| puts i }  # 1, 3, 5, 7, 9

["a", "b", "c"].each { |ch| puts ch }
["a", "b", "c"].each_with_index { |ch, i| puts "#{i}: #{ch}" }

# 循环控制
[1, 2, 3, 4, 5].each do |n|
  next if n == 3    # 跳过当前迭代(类似 continue)
  break if n == 5   # 终止循环
  puts n
end

6. 异常处理

begin
  result = 10 / 0
rescue ZeroDivisionError => e
  puts "除零错误:#{e.message}"
rescue StandardError => e
  puts "其他错误:#{e.message}"
ensure
  puts "无论如何都会执行(类似 finally)"
end

主动抛出异常

def divide(a, b)
  raise ArgumentError, "除数不能为零" if b.zero?
  a.to_f / b
end

begin
  divide(10, 0)
rescue ArgumentError => e
  puts e.message  # => "除数不能为零"
end

自定义异常

class InsufficientFundsError < StandardError
  attr_reader :amount

  def initialize(amount)
    @amount = amount
    super("余额不足,缺少 #{amount} 元")
  end
end

def withdraw(balance, amount)
  raise InsufficientFundsError.new(amount - balance) if amount > balance
  balance - amount
end

begin
  withdraw(100, 250)
rescue InsufficientFundsError => e
  puts e.message   # => "余额不足,缺少 150 元"
  puts e.amount    # => 150
end

retry 重试机制

attempts = 0

begin
  attempts += 1
  puts "尝试第 #{attempts} 次..."
  raise "网络超时" if attempts < 3
  puts "请求成功"
rescue RuntimeError
  retry if attempts < 3
  puts "重试次数已用完"
end

🔄 异常处理对比

语言 捕获 抛出 清理
Ruby begin/rescue raise ensure
Python try/except raise finally
PHP try/catch throw finally
Java try/catch throw finally

Ruby 独有 retry 关键字,可在 rescue 块中自动重试 begin 块,非常适合网络请求等可重试操作。

7. 本章要点

📌 变量与类型

局部变量无前缀,@ 实例变量,@@ 类变量,$ 全局变量,大写常量。一切皆对象,用 .class 查看类型。

📝 字符串

双引号支持 #{} 插值和转义,单引号原样输出。Symbol 是不可变的标识符,适合做 Hash 键。

📋 数组

mapselectreduce 等函数式方法是 Ruby 数组的核心操作。%w[] 快速创建字符串数组。

🗂️ Hash

{ key: value } 是 Symbol 键简写。dig 安全访问嵌套结构,merge 合并 Hash。

🔀 控制流

unlessif 的反义词,后缀条件是 Ruby 特色。case...in 支持强大的模式匹配。

⚠️ 异常处理

begin/rescue/ensure 结构,raise 抛出异常,retry 重试。自定义异常继承 StandardError

掌握了基础语法后,下一章将学习 方法与面向对象编程 —— Ruby 的方法、Block/Proc/Lambda 和类系统是其最强大的特性。