大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
数据类型
这一课你将能做到
- 正确使用 Integer、Float、String、Symbol、Array、Hash、Range、nil 八种核心类型
- 说清 Symbol 与 String 的本质区别,知道什么时候该用哪个
- 用 Array/Hash 的常用方法完成增删改查,并理解 Ruby 的「一切皆对象」
为什么要学
Ruby 代码 80% 的操作是在字符串、数组、哈希之间倒腾数据。Rails 里从数据库取一行是 Hash 风格的访问,路由参数是 Hash,配置是 Hash——Hash 和 Symbol 不熟,Rails 源码一行都读不下去。
人话:类型是盛放数据的容器。写业务代码就是不停地把数据从一种容器倒进另一种容器,再筛一遍。容器认不全,等于进了厨房不认识锅碗瓢盆。
先建立图像
Ruby 常见类型一览
┌──────────┬───────────────────┬────────────────────┐
│ 类型 │ 字面量示例 │ 一句话定位 │
├──────────┼───────────────────┼────────────────────┤
│ Integer │ 42, -7 │ 任意精度整数 │
│ Float │ 3.14 │ 双精度浮点 │
│ String │ "hello" │ 可变字符序列 │
│ Symbol │ :name │ 不可变「名字标签」 │
│ Boolean │ true / false │ 仅两个实例 │
│ NilClass │ nil │ 「什么都没有」的对象 │
│ Array │ [1, "a", :x] │ 有序、可混合类型 │
│ Hash │ {name: "Tom"} │ 键值对(保持插入序) │
│ Range │ (1..5), ("a".."z")│ 区间 │
└──────────┴───────────────────┴────────────────────┘
关键心智模型:Ruby 中一切值都是对象,包括数字和 nil。1.class #=> Integer,nil.class #=> NilClass。没有 Java 那种基本类型/包装类的分裂。
跟着做
步骤 1:数字与布尔
新建 types.rb:
# 数字:Integer 没有溢出问题,自动变大数
big = 2 ** 100
puts big # 一个 31 位数
puts 7 / 2 # => 3 整数除法
puts 7.0 / 2 # => 3.5 有一个是浮点就是浮点结果
puts 7.divmod(2).inspect # => [3, 1] 商和余数
# 只有 false 和 nil 是「假」,其余全为真——包括 0 和空字符串!
puts 0 ? "0 是真" : "0 是假" # => 0 是真(Java/JS 程序员最易踩)
puts "" ? "空串是真" : "空串是假" # => 空串是真
步骤 2:字符串与插值
name = "小明"
# 双引号支持插值 #{},单引号完全不解析
puts "你好,#{name},明年 #{18 + 1} 岁" # => 你好,小明,明年 19 岁
puts '你好,#{name}' # => 你好,#{name}(原样输出)
s = "ruby"
puts s.upcase # => "RUBY" 不改原串
puts s # => "ruby"
s.upcase! # 带 ! 的方法原地修改
puts s # => "RUBY"
# 常用:分割、去空白、包含判断
"a,b,c".split(",") # => ["a", "b", "c"]
" hi ".strip # => "hi"
"hello world".include?("wor") # => true
步骤 3:Symbol——Ruby 特有
# Symbol 是不可变的「名字」,写一次内存里只存一份
:name.object_id == :name.object_id # => true,同一个对象
"a".object_id == "a".object_id # => false,两个字符串对象
# 典型用途:Hash 的键、方法名、状态标签
status = :pending
user = { name: "Tom", role: :admin } # name: 是 :name 的语法糖
puts user[:name] # => Tom
puts status == "pending" # => false!Symbol 不等于 String
步骤 4:Array 增删改查
arr = [3, 1, 2]
arr << 4 # 尾部追加 => [3,1,2,4]
arr.push(5) # 同上
arr.unshift(0) # 头部插入 => [0,3,1,2,4,5]
arr.pop # 删除尾部 => 5
puts arr.first.inspect # => 0
puts arr.last.inspect # => 4
puts arr[1..2].inspect # => [3, 1] 区间切片
puts arr.sort.inspect # => [0,1,2,3,4] 不改原数组
puts arr.map { |n| n * 10 }.inspect # => [0,30,10,20,40]
puts arr.select { |n| n.even? }.inspect # => [0,2,4]
puts arr.sum # => 10
步骤 5:Hash 与 Range
# Hash:保持插入顺序
prices = { apple: 3, banana: 2 }
prices[:orange] = 5 # 增
prices[:apple] = 4 # 改
prices.delete(:banana) # 删
puts prices[:apple] # => 4
puts prices[:missing].inspect # => nil(不存在不报错)
puts prices.fetch(:missing, 0) # => 0(fetch 可给默认值)
prices.each do |fruit, price|
puts "#{fruit}: #{price} 元"
end
puts prices.keys.inspect # => [:apple, :orange]
# Range:连续区间
(1..5).to_a # => [1,2,3,4,5] 双点含尾
(1...5).to_a # => [1,2,3,4] 三点不含尾
("a".."e").to_a # => ["a","b","c","d","e"]
(1..10).step(2).to_a # => [1,3,5,7,9]
运行 ruby types.rb 确认每一步输出。
讲清楚要点
- 一切皆对象:
nil是 NilClass 的唯一实例,有自己的方法(nil.to_s、nil.nil?)。这统一了模型,也是 Ruby 优雅的根基。 - Symbol vs String:Symbol 不可变、同一名字全局唯一、比较是 O(1) 指针比较;String 可变、每次字面量都新建对象。规则:当「名字/标签」用 Symbol,当「内容数据」用 String。
- 可变性与
!约定:方法名带!表示原地修改接收者(危险版),不带则返回新对象。sortvssort!、upcasevsupcase!。注意!只是命名约定,不是语言机制。 - Hash 键不自动转换:
{name: "x"}["name"]取不到值——:name(Symbol)和"name"(String)是两个键。Rails 的HashWithIndifferentAccess才做这种宽容处理。 - 真值表极简:全 Ruby 只有
false和nil为假。0、""、[]都是真。与 JavaScript 完全不同,务必刻进脑子。
你可能会懵的地方
-
为什么 Ruby 要发明 Symbol 这种怪东西? 为了表达「这是一个标识符,不是一段文本」。Hash 键、方法名这类场景需要快速比较和不可变性,Symbol 正好是。它还省了字符串驻留的开销。
-
** frozen_string_literal 是什么?** Ruby 3 中文件首行加
# frozen_string_literal: true,该文件所有字符串字面量自动冻结不可变,提升性能。新文件建议都加。 -
Array 能装不同类型吗? 能。
[1, "a", :sym, [2, 3]]完全合法。但实务上保持同类型,遍历处理时才不出幺蛾子。 -
arr[10]越界会怎样? 返回nil,不抛异常。这是 Ruby 的宽容风格,但也意味着拼错下标不会报错——调试时注意。 -
Range 的
..和...哪个常用?..(含尾)更常用。...主要用于数组切片arr[0...3](取前 3 个)和 Rails 时间区间。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
if count == 0 当假判断后写 else | Ruby 中 0 为真,逻辑反了 | 显式 count.zero? 或牢记 0 是真 |
| 用字符串当 Hash 键又混用 symbol | 取值莫名 nil | 全项目统一用 symbol 键 |
s.upcase 后直接打印 s | 以为已修改,其实返回新串 | 要改原串用 upcase! 或重新赋值 |
prices[:miss] 报错预期异常 | Hash 缺键返回 nil 静默通过 | 需要严格时用 fetch(:key) |
"5" + 3 | TypeError,Ruby 不做隐式转换 | "5".to_i + 3 显式转换 |
卡点急救
- Hash 取不到值但 puts 明明有:99% 是 Symbol/String 键混用。
puts hash.keys看真实键类型。 - 数字计算结果全是整数丢了小数:两个 Integer 相除。把任一操作数加
.0或用7.fdiv(2)。 - 字符串里中文乱码:文件保存为 UTF-8,且首行加
# encoding: UTF-8(Ruby 3 默认 UTF-8,一般不用加,Windows 旧编辑器除外)。
面试常问
-
Symbol 和 String 的区别? 可变性(Symbol 不可变)、内存(同名 Symbol 单例,同内容 String 多份)、用途(Symbol 做标识,String 装数据)、比较成本(Symbol O(1) 比较对象ID)。补一句 Ruby 2.2+ Symbol 会被 GC,不再有符号泄露 DoS 问题,更加分。
-
Ruby 的 nil 和其他语言的 null 有什么不同? nil 是 NilClass 的对象实例,有方法(
nil.to_a => []、nil.nil? => true),可以参与链式调用而不立刻 NPE(配合&.)。null 只是空指针关键字。 -
==、eql?、equal?区别?==值相等;eql?更严格(区分 1 和 1.0),Hash 键比较用它;equal?是对象身份(object_id)相等。
自测三问
0、""、[]、nil、false中哪些在 if 里是「假」?- 什么时候 Hash 键应该用 Symbol,什么时候用 String?
map和map!的差别?!后缀是语法还是约定?
小结一句话
记住「一切皆对象、只有 false 和 nil 为假、Symbol 是名字 String 是内容」,Ruby 类型系统就掌握了。
延伸
- Ruby 官方核心类文档
- 下一课:语法基础——变量、常量、运算符与 Ruby 独特的命名约定。