`

理解 Ruby Symbol

    博客分类:
  • Ruby
 
阅读更多
转自:http://www.ibm.com/developerworks/cn/opensource/os-cn-rubysbl/index.html

Symbol 是什么
Ruby 是一个强大的面向对象脚本语言(本文所用 Ruby 版本为1.8.6),在 Ruby 中 Symbol 表示“名字”,比如字符串的名字,标识符的名字。
创建一个 Symbol 对象的方法是在名字或者字符串前面加上冒号:

创建 symbol 对象
               
:foo
:test

:”abc”
:”I am a boy”

你可能会问,字符串就是字符串,干吗还有字符串的名字?这是因为在 Ruby 中字符串也是一种对象,即 String 对象。无论其结构还是操作和 Symbol 对象都是不同的。
在 Ruby 中每一个对象都有唯一的对象标识符(Object Identifier),可以通过 object_id 方法来得到一个对象的标识符。我们来看看 Symbol 对象和 String 对象的差别:

Ruby 对象标识符
               
irb(main):001:0> puts :foo.object_id
327458
=> nil
irb(main):002:0> puts :foo.object_id
327458
=> nil
irb(main):003:0> puts :"foo".object_id
327458
=> nil
irb(main):004:0> puts "foo".object_id
24303850
=> nil
irb(main):005:0> puts "foo".object_id
24300010
=> nil
irb(main):006:0> puts "foo".object_id
24296170
=> nil

可以看到,前三行语句中的 :foo (或者 :"foo")都是同一个 Symbol 对象,其 object id 为327458,而后三行中的字符串”foo”都是不同的对象,其 object id 依次为24303850、24300010、24296170。
可见,每个 String 对象都是不同的,即便他们包含了相同的字符串内容;而对于 Symbol 对象,一个名字(字符串内容)唯一确定一个 Symbol 对象。
值得注意的是创建 Symbol 对象的字符串中不能含有’\0’字符,而 String 对象是可以的。

非法 Symbol 字符串
               
irb(main):001:0>  :"fo\0o"
SyntaxError: compile error
(irb):1: symbol cannot contain '\0'
        from (irb):1
irb(main):002:0> :"foo\0"
SyntaxError: compile error
(irb):2: symbol cannot contain '\0'
        from (irb):2
irb(main):003:0> puts "foo\0".object_id
24305140
=> nil
irb(main):004:0> puts "fo\0o".object_id
24301000
=> nil
irb(main):005:0>

除了可以采用一般的字符串,还可以使用操作符(例如+, -, *, /),变量,常量,方法甚至类的名字来创建 Symbol 对象,例如:+就是一个合法的 Symbol 。实际上,在 Ruby 内部操作符、变量等名字本身就是作为 Symbol 处理的,例如当你定义一个实例变量时, Ruby 会自动创建一个 Symbol 对象,例如 @test 对应为 :@test 。

实例变量的 Symbol
               
class Test 
attr_accessor :test
end

这个类定义了一个具有读写方法的实例变量 @test 。实际上 Ruby 创建了两个 Symbol ,一个是实例变量的 symbol :@test ,另一个是 :test 。那如果使用字符串对象 ”test” 作为参数呢?也可以,仍然会创建两个 symbol ,:test 和 :@test ,为什么还会创建 :test 呢?这是和Ruby的实现相关的(至少Ruby1.8.6里是这样)。
注意,类变量 @@test 和实例变量 @test 对应的 Symbol 显然是不同的。记住:名字相同,则Symbol 相同。

名字相同, Symbol 相同
               
class Test 
  puts :Test.object_id
  Test = 10
  puts :Test.object_id
 
  def Test 
    puts :Test.object_id
  end 
end

Test.new.Test


运行结果
               
224298
224298
224298


名字不同, Symbol 不同
               
class Test 
  puts :Test.object_id

  @@test = 10
  puts :@@test.object_id
  def test 
    puts :test.object_id
    @test = 10 
    puts :@test.object_id

  end 
end

t =Test.new 
t.test 


运行结果
               
224298
288068
79858
288108

第一个例子里,类名、常量名和方法名都是 Test ,因此相应的 Symbol 对象都是 :Test 。不用担心, Ruby 可以很好区分它在不同上下文中到底表示什么。当然这并不是一个好的编程风格,但对于理解 Ruby 的 Symbol 还是有帮助的: Symbol 表示一个名字,仅此而已。
Symbol 对象一旦定义将一直存在,直到程序执行退出。所有 Symbol 对象存放在 Ruby 内部的符号表中,可以通过类方法 Symbol.all_symbols 得到当前 Ruby 程序中定义的所有 Symbol 对象,该方法返回一个 Symbol 对象数组。由于 Symbol 比较多,你可以 dump 到文件中来查看。

all_symbols 方法
               
irb(main):001:0> Symbol.all_symbols.size
=> 4047
irb(main):002:0> Symbol.all_symbols[0..9]
=> [:@level_notifier, :ppx, :msg_dn, :version, :secs, :@user, :pos, :socketpair,
:TkENSURE, :HTTPAccepted]
irb(main):003:0> File.open("sym", "w") do |file| file.puts Symbol.all_symbols end
=> nil

回页首
Symbol 和 String
Symbol 对象和 String 对象是完全不同的东西,对象标识符很明确的说明了这一点。除此之外,我们还可以从两种对象的方法上区分。
查看 Ruby 库参考,你会发现 String 类有非常多的方法,包括 Mixed-in 方法(Ruby中一个类通过 include 其他模块而得到的方法,实现多重继承的效果)、类方法和实例方法;而 Symbol 类只有一个类方法 all_symbols 和7个实例方法。
例如,可以通过 []= 方法改变 string 的内容,而 symbol 则不行:

[]= 方法比较
               
irb(main):001:0> s="test"
=> "test"
irb(main):002:0> s[0]='1'
=> "1"
irb(main):003:0> puts s
1est
=> nil
irb(main):004:0> sym=:test
=> :test
irb(main):005:0> sym[0]=1
NoMethodError: undefined method `[]=' for :test:Symbol
        from (irb):5
