• 理解 Ruby Symbol


    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 是用 C 语言实现的,本文我们将以 Ruby 1.8.6 版本实现为例。

    Ruby 对象

    Ruby 是一个完全面向对象的语言,这点很好地体现在对象方法调用的一致性上,一个对象,无论是如何创建的,调用实例方法的方式是一样的,即“<对象>.<方法名>”。


    Ruby对象的方法调用

                    
    "gin joint".length  9
    "Rick".index("c")  2
    -1942.abs  1942
    

    自然 Symbol 也是一种对象,那么 Ruby 内部是如何一致地表示各种对象的呢?

    对象定义


    VALUE(ruby.h)

                    
    typedef unsigned long VALUE;
    

    没错,这就是 Ruby 对象的定义,确切地说是对象的引用。在 Ruby 中绝大多数对象的内容表示为 C 语言中的结构体,比如用户定义的对象和 Ruby 预定义的对象如 String, Array 等。我们知道引用结构体的方法是指针, void * 指针是比较通用的,使用之前先转换为待引用结构体类型的指针。那么为什么不使用 void * 作为对象的引用呢?

    这是因为Ruby在 VALUE 中内嵌了其他类型的,不用结构体表示的对象,也就是说直接用VALUE 表示的对象,这其中就有 Symbol 。考虑到现在大多数计算机体系上 void * 指针和 sizeof(unsigned long) 大小是相同的(比如4字节),可以互相转换,因此使用VALUE 更有价值。

    内嵌到 VALUE 中的对象有 Fixnum,Symbol,true,false,nil 和 undef 。


    Fixnum(ruby.h)

                    
    #define FIXNUM_MAX (LONG_MAX>>1)
    #define FIXNUM_MIN RSHIFT((long)LONG_MIN,1)
    
    #define FIXNUM_FLAG 0x01
    #define INT2FIX(i) ((VALUE)(((long)(i))<<1 | FIXNUM_FLAG))
    #define LONG2FIX(i) INT2FIX(i)
    #define FIX2INT(x) rb_fix2int((VALUE)x)
    #define FIXNUM_P(f) (((long)(f))&FIXNUM_FLAG)
    

    Fixnum 类表示小整数,由 INT2FIX 得到。由于可能在程序中频繁使用,将其用结构体表示可能会使执行速度大打折扣,因此直接嵌入 VALUE 中。数值左移一位或上0x01使得 Fixnum总是一个奇数,其实际可用比特位为 sizeof(long)*8-1 ,可表示的最大最小值分别为FIXNUM_MAX 和 FIXNUM_MIN ,超出该范围的数属于 Bignum 类,由C结构体实现。

    宏 FIXNUM_P 返回一个布尔值,P 表示断言(predicate),即“参数f是否为 fixnum 对象”。FIX2INT 将 Fixnum 转换回来,由 rb_fix2int 函数实现,它根据实际平台上 int 和long 的大小是否相同来分别处理。

    注意,在 VALUE 中 Fixnum 总是一个奇数,而使用 C 结构体表示的 Ruby 对象的内存空间是由 malloc 分配的,所得地址通常为4的倍数,因此以VALUE表示的 Fixnum 和C结构体 Ruby对象不会发生冲突。


    Symbol(ruby.h)

                    
    typedef unsigned long ID;
    #define SYMBOL_FLAG 0x0e
    #define SYMBOL_P(x) (((VALUE)(x)&0xff)==SYMBOL_FLAG)
    #define ID2SYM(x) ((VALUE)(((long)(x))<<8|SYMBOL_FLAG))
    #define SYM2ID(x) RSHIFT((unsigned long)x,8)
    

    Ruby 的 Symbol 定义很简单,它在C层次上就是一个无符号整数,转换为 Ruby 的 VALUE值的方法是先左移8位,然后加上 Symbol 的 flag 0x0e,这使得 Symbol 既不是4的倍数,也不是奇数,以 VALUE 表示的 Symbol 不会和 fixnum 对象,结构体对象冲突。


    True false nil undef(ruby.h)

                    
    #define Qfalse ((VALUE)0)	/*false*/
    #define Qtrue  ((VALUE)2)	/*true*/
    #define Qnil   ((VALUE)4)	/*nil*/
    #define Qundef ((VALUE)6)	/* undefined value for placeholder */
    

    Ruby 中一切皆对象,连 true/false/nil 也是。由于虚拟内存的第一个块(对应地址4)一般不分配, Qnil 也不会和结构体对象冲突。

    这样以 VALUE 表示的C结构体对象、Fixnum、Symbol、true、false、nil和undef在内存地址空间中都可以互不侵犯,和平共处。 VALUE 可以引用 Ruby 内部所有的对象,这使得 Ruby可以统一处理不同类型的对象。

    当给定一个 VALUE 对象时,使用 TYPE 宏判断该对象的种类,例如 Symbol 对象类型为T_SYMBOL。


    TYPE宏(ruby.h)

                    
    #define T_MASK   0x3f
    #define BUILTIN_TYPE(x) (((struct RBasic*)(x))->flags & T_MASK)
    static inline int rb_type(obj)
       VALUE obj;
    {
        if (FIXNUM_P(obj)) return T_FIXNUM;
        if (obj == Qnil) return T_NIL;
        if (obj == Qfalse) return T_FALSE;
        if (obj == Qtrue) return T_TRUE;
        if (obj == Qundef) return T_UNDEF;
        if (SYMBOL_P(obj)) return T_SYMBOL;
        return BUILTIN_TYPE(obj);
    }
    #define TYPE(x) rb_type((VALUE)(x))
    

    inline 函数 rb_type 首先判断 VALUE 是否为内嵌对象,即 Fixnum、Symbol、true、false、nil和undef 。如果都不是,说明该对象是一个C结构体对象,调用 BUILTIN_TYPE 宏二次判断。该宏涉及到结构体对象中的内容。

    结构体对象例子: String

    我们以 String 对象定义为例看一下 Ruby 结构体对象。


    String(ruby.h)

                    
    struct RBasic {
        unsigned long flags;
        VALUE klass;
    };
    struct RString {
        struct RBasic basic;
        long len;
        char *ptr;
        union {
    	long capa;
    	VALUE shared;
        } aux;
    };
    #define RSTRING_PTR(s) (RSTRING(s)->ptr)
    #define RSTRING_LEN(s) (RSTRING(s)->len)
    

    和我们自己在C中使用字符串时差不多,最重要的两个成员是 len 和 ptr 。 len 表示字符串长度, ptr 指向实际的字符数组。 RSTRING_PTR 和 RSTING_LEN 用来快速访问这两个域,当然使用之前最好用 TYPE 宏检查一下 VALUE s 是否真正是一个 String 对象。 aux联合涉及 String 对象处理技巧,这里我们不讨论。

    注意 RString 定义中有一个类型为 RBasic 结构体的成员,里面是 flags 和 klass ,联系上面 TYPE 宏的定义, BUILTIN_TYPE 宏要检查这个 flags 值:没错, flags 就存储着结构体对象的类型。当然 T_MASK 只占用了 unsigned long 中的6个比特位,剩下的 flags 位中还有一些用于其他用途。

    另一个 RBasic 成员 klass 是 VALUE 类型,指向这个对象归属的类,即“类对象”。在Ruby 中类也是以对象存在的(记住,一切皆对象)。类对象用 struct RClass 结构体表示,对应的 TYPE 标志为 T_CLASS 。

    除个别结构体外,绝大多数 Ruby 结构体对象第一个域为 RBasic 成员,这使得 Ruby 可以一致地判断各种对象的类型。

    此外还有一个 CLASS_OF 宏,用来得到对象的归属类对象,由 inline 函数 rb_class_of实现,对于结构体对象返回 RBasic 成员的 klass 的值,对于 VALUE 内嵌对象,他们没有结构体,则返回 Ruby 初始化时创建的类对象指针,例如 Symbol 类对象指针为rb_cSymbol 。


    CLASS_OF 宏(ruby.h)

                    
    static inline VALUE
    rb_class_of(obj)
        VALUE obj;
    {
        if (FIXNUM_P(obj)) return rb_cFixnum;
        if (obj == Qnil) return rb_cNilClass;
        if (obj == Qfalse) return rb_cFalseClass;
        if (obj == Qtrue) return rb_cTrueClass;
        if (SYMBOL_P(obj)) return rb_cSymbol;
    
        return RBASIC(obj)->klass;
    }
    #define CLASS_OF(v) rb_class_of((VALUE)(v))
    

    可以看到, Symbol 对象和 String 对象在 Ruby 内部的表示完全不同。 Symbol 对象直接嵌入到 VALUE 中,本质上是一个数字,这个数字和创建 Symbol 的名字形成一对一的映射;而String 对象是一个重量级的用C结构体表示的家伙,因此使用 Symbol 和 String 的开销相差很大。

    那么 Symbol 对象的数字 ID 和名字是如何对应起来的呢?下面我们就会看到。

     

    符号表(Symbol Table)

    我们使用:<字符串>的方式创建 Symbol 对象,但到现在为止,我们看到的 Symbol 只是一个数字,那这个数字对应的名字在哪里?在符号表里。

    符号表是一个全局数据结构,它存放了所有 Symbol 的(数字ID,名字)对, Ruby 不会从中删除 Symbol ,因此当你创建一个 Symbol 对象后,它将一直存在,直到程序结束。为了方便 name 和 ID 之间的双向查找,设置了两个符号表 sym_tbl 和 sym_rev_tbl 。


    Symbol table(parse.c)

                    
    static st_table *sym_tbl;		/*name to ID*/
    static st_table *sym_rev_tbl;	/*ID to name*/
    void Init_sym()
    {
        sym_tbl = st_init_strtable_with_size(200);
        sym_rev_tbl = st_init_numtable_with_size(200);
    }
    

    符号表采用链式哈希表,如下图所示。

    bins 为数组指针,每个数组元素类型为 struct st_table_entry * 指针,通过哈希表元素的 next 成员链成链表。 num_bins 为数组元素个数, num_entries 为哈希表元素个数。每个哈希表元素包括哈希值和 key/record 对。


    链式哈希表
     

    哈希表定义

                    
    /*st.h*/
    typedef struct st_table st_table;
    struct st_hash_type {
        int (*compare)();
        int (*hash)();
    };
    struct st_table {
        struct st_hash_type *type;
        int num_bins;			
        int num_entries;
        struct st_table_entry **bins;
    };
    typedef unsigned long st_data_t;
    
    /*st.c*/
    typedef struct st_table_entry st_table_entry;
    
    struct st_table_entry {
        unsigned int hash;
        st_data_t key;
        st_data_t record;
        st_table_entry *next;
    };
    

    st_table 的 type 成员存放该哈希表的 key 比较函数指针和哈希函数指针,在哈希表初始化时填入。例如,对于 ID 到 name 的 sym_rev_tbl 表,其哈希函数就是简单地返回ID。

     

    Symbol 方法实现

    有了数据结构,我们来看算法:对象方法的实现。

    类对象初始化

    在 Ruby 中类也是以对象存在的,即类对象。对象通过 CLASS_OF 宏可以得到它的归属类对象,对象的方法就存储在类对象的方法表中,也是一个哈希表。类对象初始化的时候通过调用rb_define_method 将对象方法加入到方法表中。


    Symbol类对象初始化(object.c)

                    
    VALUE rb_cSymbol; /*Symbol class object, global variable*/
    
    /*class name:Symbol, super class:rb_cObject*/
    rb_cSymbol = rb_define_class("Symbol", rb_cObject);
    
    /*method initialization*/
    …
    rb_define_method(rb_cSymbol, "to_s", sym_to_s, 0);
    rb_define_method(rb_cSymbol, "id2name", sym_to_s, 0);
    



    String类对象初始化(String.c)

                    
    VALUE rb_cString;  
    
    /*class name:String, super class:rb_cObject*/
    rb_cString  = rb_define_class("String", rb_cObject);
    
    
    /* method initialization*/
    …
    rb_define_method(rb_cString, "intern", rb_str_intern, 0);
    rb_define_method(rb_cString, "to_sym", rb_str_intern, 0);
    

    显然, String 对象的 intern 方法和 to_sym 方法由 rb_str_intern 函数实现,而Symbol 对象的 to_s 和 id2name 方法由 sym_to_s 函数实现。

    String 到 Symbol 的方法

    在 Ruby 内部, String 对象到 Symbol的转换由 string.c 中的函数 rb_str_intern实现。

    该函数进行一些必要的检查,然后调用 rb_intern 函数创建字符串和 ID 的映射。该函数很有意思,它实际上是 Ruby 解析器的一部分,从中我们可以看到 Ruby 是如何处理程序中的各种名字符号的。

    函数 rb_intern 大致完成以下工作:

    1. 搜索符号表,如果名字对应的 Symbol 已经创建,直接返回 symbol 的 ID。
    2. 解析名字的类型(全局变量、类变量、实例变量、操作符、属性赋值( attribute assignment )、局部变量、常量或其他),创建 ID 并打上相应的类型标记(在 ID 的最低3位),其中 Ruby 操作符的 ID 是在 op_tbl 表中预定义的,其他的由全局变量 last_id 动态生成。这样每一个名字都将唯一对应一个 ID。
    3. 将(name,ID)对登记在符号表 sym_tbl 和 sym_rev_tbl 中。


    简化的 rb_intern 函数(parse.c)

                    
    ID rb_intern(name)
        const char *name;
    {
        const char *m = name;
        ID id;
        int last;
    
        if (st_lookup(sym_tbl, (st_data_t)name, (st_data_t *)&id))
    	return id;
    
    last = strlen(name)-1;
    id = 0;
        
    /*...parse the name, tag attribute for id*/
    
      id_regist:
        name = strdup(name);
        st_add_direct(sym_tbl, (st_data_t)name, id);
        st_add_direct(sym_rev_tbl, id, (st_data_t)name);
        return id;
    }
    

    最终 rb_intern 返回的 ID 被 rb_str_intern 转换为一个VALUE值(通过宏ID2SYM)

    Symbol 到 String 的方法

    Symbol 到 String 由 sym_to_s 实现。其中 rb_id2name 函数将 id 转换为一个名字,然后 rb_str_new2 使用该名字创建一个新的 string 对象。


    sym_to_s 函数(object.c)

                    
    static VALUE
    sym_to_s(sym)
        VALUE sym;
    {
        return rb_str_new2(rb_id2name(SYM2ID(sym)));
    }
    

    函数 rb_id2name 本质上是简单地查询操作符表 op_tbl 和符号表 sym_rev_tbl ,但由于属性赋值类型名字处理的缘故,具体代码要稍复杂一些。

     

    Ruby 1.9 中的新变化

    就在本文写作过程中,2007 年圣诞节, Ruby 1.9 发布了。

    在 Ruby 1.9 中, Symbol 有了一些变化,使得 Symbol 看起来“更像” String 了。比如Symbol 的===方法,在 Ruby 1.8.6 和 Ruby 1.9 中是完全不同的。


    symbol===string

                    
    irb(main):001:0> [RUBY_VERSION, RUBY_RELEASE_DATE]
    => ["1.8.6", "2007-09-24"]
    irb(main):002:0> :a === "a"
    => false
    irb(main):003:0>
    
    irb(main):001:0> [RUBY_VERSION, RUBY_RELEASE_DATE]
    => ["1.9.0", "2007-12-25"]
    irb(main):002:0> :a === "a"
    => true
    irb(main):003:0>
    

    除此之外,新的 Symbol 类包含了比较模块,使得 Symbol 对象可以使用诸如”==”、”>=”、”<=”等比较操作符。


    Symbol 比较

                    
    irb(main):001:0>  :test1 >= :test2
    => false
    irb(main):002:0>  :test3 >= :test2
    => true
    irb(main):003:0>
    

    Symbol 类还定义了以前只有 String 类才有的方法,比如,[]、casecmp、length、capitalize等等。

    在实现上, Symbol 类对象和 string 类对象的初始化都放到了 string.c 中,这似乎也使得 Symbol 和 String 拉近了距离。

    难道 Symbol 和 String 变成一个东西了?没有。类的关系没有变, Symbol 的父类还是Object , Symbol 的表示和存储也没有变。新方法的实现只是利用了 Symbol 和 String的互换函数。比如 Symbol 的 capitalize 方法就是将 Symbol 转换为 String ,最后再转换回来。这个时候其实已经是一个新的 Symbol 了。


    capitalize 方法

                    
    static VALUE
    sym_capitalize(VALUE sym)
    {
        return rb_str_intern(rb_str_capitalize(rb_id2str(SYM2ID(sym))));
    }
    

    当然可能还会有新的变化。不过我个人还是希望 Symbol 能保持简单,因为它本来就不复杂。而复杂的东西往往最终会失去生命力。

  • 相关阅读:
    php对接微信小程序支付
    微信小程序/网站 上传图片到腾讯云COS
    php+smarty轻松开发微社区/微论坛
    精简商务合同管理系统开发
    MyBatis返回map数据
    MyBatis(五)select返回list数据
    MyBatis(四)多参数处理问题
    MyBatis(三)MyBatis的增删改查
    dbconfig.properties
    MyBatis入门(二)接口式编程
  • 原文地址:https://www.cnblogs.com/rywx/p/2560487.html
Copyright © 2020-2023  润新知