<< 在 Ruby 里不是单纯的移位运算符(虽然数字也支持移位),更多是一个实例方法 #<<,不同类有完全不同的语义,是重载的操作符。
语法:
obj << something本质等价:
obj.<<(something)
一、最常用场景:追加(可变对象原地修改)
1. String#<< 字符串追加(原地修改!重点)
<< 直接修改原字符串对象,不会生成新字符串,object_id 不变
ruby
s = "hi"
puts s.object_id
s << " world"
puts s # "hi world"
puts s.object_id # 和上面一样!原地修改
对比 +:s + "xxx" 返回新字符串,原对象不变。
ruby
s = "hi"
s2 = s + " world"
p s # "hi"
2. Array#<< 数组追加元素(原地修改)
往数组尾部追加元素,等价于 push
ruby
arr = [1,2]
arr << 3
p arr # [1,2,3]
# 链式调用,因为 << 返回自身
arr << 4 << 5
p arr # [1,2,3,4,5]
⚠️ 注意:
<<是追加一个元素
ruby
arr = [1,2]
arr << [3,4]
p arr # [1,2,[3,4]] 嵌套数组!不是合并
# 如果想要合并数组,用 concat 或者 +
arr.concat([3,4])
3. Hash 不支持 <<
Hash 没有 #<< 方法,h << xxx 直接报错。
二、Integer#<< 位运算:左移(数字,不可变)
数字的 << 是二进制左移,数字是不可变对象,返回新数字,不修改原值
ruby
1 << 2 # 1 的二进制 0b1 左移2位 → 0b100 = 4
# 公式:n << k = n * (2 ** k)
puts 3 << 2 # 3 * 4 =12
数字的
<<不会修改原变量:
ruby
a = 3
a << 2
puts a # 3,不变!因为生成新数字,没有赋值回去
a = a << 2
puts a #12
三、其他内置类的 <<
IO#<<:向流写入内容(stdout << "hello")
ruby
$stdout << "hello\n" # 等价 puts,但不自动换行
Range、Symbol:没有<<方法
四、自定义类也可以重载 <<
Ruby 可以自己定义 << 方法:
ruby
class Box
def initialize
@items = []
end
def <<(item)
@items << item
self # 返回自身,支持链式调用
end
attr_reader :items
end
b = Box.new
b << "apple" << "banana"
p b.items # ["apple", "banana"]
五、高频踩坑点
坑1:String#<< 原地修改,带来引用副作用
ruby
s1 = "hello"
s2 = s1
s1 << "!"
puts s2 # hello! s1 s2 指向同一个对象,一起变
这就是前面我们讲可变对象时的经典坑!
坑2:Array << 数组,会嵌套而不是扁平化
ruby
arr = [1,2]
arr << [3,4]
p arr # [1,2,[3,4]] ❗嵌套
坑3:区分 << 和 +
| 对象 | << |
+ |
|---|---|---|
| String | 原地修改原字符串,返回self | 返回新字符串,原对象不变 |
| Array | 原地追加元素,返回self | 返回新数组,合并内容 |
| Integer | 位左移,返回新整数 | 算术加法 |
六、快速记忆
- String / Array 的 <<:原地追加,返回自己,支持链式(可变对象,小心引用共享)
- Integer 的 <<:二进制左移,生成新数字
obj << x本质是调用obj.<<(x),Ruby 运算符重载