irb(main):006:0>

虽然 Symbol 和 String 是不同的对象,但它们之间关系很密切。 Ruby 提供了方法在 Symbol和 String 之间转换。
Symbol 转化为 String
使用 to_s 或 id2name 方法将 Symbol 转化为一个 String 对象:

Symbol 到 String
               
irb(main):001:0> :test.id2name
=> "test"
irb(main):002:0> :test.to_s
=> "test"
irb(main):003:0> :"I am a boy".to_s
=> "I am a boy"

注意,每个 String 对象都是唯一的,因此对一个 Symbol 调用多次将产生多个 String 对象。
String 转化为 Symbol
除了在字符串前面加冒号,还可以使用 to_sym 或 intern 方法将 String 转化为 Symbol ,如果该 Symbol 已经存在,则直接返回。

String 到 Symbol
               
irb(main):001:0> var1 = "test".to_sym
=> :test
irb(main):002:0> var2 = "test".intern
=> :test
irb(main):003:0> var1 == var2
=> true
irb(main):004:0>

回页首
使用 Symbol
正如前边提到的, Ruby 内部一直在使用 Symbol ,比如 Ruby 程序中的各种名字,Symbol本质上是 Ruby 符号表中的东西。使用 Symbol 处理名字可以降低 Ruby 内存消耗,提高执行速度,这点我们在下一篇文章中会看到。
那么 Symbol 对我们有什么用呢?当然也是内存。使用 String 的开销太大了,因为每一个String 都是一个对象。想想前边的例子,一个字符串每出现一次 Ruby 就会创建一个 String 对象。
通常来讲,当你面临 String 还是 Symbol 的选择时,可以参考以下标准:
如果使用字符串的内容,这个内容可能会变化,使用 String
如果使用固定的名字或者说是标识符,使用 Symbol
那么什么时候我们会用到名字呢?很多时候都会,比如枚举值、关键字(哈希表关键字、方法的参数)等等
作为哈希表的 key
哈希表是 Symbol 应用最为广泛的地方。
在ruby中,哈希和数组类似,一个哈希表是一系列 key/value 对的集合,只不过它的 key 取值范围更广泛,可以是任何对象,比如正则表达式。但通常我们都会取有意义的 key ,比如 String、Symbol 。
下面这个哈希表表示按城市分类的一些机器的集合。

一个哈希表例子
               
hosts{
       'beijing' => 'machine1',
       'shanghai'  => 'machine2',
       'guangzhou' => 'machine3',
       'tianjin' =>  'machine4',
       'shenzhen' => 'machine5'
}

如果要引用 beijing 的机器,使用 hosts['beijing'] 。但如果我们程序中要频繁引用哈希表中 value ,这样就不大好了,因为 Ruby 对每一次字符串引用都会生成一个 String 对象,累积下来这个开销是相当大的。
我们完全可以使用 Symbol ,因为对于这些 key 来讲,我们用的就是名字而已,例如下面hosts[:beijing]


使用 Symbol 作为 key
               
hosts = {
 :beijing => 'machine1',
 :shanghai => 'machine2',
 :guangzhou => 'machine3',
 :tianjin  => 'machine4',
 :shenzhen => 'machine5'
}

哈希参数
通常我们定义的函数的参数的个数和顺序是写死的,调用函数的时候要确保参数的个数、顺序匹配,有时候这样很不方便,使用哈希参数可以解决这个问题。
ROR 中就大量地运用这种方式,也许你已经看到了,到处都是 Symbol 和哈希。比如:

使用哈希参数的方法调用
               
link_to 'Show', :action => 'show', :id => product

add_column :products, :price, :decimal,
:precision => 8, :scale => 2, :default => 0

使用哈希参数的方法可以如下定义,前半部分为固定参数,后面为可变参数,或者干脆全采用哈希参数:

哈希参数
               
def my_method(para1, …, options={})
#your code
end

def my_method(options={})
#your code
end

如果你希望设定一些默认参数,并允许调用者更改这些参数,可以使用哈希对象的 merge! 方法
hsh.merge!( other_hash )。该方法将 other_hash 里内容加到 hsh 中,如果other_hash 与 hsh 有重复的 key ,则 key 在 other_hash 中的 value 覆盖 hsh 中对应 key 的 value 。

方法定义-使用默认参数
               
class Test
def my_method(opts={})
  default_opts={:arg1 => 10, :arg2 => "abc"}
  default_opts.merge!(opts)
  default_opts.each{|key,value| puts "#{key} is #{value}"}
end
end

t = Test.new
t.my_method :arg1=>5, :arg3=>"def"


运行结果
               
arg1 is 5
arg2 is abc
arg3 is def



在 Rails 中,对 hash 类进行了扩展,可以使用 reverse_merge! 方法来达到上述效果。该方法在 ActiveSupport::CoreExtensions::Hash::ReverseMerge 中定义。Rails 甚至还提供了 assert_valid_keys 方法,对传递进来的哈希表的 keys 进行合法性检验。
Perl 说,条条大路通罗马。在 Ruby 中也是这样的,也许你会发现更好的应用 Symbol 和哈希的方法。
分享到:
评论

相关推荐

    比较详细的ruby symbol 学习资料

    解释的不错,应该明确了不少 ruby symbol详解 起因 最近在学习ruby on rails,的确是一个优秀的数据库开发框架。但在过程中,发现在视图文件夹中的rhtml文件里有大量的类似于以下的语句: <td><%= link_...

    符号的优雅:深入探索Ruby中的Symbol

    符号不仅是 Ruby 性能优化的关键组成部分,也是理解 Ruby 核心特性的窗口之一。通过深入了解符号的特性和使用方式,我们可以更好地利用它们来编写更加高效和 Ruby 风格的代码。 #### 符号概述 符号是 Ruby 中一种...

    ruby官方chm文档

    本套官方文档包含了关于Ruby核心库、标准库、语言特性和整体框架的详细信息,是学习和深入理解Ruby不可或缺的参考资料。 《ruby23-stdlib.chm》文档主要涵盖了Ruby的标准库(Standard Library)。Ruby标准库提供了...

    eloquent ruby

    ### Eloquent Ruby:深入探索Ruby语言的魅力 #### 引言 《Eloquent Ruby》是一本深受Ruby...通过阅读本书,你可以更加深刻地理解Ruby语言的设计理念,掌握其实现复杂功能的方法,并最终成为一名真正的Ruby专家。

    ruby中文手册 chm

    通过这个中文手册,开发者不仅可以学习到Ruby的基本语法,还可以了解到更高级的主题,如元编程、闭包、 Blocks、Proc对象和Symbol,以及如何利用Ruby的灵活性来创建高效、简洁的代码。同时,了解RGSS可以让开发者...

    ruby trap 初学者使用

    Ruby是一种强大的、面向对象的编程语言,以其简洁的语法和灵活的编程理念而...通过阅读"Ruby Trap"电子书,初学者可以对这些潜在的问题有更深入的理解,并学会如何在实际编程中避免这些陷阱,从而更好地掌握Ruby编程。

    Ruby元编程第二版中文

    符号(Symbol)在Ruby中是特殊的,它们在内存中只存在一份,常用于元编程中的键值。 5. **Proc对象与lambda**:Ruby的`Proc`和`lambda`可以创建匿名函数,它们可以被赋值给变量,作为参数传递,或者用作方法的...

    learning-ruby.

    1. **变量和数据类型**:Ruby支持多种数据类型,包括整型(Integer)、浮点型(Float)、字符串(String)、布尔型(Boolean)以及符号(Symbol)。此外,Ruby还有数组(Array)和哈希(Hash)等复合数据类型。 2. ...

    Ruby-NewRelic找到并修复Ruby错误使用NewRelic的应用程序监控和故障诊断

    Ruby是一种流行的开源编程语言,以其简洁、优雅的语法和强大的元编程能力著称。在开发复杂的Web应用程序时,性能监控和故障诊断是确保系统稳定性和高效运行的关键环节。New Relic是一个强大的应用性能管理(APM)...

    ruby笔记3ruby笔记3ruby笔记3

    通过这本“Ruby笔记3”,你将能够深入理解Ruby的精髓,掌握编程实践中所需的各项技能。无论是初学者还是有经验的开发者,都能从中受益,提升自己的编程能力。现在,打开9.25章节,让我们一起踏上Ruby的学习之旅吧!

    ruby程序安装文件

    Ruby的核心理念是“人比计算机更重要”,因此它的设计目标是让代码易于理解,减少程序员的疲劳。 Ruby的语法设计深受Perl和Smalltalk的影响,具有以下特点: 1. **动态类型**:Ruby是动态类型的,这意味着变量的...

    ruby入门练习题.zip

    7. **符号(Symbol)**:Ruby中的符号是不可变的,它们在内存中只存储一份,适用于作为哈希表的键,提高查找效率。 8. **块和 Proc**:Ruby中的块是代码的另一个形式,可以用`do..end`或者花括号`{}`包裹。Proc对象...

    Programming Ruby 1.9 (3rd edition)和源码

    在这一版中,作者深入浅出地讲解了Ruby 1.9的改进和新特性,旨在帮助开发者更好地理解和利用这个版本的优势。 1. **Ruby 1.9的关键改进**: - 字符串编码:Ruby 1.9引入了对多种字符编码的支持,使得处理多语言...

    ruby中文教程(源代码)

    本教程的源代码涵盖了以上概念,你可以通过阅读和运行代码来更好地理解Ruby的语法和特性。例如,你可能会找到关于类定义、方法调用、数据结构操作、控制流以及简单的Rails应用示例。 通过深入学习这个Ruby中文教程...

    ruby-2.1.10

    2. **Hash和Symbol的变化**:Ruby-2.1引入了新的Hash实现,提升了哈希表的性能,同时可能对Symbol对象进行了调整,以减少内存消耗。 3. **块和Proc**:Ruby-2.1.10可能增强了对块(block)和Proc对象的处理,包括更...

    Ruby学习资料chm

    这份"Ruby学习资料chm"集合了关于Ruby的基础学习材料,对于初学者或是希望深入理解Ruby的人来说是一份宝贵的资源。 Ruby的核心特性包括: 1. 面向对象:在Ruby中,一切都是对象,包括基本的数据类型如整数、字符串...

    Ruby编程语言_涵盖Ruby 1.8和1.9

    此外,Ruby 1.9还引入了新的内建数据结构,如Symbol表和Fixnum/Bignum的合并,提高了性能。 Ruby编程语言的核心特性包括: 1. 面向对象:Ruby是一种纯粹的面向对象语言,所有数据都是对象,包括基本类型,如整数和...

Global site tag (gtag.js) - Google Analytics