<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>gyorou</title>
    <link>https://ruby-china.org/gyorou</link>
    <description></description>
    <language>en-us</language>
    <item>
      <title>我用 NEM 的区块链写了个吟诗作赋的应用</title>
      <description>&lt;h3 id="地址"&gt;地址&lt;/h3&gt;
&lt;p&gt;&lt;a href="http://nem.bocchi.tokyo/" rel="nofollow" target="_blank"&gt;http://nem.bocchi.tokyo/&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="玩法"&gt;玩法&lt;/h3&gt;
&lt;p&gt;很简单，直接使用 testNet 的账号向页面上的地址里转账 0 XEM，并在 message 里面用 markdown 附上你的诗歌内容。&lt;/p&gt;
&lt;h3 id="需要准备"&gt;需要准备&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;NEM 的 testNet 的账号。(包括你的 private key，public key，address)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;你的账号里需要有一点 XEM。可以从&lt;a href="http://namuyan.dip.jp/nem/testnet/" rel="nofollow" target="_blank" title=""&gt;这里&lt;/a&gt;获取。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;你需要一个转账的工具。可以用 nem 的钱包或者直接用我提供的 ruby 脚本也可以。&lt;a href="https://gist.github.com/lengshuiyulangcn/12f6cb8f69f564b0e63d365d1264a669" rel="nofollow" target="_blank" title=""&gt;这里&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="其他"&gt;其他&lt;/h3&gt;
&lt;p&gt;其实就是把投稿内容保存到 nem 的 transaction 里面并用 api 读取，渲染而已。&lt;/p&gt;</description>
      <author>gyorou</author>
      <pubDate>Thu, 15 Feb 2018 16:21:38 +0800</pubDate>
      <link>https://ruby-china.org/topics/35043</link>
      <guid>https://ruby-china.org/topics/35043</guid>
    </item>
    <item>
      <title>有人一起 Mastodon 吗？</title>
      <description>&lt;p&gt;最近两天如果关注 github trend 的话会发现有个叫&lt;a href="https://github.com/tootsuite/mastodon" rel="nofollow" target="_blank" title=""&gt;Mastodon&lt;/a&gt;的 Rails 项目的人气正在飞速上升中。&lt;/p&gt;

&lt;p&gt;引用 ReadMe 的介绍，可以发现这个是一个去中心化的微博系统。
话说我们可能对去中心化相关听得最多的应该就是 blockchain 相关了。
这里所谓的去中心化最重大的意义就是不会有服务商来投广告，塞抹布，黄赌毒各种不良信息随便发。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Mastodon is a free, open-source social network server. A decentralized solution to commercial platforms, it avoids the risks of a single company monopolizing your communication. Anyone can run Mastodon and participate in the social network seamlessly.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="那么用户如何创建一个Mastodon的账号呢？"&gt;那么用户如何创建一个 Mastodon 的账号呢？&lt;/h4&gt;
&lt;p&gt;很简单，从&lt;a href="https://github.com/tootsuite/mastodon/blob/master/docs/Using-Mastodon/List-of-Mastodon-instances.md" rel="nofollow" target="_blank" title=""&gt;这里&lt;/a&gt;的节点中选择一个节点，点进
去注册即可。注册之后用户名就相当于&lt;code&gt;username@host&lt;/code&gt;的形式。用这个用户名就可以和任何 instance 上的任何用户发生关系 (follow, retweet 之类的) 啦。&lt;/p&gt;
&lt;h4 id="如何找到感兴趣的用户？"&gt;如何找到感兴趣的用户？&lt;/h4&gt;
&lt;p&gt;mastodon 提供了从 twitter 好友中发现 mastodon 用户的方式 (&lt;a href="https://mastodon-bridge.herokuapp.com/" rel="nofollow" target="_blank" title=""&gt;这里&lt;/a&gt;)。经试用悲剧地发现并没有好友在使用 mastodon。当然还有其他方法，比如等别人把自己的用户名贴出来什么的。&lt;/p&gt;
&lt;h4 id="如何自己host一个Mastodon的实例？"&gt;如何自己 host 一个 Mastodon 的实例？&lt;/h4&gt;
&lt;p&gt;按照 ReadMe 的方法用 Docker-compose 是最快的方法。&lt;/p&gt;

&lt;p&gt;clone 项目之后&lt;code&gt;docker-compose build &amp;amp;&amp;amp; docker-compose up -d&lt;/code&gt; 就好。&lt;/p&gt;

&lt;p&gt;当然要公开你的 instance 别忘了加上反向代理和 SSL。
SSL 的话用 Letsencrypt 即可。&lt;/p&gt;
&lt;h4 id="最后"&gt;最后&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://mustodon.bocchi.tokyo" rel="nofollow" target="_blank" title=""&gt;这个&lt;/a&gt;是我的 Instance。&lt;/p&gt;

&lt;p&gt;我的 id 是 &lt;code&gt;gyorou_bocchi@mustodon.bocchi.tokyo&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;欢迎大家和我搞基以及聊三俗话题。&lt;/p&gt;</description>
      <author>gyorou</author>
      <pubDate>Fri, 07 Apr 2017 21:20:32 +0800</pubDate>
      <link>https://ruby-china.org/topics/32724</link>
      <guid>https://ruby-china.org/topics/32724</guid>
    </item>
    <item>
      <title>[译] 使用 Elixir OTP 实现 MapReduce</title>
      <description>&lt;p&gt;最近开始学习 Elixir。顺带推荐一下几本书。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pragprog.com/book/elixir/programming-elixir" rel="nofollow" target="_blank" title=""&gt;Programming Elixir&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pragprog.com/book/phoenix/programming-phoenix" rel="nofollow" target="_blank" title=""&gt;Programming Phoenix&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pragprog.com/book/cmelixir/metaprogramming-elixir" rel="nofollow" target="_blank" title=""&gt;Metaprogramming Elixir&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.co.jp/Learning-Elixir-Kenny-Ballou-ebook/dp/B0196Q7CUO" rel="nofollow" target="_blank" title=""&gt;Learning Elixir&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;有 Safaribooks 的账号其实这些书都可以随便看。公司没福利的话个人账号一个月其实也没多少钱。&lt;/p&gt;

&lt;p&gt;下面正文。&lt;/p&gt;

&lt;hr&gt;
&lt;h3 id="使用Elixir OTP实现MapReduce"&gt;使用 Elixir OTP 实现 MapReduce&lt;/h3&gt;
&lt;p&gt;&lt;a href="http://dev.classmethod.jp/etc/elixir_otp_mapreduce/" rel="nofollow" target="_blank" title=""&gt;原文&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Elixir 给我们提供了使用多进程实现 application 的框架 OTP(实际是 Erlang 的东西)。使用 OTP 可以实现进程间的消息交换，当通信出现错误时候的重启，以及进程状态管理。&lt;/p&gt;
&lt;h2 id="MapReduce"&gt;MapReduce&lt;/h2&gt;&lt;h3 id="Apache Lucene"&gt;Apache Lucene&lt;/h3&gt;
&lt;p&gt;下面介绍一下利用 MapReduce 机制的一些软件。
Lucene 是基于 Java 的文档搜索引擎，在 ElasticSearch 的内部也用到了 MapReduce。
Lucene 使用 MapReduce 的机制生成单词以及出现位置的索引。(据说 Lucene 的作者最早是使用 Lisp 实现的)。
(简单起见，这次我们使用词法分析完毕的文档)&lt;/p&gt;
&lt;h3 id="map函数"&gt;map 函数&lt;/h3&gt;
&lt;p&gt;下面是 map 的简单的例子。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Enum.map([1,2,3,4,5], fn(elem) -&amp;gt; elem * elem end)
#=&amp;gt; [1, 4, 9, 16, 25]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这次的例子中，我们对于多个文档，使用将其变换为"文档编号，单词，单词索引"的 map 函数。
假设我们已经有了下面这种形式的词法分析完毕的文档。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{:doc1, "ワタシ ハ エリクサー チョット デキル"}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(doc1 作为文档的 ID，各个文档的 ID 都是独一无二的。)
我们将其变换为如下的"文档编号，单词，单词索引"的形式。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[{:doc1, "ワタシ", 0}, {:doc1, "ハ", 1}, {:doc1, "エリクサー", 2}, {:doc1, "チョット", 3}, {:doc1, "デキル", 4}]
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="reduce函数"&gt;reduce 函数&lt;/h3&gt;
&lt;p&gt;reduce 函数接受多个元素以及一个函数，虽然也是将参数带入函数，不过一开始是针对起始的两个元素，接下来将前面对应的结果以及第三个元素带入函数，然后是其结果和第四个元素带入，然后第五个，这样逐渐带入之后元素层层减少最终返回结果。
reduce 函数的简单的例子如下&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Enum.reduce([1,2,3,4,5], 0, fn(elem, acc) -&amp;gt; elem + acc end) # 第二个参数是初始值
#=&amp;gt; 15
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这次的例子中，对于刚刚经过 map 函数处理过的文档，我们使用 reduce 函数进行统合。将 map 函数变换过的多个文档&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[
[{:doc1, "ワタシ", 0}, {:doc1, "ハ", 1}, {:doc1, "エリクサー", 2}, {:doc1, "チョット", 3}, {:doc1, "デキル", 4}],
[{:doc2, "ワタシ", 0}, {:doc2, "ハ", 1}, {:doc2, "ルビー", 2}, {:doc2, "チョット", 3}, {:doc2, "デキル", 4}]
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;转换成如下形式&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%{"エリクサー" =&amp;gt; [doc1: 2], "チョット" =&amp;gt; [doc1: 3, doc2: 3], "デキル" =&amp;gt; [doc1: 4, doc2: 4],
  "ハ" =&amp;gt; [doc1: 1, doc2: 1], "ルビー" =&amp;gt; [doc2: 2], "ワタシ" =&amp;gt; [doc1: 0, doc2: 0]}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;到这里我们就获得了文档索引，通过检索单词就可以获得出现该单词的文档名 (文档 ID) 以及相应的出现的位置。&lt;/p&gt;
&lt;h2 id="使用OTP进行MapReduce"&gt;使用 OTP 进行 MapReduce&lt;/h2&gt;&lt;h3 id="单线程实现"&gt;单线程实现&lt;/h3&gt;
&lt;p&gt;首先不使用 OTP 来实现。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;defmodule MapReduce do
  # Application入口  
  def entry do
    doc1 = {:doc1, "ワタシ ハ エリクサー チョット デキル"}
    doc2 = {:doc2, "ワタシ ハ ルビー チョット デキル"}

    [doc1, doc2]
    |&amp;gt; Enum.map(fn(doc) -&amp;gt; MapReduce.word_map(doc) end) # 使用word_map函数实现map
    |&amp;gt; Enum.reduce(%{}, fn(items, acc) -&amp;gt; MapReduce.invert_array(items, acc) end) # 使用invert_array实现reduce。
  end

  # 作为map函数的参数的函数。各个单词将变换成{:doc1, "ワタシ", 0}的形式。
  def word_map({doc_id, words}) do
    String.split(words)
    |&amp;gt; Enum.with_index
    |&amp;gt; Enum.map(fn(tup) -&amp;gt; Tuple.insert_at(tup, 0, doc_id) end)
  end

  # 作为reduce函数的参数的函数。将上面word_map的结果进行减缩。
  # 形成%{"ワタシ" =&amp;gt; [doc1: 0, doc2: 0], ....}的字典表。
  def invert_array([{doc_id, word, index} | tail], shuffle_map) do
    invert_array(tail, Map.put(shuffle_map, word, [{doc_id, index} | Map.get(shuffle_map, word, [])]))
  end
  def invert_array([], shuffle_map), do: shuffle_map  
end

IO.inspect MapReduce.entry
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出结果&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%{"エリクサー" =&amp;gt; [doc1: 2], "チョット" =&amp;gt; [doc1: 3, doc2: 3], "デキル" =&amp;gt; [doc1: 4, doc2: 4],
  "ハ" =&amp;gt; [doc1: 1, doc2: 1], "ルビー" =&amp;gt; [doc2: 2], "ワタシ" =&amp;gt; [doc1: 0, doc2: 0]}
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="出现错误的时候怎么办？"&gt;出现错误的时候怎么办？&lt;/h3&gt;
&lt;p&gt;这次的 sample 相对比较少，出错的话大不了把程序再跑一遍就是了。但是要是要处理的文档很多，那就没这么简单了。&lt;/p&gt;

&lt;p&gt;我们想要这么一种机制，将 map 函数生成的"文档编号，单词，单词索引"的形式作为状态保存下来，当某个文档处理出现问题的时候，我们仅仅重启处理那个文档的进程，这样就可以继续处理剩下的文档。&lt;/p&gt;

&lt;p&gt;要实现这种机制，我们就需要在文档变换处理结束后将其状态保存，以及实现进程的监视来对应出错时候进程的重启。
为了实现该机制，我们使用 OTP 中的 GenServer 和 Supervisor 来实现。
下面是对这两个东西的简单说明。&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th style="text-align:center;"&gt;名称&lt;/th&gt;
&lt;th style="text-align:center;"&gt;概要&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;GenServer&lt;/td&gt;
&lt;td style="text-align:center;"&gt;用来实现能够保持进程状态的 Application 的模块&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;Supervisor&lt;/td&gt;
&lt;td style="text-align:center;"&gt;用来监视进程，提供出错重启的机制&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;具体请参考 Elixir 的官网上的 GenServer 以及 Supervisor 的说明。&lt;/p&gt;
&lt;h3 id="Supervision Tree"&gt;Supervision Tree&lt;/h3&gt;
&lt;p&gt;接下来我们按照 Supervisor 的原理来构建 MapReduce 中监控各个处理，保存状态，的进程之间的上下关系表。&lt;/p&gt;

&lt;p&gt;&lt;img src="http://cdn.dev.classmethod.jp/wp-content/uploads/2015/12/MapReduce-Supervision-Tree.png" title="" alt=""&gt;&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th style="text-align:center;"&gt;名称&lt;/th&gt;
&lt;th style="text-align:center;"&gt;概要&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;GenServer&lt;/td&gt;
&lt;td style="text-align:center;"&gt;用来实现能够保持进程状态的 Application 的模块&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;Supervisor&lt;/td&gt;
&lt;td style="text-align:center;"&gt;用来监视进程，提供出错重启的机制&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;MapServer&lt;/td&gt;
&lt;td style="text-align:center;"&gt;负责将文档转换成"文档编号，单词，单词索引"的形式的进程。处理结束之后将结果保存在 Stash 中。(因此需要其持有 stash 的进程号)，这个进程会启动多个进程&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;ReduceSupervisor&lt;/td&gt;
&lt;td style="text-align:center;"&gt;负责监视 ReduceServer 的进程。设置 ReduceServer 的重启策略&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;ReduceServer&lt;/td&gt;
&lt;td style="text-align:center;"&gt;负责从 Stash 中获取通过 map 变换得到的文档，然后将其变换成倒置矩阵的进程 (因为要从 Stash 获取数据所以持有 Stash 的进程号)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;h3 id="Supervisor"&gt;Supervisor&lt;/h3&gt;
&lt;p&gt;把先启动的 Stash 的进程号传递给 MapSupervisor 和 ReduceSupervisor。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;defmodule MapReduce.Supervisor do
  use Supervisor

  def start_link do
    result = {:ok, sup } = Supervisor.start_link(__MODULE__, [])
    {:ok, stash_pid} =
      Supervisor.start_child(sup, worker(MapReduce.Stash, []))
    Supervisor.start_child(sup, supervisor(MapReduce.MapSupervisor, [stash_pid]))
    Supervisor.start_child(sup, supervisor(MapReduce.ReduceServer, [stash_pid]))
    result
  end

  def init(_) do
    supervise [], strategy: :one_for_one
  end
end
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="MapSupervisor, ReduceSupervisor"&gt;MapSupervisor, ReduceSupervisor&lt;/h3&gt;
&lt;p&gt;将从 Supervisor 获取的 Stash 的进程号传递给各自启动的子进程。&lt;/p&gt;
&lt;h4 id="MapSupervisor"&gt;MapSupervisor&lt;/h4&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;defmodule MapReduce.MapSupervisor do
  use Supervisor

  def start_link(stash_pid) do
    {:ok, _pid} = Supervisor.start_link(__MODULE__, stash_pid)
  end

  def init(stash_pid) do
    child_processes = [ worker(MapReduce.MapServer, [stash_pid]) ]
    supervise child_processes, strategy: :one_for_one
  end
end
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="ReduceSupervisor"&gt;ReduceSupervisor&lt;/h4&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;defmodule Mr.ReduceSupervisor do
  use Supervisor

  def start_link(stash_pid) do
    {:ok, _pid} = Supervisor.start_link(__MODULE__, stash_pid)
  end

  def init(stash_pid) do
    child_processes = [ worker(MapReduce.ReduceServer, [stash_pid]) ]
    supervise child_processes, strategy: :one_for_one
  end
end
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="Stash"&gt;Stash&lt;/h3&gt;
&lt;p&gt;为了保存 MapServer 变换结果的文档我们使用 GenServer。
我们这里定义了供其进程调用的&lt;code&gt;save_document&lt;/code&gt;以及&lt;code&gt;get_document&lt;/code&gt;两个函数，对于这两个函数的 callback 我们需要自己进行实现。
只通过实现 callback 函数就能实现进程间的通信了。
这个进程在启动的时候会建立一个空的 list，当其他的进程调用&lt;code&gt;save_document&lt;/code&gt;的时候就会往这个 list 中追加 data。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;defmodule MapReduce.Stash do
  use GenServer

  def start_link do
    GenServer.start_link(__MODULE__, []) # 启动时候带一个空的list，之后往这里追加文档
  end

  def save_document(pid, indexing_doc) do
    GenServer.cast pid, {:save_document, indexing_doc}
  end

  def get_documents(pid) do
    GenServer.call pid, :get_documents
  end

  #####
  # GenServer implementation

  def handle_cast({:save_document, indexing_doc}, docs) do
    {:noreply, [indexing_doc|docs]} # 这里的docs是启动时候定义的list，将会往这里追加元素
  end

  def handle_call(:get_documents, _from, docs) do
    # 这离的docs同样是启动时候的那个list，这里的tuple的第二个元素是返回给调用者的消息，第三个元素是继续保存在进程中的list(状态)
    {:reply, docs, docs}
  end
end
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="MapServer"&gt;MapServer&lt;/h3&gt;
&lt;p&gt;这个进程需要保持启动时候从 Stash 获取的进程号所以使用 Genserver。
实际上对文档进行变换的函数 (word_map) 我们放在别的文件中定义。为了将文档变换的结果保存到 Stash 中，我们调用&lt;code&gt;Stash.save_document&lt;/code&gt;。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;defmodule MapReduce.MapServer do
  use GenServer

  def start_link(stash_pid) do
    GenServer.start_link(__MODULE__, stash_pid, name: __MODULE__)
  end

  def make_indexing_document(words_list) do
    GenServer.cast(__MODULE__, {:make_indexing_document, words_list})
  end

    # 在这里开始进行处理
  def make_indexing_documents(words_list) when is_list(words_list) do
    words_list
    |&amp;gt; Enum.map(&amp;amp;(Task.async(fn -&amp;gt; make_indexing_document(&amp;amp;1) end)))
    |&amp;gt; Enum.map(&amp;amp;Task.await/1)
  end

  def get_documents do
    GenServer.call(__MODULE__, :get_documents)
  end

  def handle_cast({:make_indexing_document, words}, stash_pid) do
    word_map(stash_pid, words)
    {:noreply, stash_pid}
  end

  def handle_call(:get_documents, _from, stash_pid) do
    {:reply, MapReduce.Stash.get_documents(stash_pid), stash_pid}
  end

  # 在这里进行文档的变换，将结果保存到Stash中。
  defp word_map(stash_pid, {doc_id, words}) do
    indexing_doc = MapReduce.WorkerFunction.word_map({doc_id, words})
    :ok = MapReduce.Stash.save_document(stash_pid, indexing_doc)
  end
end
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="ReduceServer"&gt;ReduceServer&lt;/h3&gt;
&lt;p&gt;因为需要从 Stash 中获取经过 MapServer 变换的文档，所以我们需要继续保存 Stash 的进程号。
统合成倒置矩阵的函数我们在别的文件中定义。
在函数处理之前我们调用&lt;code&gt;Stash.get_documents(stash_pid)&lt;/code&gt;获取要处理的文档。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;defmodule MapReduce.ReduceServer do
  use GenServer

  def start_link(stash_pid) do
    GenServer.start_link(__MODULE__, stash_pid, name: __MODULE__)
  end

  # 从这里开始处理
  def make_inverted_array do
    GenServer.call(__MODULE__, :make_inverted_array)
  end

  # 在这里将从map变换来的文档变换成倒置矩阵。
  # 处理需要从Stash进程中获取文档的list
  def handle_call(:make_inverted_array, _from, stash_pid) do
    indexing_docs = MapReduce.Stash.get_documents(stash_pid)
    {:reply, invert_array(indexing_docs), stash_pid }
  end

  def invert_array(indexing_docs) do
    MapReduce.WorkerFunction.invert_array(indexing_docs)
  end
end
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="word_map, invert_array的实现"&gt;word_map, invert_array 的实现&lt;/h3&gt;
&lt;p&gt;我们在另一个文件中实现了这些函数。和之前单进程的例子几乎一样。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;defmodule MapReduce.WorkerFunction do
  def word_map({doc_id, words}) do
    String.split(words)
    |&amp;gt; Enum.with_index
    |&amp;gt; Enum.map(fn(tup) -&amp;gt; Tuple.insert_at(tup, 0, doc_id) end)
  end

  def invert_array(items) do
    items
    |&amp;gt; Enum.reduce(%{}, fn(item, acc) -&amp;gt; invert_array(item, acc) end)
  end

  defp invert_array([{doc_id, word, index} | tail], shuffle_map) do
    invert_array(tail, Map.put(shuffle_map, word, [{doc_id, index} | Map.get(shuffle_map, word, [])]))
  end

  defp invert_array([], shuffle_map), do: shuffle_map
end
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="执行结果"&gt;执行结果&lt;/h3&gt;
&lt;p&gt;因为用的例子和单进程实现相同所以其实并没什么意义，但是总之先跑一下看看。
首先在项目根目录启动 REPL(IEx)，然后准备好数据。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;iex -S mix
Erlang/OTP 18 [erts-7.1] 1 [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Interactive Elixir (1.1.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)&amp;gt; doc1 = {:doc1, "ワタシ ハ エリクサー チョット デキル"}
{:doc1, "ワタシ ハ エリクサー チョット デキル"}
iex(2)&amp;gt; doc2 = {:doc2, "ワタシ ハ ルビー チョット デキル"}
{:doc2, "ワタシ ハ ルビー チョット デキル"}
iex(3)&amp;gt; doc3 = {:doc3, "ワタシ ハ リナックス チョット デキル"}
{:doc3, "ワタシ ハ リナックス チョット デキル"}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来进行 Map 处理。可以看到追加的数据被存放到了 list 中。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;iex(4)&amp;gt; MapReduce.MapServer.make_indexing_documents [doc1, doc2, doc3]
iex(5)&amp;gt; MapReduce.MapServer.get_documents
[[{:doc3, "ワタシ", 0}, {:doc3, "ハ", 1}, {:doc3, "リナックス", 2},
  {:doc3, "チョット", 3}, {:doc3, "デキル", 4}],
 [{:doc2, "ワタシ", 0}, {:doc2, "ハ", 1}, {:doc2, "ルビー", 2},
  {:doc2, "チョット", 3}, {:doc2, "デキル", 4}],
 [{:doc1, "ワタシ", 0}, {:doc1, "ハ", 1}, {:doc1, "エリクサー", 2},
  {:doc1, "チョット", 3}, {:doc1, "デキル", 4}]]

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后进行 Reduce 生成倒置矩阵。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;iex(6)&amp;gt; MapReduce.ReduceServer.make_inverted_array
%{"エリクサー" =&amp;gt; [doc1: 2], "チョット" =&amp;gt; [doc1: 3, doc2: 3, doc3: 3],
  "デキル" =&amp;gt; [doc1: 4, doc2: 4, doc3: 4],
  "ハ" =&amp;gt; [doc1: 1, doc2: 1, doc3: 1], "リナックス" =&amp;gt; [doc3: 2],
  "ルビー" =&amp;gt; [doc2: 2], "ワタシ" =&amp;gt; [doc1: 0, doc2: 0, doc3: 0]}

&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;使用 OTP 可以非常轻松实现多进程的 application。
如果换成别的语言用线程或者进程来实现重启策略会非常麻烦，想到这里笔者就感觉 OTP 是在是难能可贵。
在官网上也有使用 OTP 实现 kvs 的示例，大家有兴趣的话务必试一下。&lt;/p&gt;</description>
      <author>gyorou</author>
      <pubDate>Tue, 22 Nov 2016 20:08:07 +0800</pubDate>
      <link>https://ruby-china.org/topics/31662</link>
      <guid>https://ruby-china.org/topics/31662</guid>
    </item>
    <item>
      <title>把妹向在线黑客马拉松</title>
      <description>&lt;h2 id="在线hackthon エンジニアも恋したい"&gt;在线 hackthon エンジニアも恋したい&lt;/h2&gt;
&lt;p&gt;偶然发现了这么一个东西。
下面涉及剧透。
&lt;a href="https://paiza.jp/poh/enkoi" rel="nofollow" target="_blank" title=""&gt;エンジニアも恋したい&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;卧槽还能这样玩？总之在通过漫画的形式进行的剧情中会穿插一些编程的问题，通过解决这些问题，提交代码会触发不同剧情。
如果能通过所有测试，那么最终可以看到 Good Ending。相反如果测试中出现一些问题，就无法到达最完美结局。&lt;/p&gt;
&lt;h3 id="game start"&gt;game start&lt;/h3&gt;
&lt;p&gt;故事开头男主转职第一天遇到巧遇女主。
&lt;img src="https://paiza.jp/poh4/images/img_manga_top01_02.png" title="" alt="resize"&gt;
&lt;img src="https://paiza.jp/poh4/images/img_manga_top02.png" title="" alt="resize"&gt;
&lt;img src="https://paiza.jp/poh4/images/img_manga_contents01_02.png" title="" alt="resize"&gt;
&lt;img src="https://paiza.jp/poh4/images/img_manga_contents02.png" title="" alt="resize"&gt;
&lt;img src="https://paiza.jp/poh4/images/box_maintxt01.png" title="" alt="resize"&gt;&lt;/p&gt;
&lt;h4 id="任务1"&gt;任务 1&lt;/h4&gt;
&lt;p&gt;总之第一天，你想要在妹子面前装装 B。这个时候你接到了要完成的任务。计算商品的库存量。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;输入格式
输入格式将作为标准输入提供。
N
S_1
S_2
・
・
S_N

条件
所有的测试用例满足下面条件。
1 ≦ N ≦ 100
0 ≦ S_i ≦ 100
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;要求输入所有商品数量的总和。&lt;/p&gt;
&lt;h4 id="解答1"&gt;解答 1&lt;/h4&gt;
&lt;p&gt;嘛这个问题很简单。用 ruby 就更简单了。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;gets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chomp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="nb"&gt;gets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chomp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ok，三个测试全部通过。继续 misson2。
&lt;img src="https://paiza.jp/poh4/images/img_manga_1ok_asameshi.png" title="" alt="resize"&gt;
&lt;img src="https://paiza.jp/poh4/images/box_maintxt03.png" title="" alt="resize"&gt;
&lt;img src="http://49.212.212.212/imagehost/image.php?di=LUS7" title="" alt="resize"&gt;&lt;/p&gt;

&lt;p&gt;……卧槽你是来卖萌的么？&lt;/p&gt;
&lt;h4 id="任务2"&gt;任务 2&lt;/h4&gt;
&lt;p&gt;计算需要补充商品的总价格。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;输入格式
N
T_1 S_1 P_1
T_2 S_2 P_2
・
・
・
T_N S_N P_N

满足条件
1 ≦ N ≦ 100
1 ≦ T_i ≦ 100
0 ≦ S_i ≦ 100
1 ≦ P_i ≦ 10,000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输入的第一行 N 为商品数量。接下来，每行第一个 T 为需求量，第二个 S 为库存量，第三个 P 为单价。
即当 T&amp;gt;=S 的时候我们需要花费 (T-S)*P 来补充某一个商品。&lt;/p&gt;

&lt;p&gt;特么这个自然也很简单。&lt;/p&gt;
&lt;h4 id="解法"&gt;解法&lt;/h4&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;input_lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;gets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chomp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;
&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;input_lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;tsp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;gets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chomp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tsp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;tsp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tsp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tsp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tsp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;嗯继续看漫画。卧槽这进展略快？已经约了？&lt;/p&gt;

&lt;p&gt;&lt;img src="https://paiza.jp/poh4/images/img_manga_2ok_bar.png" title="" alt="resize"&gt;
&lt;img src="https://paiza.jp/poh4/images/box_maintxt05.png" title="" alt="resize"&gt;&lt;/p&gt;

&lt;p&gt;反正就是要做个游戏，具体说明游戏特么太长了。简而言之就是
这种
&lt;img src="http://49.212.212.212/imagehost/image.php?di=ZE6P" title="" alt="resize"&gt;&lt;/p&gt;

&lt;p&gt;给定一串数字 (比如途中的 4,5,1,10,3,4,1) 和一个窗口 (比如 3),要求找出这串数字中连续窗口大小的最大和。比如图中 (10+3+4＝17 最大)
&lt;img src="http://49.212.212.212/imagehost/image.php?di=JJ22" title="" alt="resize"&gt;&lt;/p&gt;
&lt;h4 id="任务3"&gt;任务 3&lt;/h4&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;输入格式
t n
m_1
m_2
m_3
...
m_n

条件
1 ≦ t ≦ n ≦ 300,000
0 ≦ m_i ≦ 10,000
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="解法3"&gt;解法 3&lt;/h4&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;lw&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;gets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chomp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;lw&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; 
  &lt;span class="n"&gt;arr&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;gets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chomp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lw&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
   &lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;lw&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"+"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
   &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="http://49.212.212.212/imagehost/image.php?di=07JX" title="" alt="resize"&gt;&lt;/p&gt;

&lt;p&gt;卧槽怎么回事。超时了，嘛看来还得考虑一下效率的问题。总之看一下这个时候的结局会是如何。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://paiza.jp/poh4/images/img_manga_3ok_3yearsago.png" title="" alt="resize"&gt;
&lt;img src="https://paiza.jp/poh4/images/box_maintxt07.png" title="" alt="resize"&gt;
&lt;img src="https://paiza.jp/poh4/images/honeymoon_hakone.jpg" title="" alt="resize"&gt;&lt;/p&gt;

&lt;p&gt;特么新婚旅行箱根温泉？我不能接受。女主这大妈的样子。卧槽。重来。&lt;/p&gt;
&lt;h3 id="解法3 again"&gt;解法 3 again&lt;/h3&gt;
&lt;p&gt;首先刚才的代码有两个地方效率很低。其一使用了 eval。其二是其实进行了不必要的计算。&lt;/p&gt;

&lt;p&gt;比如计算 4，3，5，2 当窗口大小为 3 的时候的最大值。当我们计算完 4+3+5，想要计算 3+5+2 的时候，其实只需要把刚才的计算的值减去 4 并加上 2。否则当窗口很大的时候，加法计算就会很浪费时间。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;lw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;gets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chomp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;lw&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; 
 &lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;gets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chomp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;
 &lt;span class="n"&gt;arr&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt;
 &lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lw&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lw&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;arr&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;gets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chomp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;
&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;lw&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;..-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;each_with_index&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
   &lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;
   &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tmp&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="http://49.212.212.212/imagehost/image.php?di=O65Q" title="" alt="resize"&gt;
终于……&lt;/p&gt;

&lt;p&gt;&lt;img src="https://paiza.jp/poh4/images/honeymoon_hawaii.jpg" title="" alt="resize"&gt;&lt;/p&gt;

&lt;p&gt;恩满分的结果是最后新婚旅行地变成夏威夷……&lt;/p&gt;
&lt;h3 id="感想"&gt;感想&lt;/h3&gt;
&lt;p&gt;年轻人就应该好好撸管特么做什么白日梦。世界上哪有这么简单把妹的方法。无耻期待续作。&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;</description>
      <author>gyorou</author>
      <pubDate>Thu, 18 Dec 2014 23:00:59 +0800</pubDate>
      <link>https://ruby-china.org/topics/23272</link>
      <guid>https://ruby-china.org/topics/23272</guid>
    </item>
    <item>
      <title>Ruby 扩展库两种写法</title>
      <description>&lt;p&gt;算是学习心得？欢迎指正。&lt;/p&gt;

&lt;p&gt;考虑 ruby 扩展的情况一般有两种，一种是实现某个东西的时候，ruby 的代码是在是太慢。但是又实在是贪恋 ruby 便利的书写方法。
另一种情况则是想给用 C，C++ 实现的功能函数加上一层 ruby 的 wrap，使得能够在 ruby 程序中方便调用这些功能。&lt;/p&gt;
&lt;h3 id="一般方法"&gt;一般方法&lt;/h3&gt;
&lt;p&gt;就实现 ruby 的 C 语言扩展而言，首先当然是要准备好用 c 语言实现的源码。
比如有这么一个简单的加法函数。&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来就要用 ruby 中的一套数据结构来对这个函数进行包裹。&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;//warpper.c&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;"ruby.h"&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;VALUE&lt;/span&gt; &lt;span class="n"&gt;wrap_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aa&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="n"&gt;VALUE&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aa&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bb&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FIX2INT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aa&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FIX2INT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bb&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;INT2FIX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Init_test&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;VALUE&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rb_define_module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Test"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;rb_define_module_function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"add"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wrap_add&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你觉得看这个代码比较吃力，则可以去读一下&lt;a href="http://blog.nigbee.pink/tags/%E6%BA%90%E7%A0%81" rel="nofollow" target="_blank" title=""&gt;ruby 源码解读&lt;/a&gt;的内容。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;VALUE c 语言中 ruby 对象数据结构的地址。其实际上是一个 unsigned int 类型。ruby 的所有扩展库函数必须返回 VALUE。一切 ruby 的对象的数据结构在声明时候也必须是 VALUE。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;FIX2INT 将 ruby 的 Fixnum 转换成 C 语言的 int 类型。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;INT2FIX 和上面反过来。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;rb_define_module 定义模块并初始化。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;rb_define_module_function(module, "add", wrap_add, 2) 向 module 指向的模块中添加名为 add 的方法，这个方法的 c 语言实体函数为 wrap_add，带两个参数。注意这个两个参数是 ruby 中 add 方法的参数，而 warp_add 的第一个参数为 self，所以我们在 ruby 中不管何时何地都有一个 self。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;当然还要有很多其他常用的，比如&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;NUM2DBL(value) ruby 中的 Num 转换为 c 的 double&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;STR2CSTR(value) ruby 中的 String 转换为 C 的 char *&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;rb_str_new2(s) 用 char *s 指向的字符串新建一个 ruby 的 String 对象。并自动计算其长度。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;等等等等&lt;/p&gt;

&lt;p&gt;warp 函数的处理大抵就是一下步骤&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;将参数从 ruby 对象转换成 C 的变量&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;用 C 的变量调用 C 的函数进行计算，获取 C 的结果变量。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;将 C 的结果变量转换成 ruby 的对象并返回。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;最后在 void Init_xxx() 中，我们可以做的事情有&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rb_define_class() 定义一个类&lt;/li&gt;
&lt;li&gt;rb_define_class_under() 在类中定义类&lt;/li&gt;
&lt;li&gt;rb_define_module() 定义一个模块&lt;/li&gt;
&lt;li&gt;rb_define_module_under() 在模块中定义模块&lt;/li&gt;
&lt;li&gt;rb_define_module_function() 在模块中定义一个方法&lt;/li&gt;
&lt;li&gt;rb_define_class_function 在类中定义方法&lt;/li&gt;
&lt;li&gt;……&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;其实不宜把逻辑写的太复杂，注意用 C 语言实现的，通常只是很消耗计算时间的那一小块内容。其他的一些衔接可以继续用 ruby 实现。&lt;/p&gt;

&lt;p&gt;写好上面的 wrap.c 之后，在同一个目录中新建一个文件命名为 extconf.rb&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#extconf.rb
require "mkmf"
create_makefile("hoge")
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来执行&lt;code&gt;ruby extconf.rb&lt;/code&gt;。ruby 就会生成一个扩展库的 makefile，然后执行&lt;code&gt;make&lt;/code&gt; 即可以得到一个名为 hoge 的 ruby 库。这个 hoge 库根据不同环境可能名称不同。linux 下为.so 文件，而 OS X 下则是.bundle 文件。&lt;/p&gt;
&lt;h3 id="swig"&gt;swig&lt;/h3&gt;
&lt;p&gt;如果熟悉 ruby 的 C 语言源码，并且我们是白手起家写一个东西或者，我们要写的代码很简单，那么上述方法自然是很顺利。当我们要将一个大的 C 库包裹上 ruby 的接口，显然上面的方法就有些繁琐而力不从心了。&lt;/p&gt;

&lt;p&gt;我们仔细考虑一下，其实，wrap 函数无非就是在做 ruby 的对象到 C 的数据类型的变换。这些变化当然可以通过某种方法自动完成。&lt;/p&gt;

&lt;p&gt;于是 swig 就是这样一个工具。&lt;/p&gt;

&lt;p&gt;下面就用 swig 来写一个 NLPIR 中文分词的 ruby 的 wrapper。&lt;/p&gt;
&lt;h4 id="环境"&gt;环境&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;linux ubuntu 12.04 32-bit&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ruby 2.0.0&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;gcc v4.6&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;首先从&lt;a href="http://www.nlpir.org/" rel="nofollow" target="_blank" title=""&gt;这里&lt;/a&gt;下载最新的接口包。将里面的 data 文件夹，linx32 目录里面的 libNLPIR.so 放到当前目录中来。然后将&lt;a href="https://gist.github.com/lengshuiyulangcn/b65e51d4a7e068b9cc62" rel="nofollow" target="_blank" title=""&gt;这里&lt;/a&gt;的两个文件也放到当前目录中。&lt;/p&gt;

&lt;p&gt;注意，NLPIR.h 是经过修改过的头文件，不知道为什么包里自带的头文件对 gcc 兼容性极差。&lt;/p&gt;

&lt;p&gt;注意看 swig 的写法。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%module NLPIR
%{
#define SWIG_FILE_WITH_INIT
#include "NLPIR.h"
%}
#define POS_MAP_NUMBER 4
#define ICT_POS_MAP_FIRST 1
#define ICT_POS_MAP_SECOND 0
#define PKU_POS_MAP_SECOND 2
#define PKU_POS_MAP_FIRST 3
#define  POS_SIZE 40

#define GBK_CODE 0
#define UTF8_CODE GBK_CODE+1
#define BIG5_CODE GBK_CODE+2
#define GBK_FANTI_CODE GBK_CODE+3

int NLPIR_Init(const char * sDataPath=0,int encode=GBK_CODE,const char*sLicenceCode=0);

bool NLPIR_Exit();

const char * NLPIR_ParagraphProcess(const char *sParagraph,int bPOStagged=1);

const result_t * NLPIR_ParagraphProcessA(const char *sParagraph,int *pResultCount,bool bUserDict=true);

int NLPIR_GetParagraphProcessAWordCount(const char *sParagraph);

void NLPIR_ParagraphProcessAW(int nCount,result_t * result);

double NLPIR_FileProcess(const char *sSourceFilename,const char *sResultFilename,int bPOStagged=1);

unsigned int NLPIR_ImportUserDict(const char *sFilename);

int NLPIR_AddUserWord(const char *sWord);

int NLPIR_SaveTheUsrDic();

int NLPIR_DelUsrWord(const char *sWord);

double NLPIR_GetUniProb(const char *sWord);

bool NLPIR_IsWord(const char *sWord);

const char * NLPIR_GetKeyWords(const char *sLine,int nMaxKeyLimit=50,bool bWeightOut=false);

const char * NLPIR_GetFileKeyWords(const char *sFilename,int nMaxKeyLimit=50,bool bWeightOut=false);

const char * NLPIR_GetNewWords(const char *sLine,int nMaxKeyLimit=50,bool bWeightOut=false);

const char * NLPIR_GetFileNewWords(const char *sFilename,int nMaxKeyLimit=50,bool bWeightOut=false);

unsigned long NLPIR_FingerPrint(const char *sLine);

int NLPIR_SetPOSmap(int nPOSmap);

CNLPIR* GetActiveInstance();

bool NLPIR_NWI_Start();

int  NLPIR_NWI_AddFile(const char *sFilename);

bool NLPIR_NWI_AddMem(const char *sText);

bool NLPIR_NWI_Complete();

const char * NLPIR_NWI_GetResult(bool bWeightOut=false);

unsigned int  NLPIR_NWI_Result2UserDict();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中的中括号涵盖了头文件的位置，然后下面则是我们需要进行包裹的函数。&lt;/p&gt;

&lt;p&gt;然后就执行 &lt;code&gt;swig -c++ -ruby nlpir.i&lt;/code&gt; 这个时候会在当前目录下生成一个 nlpir.c_xx 的文件，其实这个就是我们刚才自己写的 wrap 的本体，而 swig 从函数的返回值，参数中自动帮我们生成了这些内容。&lt;/p&gt;

&lt;p&gt;在当前目录下新建一个 extconf.rb，内容为&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require 'mkmf'

$CFLAGS+="-Wall"
$LOCAL_LIBS+="./libNLPIR.so"
$libs = append_library($libs, "supc++")
create_makefile('Nlpir')
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意其中加上 c++ 的运行环境，这个包用纯 c 编译会出错。&lt;/p&gt;

&lt;p&gt;接下来和刚才一样了，执行&lt;code&gt;ruby extconf.rb&lt;/code&gt;，然后 make。 &lt;/p&gt;

&lt;p&gt;这个时候显示 make 成功，目录下多出来一个 Nlpir.so 文件。这个就是我们生成的 ruby 扩展库了。我们试着用 irb 来加载。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;irb(main):001:0&amp;gt; require "./NLPIR"
=&amp;gt; true
irb(main):002:0&amp;gt; NLPIR.NLPIR_Init
=&amp;gt; 1
irb(main):003:0&amp;gt; NLPIR.NLPIR_ParagraphProcess("吃不到葡萄说葡萄酸。")
=&amp;gt; "\xE5\x90/n \x83\xE4/n \xB8\x8D/n \xE5\x88/n \xB0\xE8/v \x91\xA1/n \xE8\x90/n \x84\xE8/n \xAF\xB4/n \xE8\x91/n \xA1\xE8/m \x90\x84/n \xE9\x85/n \xB8\xE3/v \x80\x82/n "
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;啊，忘记将默认编码改成 utf-8 了。&lt;/p&gt;

&lt;p&gt;恩，其实我是故意的。我就是要来说一句，
至今默认 GBK 是 NLPIR 这个工具的硬伤。&lt;/p&gt;</description>
      <author>gyorou</author>
      <pubDate>Thu, 04 Dec 2014 20:56:18 +0800</pubDate>
      <link>https://ruby-china.org/topics/23030</link>
      <guid>https://ruby-china.org/topics/23030</guid>
    </item>
    <item>
      <title>来试着用 Ruby 做个文字游戏引擎吧</title>
      <description>&lt;p&gt;####起因&lt;/p&gt;

&lt;p&gt;毕业论文憋不出来，蛋疼。找点别的事情转移一下注意力。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/jlnr/gosu/" rel="nofollow" target="_blank" title=""&gt;gosu&lt;/a&gt;是一个 2d 的游戏图形库。提供了 ruby 和 cpp 接口。&lt;/p&gt;

&lt;p&gt;ruby 还是可以干许多有趣的事情的。&lt;/p&gt;

&lt;p&gt;####环境&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;rvm ruby-2.0.0 Mac OS X Yosemite&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;gem install gosu&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;bash/atom&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;####进展&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;实现了开头场景&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;实现了保存和读取&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;实现了文本和命令的混编&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;实现了 flag 分支&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;####待完成&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;实现音乐播放和场景切换&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;界面的美化和模块化&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;一大推 bug
&lt;a href="https://github.com/lengshuiyulangcn/ruser" rel="nofollow" target="_blank" title=""&gt;地址&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;用这个引擎写了个小故事&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#test.txt
今天天气不错。
[background=background/pic04.jpg]
我上街去散步。
遇见了叼着面包片的女孩。
[background=background/pic03.jpg]
我们撞到了一起。
[flags=特么你走路不长眼睛？#test01.txt@0:妹子我想认识你#test.txt@7]
[background=background/pic04.jpg]
她轻声说了一声抱歉。然后迅速走开了。
我:要是能再遇见就好了。
game over

#test01.txt
于是之后什么都没有发生。
哈哈，真的结束了。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="http://49.212.212.212/imagehost/image.php?di=MLPM" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;老板来抓人了，乖乖写修论去了。&lt;/p&gt;</description>
      <author>gyorou</author>
      <pubDate>Fri, 28 Nov 2014 21:09:09 +0800</pubDate>
      <link>https://ruby-china.org/topics/22916</link>
      <guid>https://ruby-china.org/topics/22916</guid>
    </item>
    <item>
      <title>试译 Ruby 源码解读</title>
      <description>&lt;p&gt;&lt;a href="http://i.loveruby.net/ja/rhg/book/minimum.html" rel="nofollow" target="_blank" title=""&gt;原文&lt;/a&gt;
(申明：仅供学习参考，本人不负任何责任，如有违规请联系删除)&lt;/p&gt;

&lt;p&gt;##第一章 最低限度的 ruby 知识
为了方便第一部分的解说，在这里简单介绍一下 ruby 的基本知识。这里不会系统介绍编程的技巧方面的东西，读完这章节也不会让你掌握 ruby 的编程方法。如果读者已经有 ruby 的经验，那就可以直接跳过本章节。&lt;/p&gt;

&lt;p&gt;另外我们会在第二部分不厌其烦地讲解语法，于是在这一章节我们尽量不涉及语法相关内容。关于 hash，literal 之类的表示方法我们会采用经常使用的方式。可以省略的东西原则上我们不会省略。这样才会使语法看起来简单，但是我们不会重复提醒"这里可以省略"。&lt;/p&gt;

&lt;p&gt;###对象&lt;/p&gt;

&lt;p&gt;####字符串&lt;/p&gt;

&lt;p&gt;ruby 程序造作的所有东西都是对象。在 ruby 里面没有如 java 里面的 int 或者 long 一样的基本数据类型。比如，如下面的例子一样书写的话，就会生成一个内容是"content"的字符串 (String) 对象。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="s2"&gt;"content"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;刚才说这个只是个字符串的对象，其实正确来讲这个是生成字符串对象的表达式。所以每写一次，都会有新的字符串对象生成。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="s2"&gt;"content"&lt;/span&gt;
&lt;span class="s2"&gt;"content"&lt;/span&gt;
&lt;span class="s2"&gt;"content"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里就生成了三个内容是"content"的对象。&lt;/p&gt;

&lt;p&gt;但是单纯有对象程序是看不到的。下面教你如何在终端显示对象。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;#显示"content"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;"#"之后的东西是注释。接下这个都表示注释。&lt;/p&gt;

&lt;p&gt;"p(……)"是调用了函数 p。它能够较逼真的表示对象的状态，基本上是一个 debug 用的函数。&lt;/p&gt;

&lt;p&gt;虽然严格意义上讲，ruby 里面并不存在函数这个概念。但是现在请先把它当作函数来理解。函数无论在哪里都可以使用。&lt;/p&gt;

&lt;p&gt;####各种各样的序列&lt;/p&gt;

&lt;p&gt;接下来我们来说明一下直接生成对象的序列 (literal)。先从一般的整数和小数说起。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 整数
1
2
100
9999999999999999999999999   # 无论多大的数都可以使用

# 小数
1.0
99.999
1.3e4     # 1.3×10^4

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;请记住，这些也全部是生成对象的表达式。在这里我们不厌其烦得重复强调，ruby 里面是没有基本类型的。&lt;/p&gt;

&lt;p&gt;下面是生成数组对象的表达式。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[1,2,3]

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个程序会按顺序生成包含 1 2 3 三个元素的数组。数组的元素可以是任何对象。于是也可以有这种表达式。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[1, "string", 2, ["nested", "array"]]

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;甚至，下面的用法可以来生成哈希表。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{"key"=&amp;gt;"value", "key2"=&amp;gt;"value2", "key3"=&amp;gt;"value3"}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所谓哈希表，是指任何的所有对象之间的一对一的数据结构。按照上述写法会构造出包含下面所示关系的一张表。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"key"   →  "value"
"key2"  →  "value2"
"key3"  →  "value3"

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样生成了哈希表之后，当我们向此对象询问"key 对应的值是什么"的时候，它就会告诉我们结果是"value"。那该怎么询问呢？那就要用到方法了。&lt;/p&gt;

&lt;p&gt;####方法的调用&lt;/p&gt;

&lt;p&gt;对象可以调用方法。用 c++ 的话说就是调用成员函数。至于什么是方法，我觉得没有必要说明，还是看一下下面这个简单的例子吧。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"content".upcase()

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里调用了字符串 (字符串的内容为"content") 的 upcase 方法。upcase 是返回一个将所有小写字母全部变成大写的字符串。于是会有下面的效果。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;p("content".upcase())   # 输出"CONTENT"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;方法可以连续调用&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"content".upcase().downcase()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个时候调用的是"content".upcase() 的返回值对象的 downcase 方法。&lt;/p&gt;

&lt;p&gt;另外，ruby 里面没有像 Java 和 C++ 一样的全局概念。于是所有对象的接口都是通过方法来实现。&lt;/p&gt;

&lt;p&gt;###程序&lt;/p&gt;

&lt;p&gt;####顶层&lt;/p&gt;

&lt;p&gt;在 ruby 里面写上一个式子就已经算是一个程序了。没有像 C++ 或者 Java 一样需要定义 main()。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;p("content")
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;仅仅就这样已经算是一个完整的 ruby 程序。把这个东西复制到 first.rb 这个文件中然后在命令行执行&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;% ruby first.rb
"content"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用选项-e 可以不需要创建文件就可以执行代码。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;% ruby -e 'p("content")'
"content"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然而要注意到的是，上面 p 的位置是程序中最外层的东西，也就是说在程序上属于最上层，于是被称作顶层。有层顶是 ruby 脚本语言的最大特征。&lt;/p&gt;

&lt;p&gt;ruby 基本上一行就是一句。不需要在句尾加上分号。所以下面的内容实际上是三个语句。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;p("content")
p("content".upcase())
p("CONTENT".downcase())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行结果如下&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;% ruby second.rb
"content"
"CONTENT"
"content"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;####局部变量&lt;/p&gt;

&lt;p&gt;ruby 中所有的变量和常量都仅仅是对对象的引用 (reference)。所以仅仅是带入其他变量的话并不会发生复制之类的行为。
这个可以联想到 Java 中的对象型变量，以及 C++ 中的指向对象的指针。但是指针的值无法改变。&lt;/p&gt;

&lt;p&gt;ruby 仅看变量的首字母就可以分别出变量的种类。小写阿拉伯字母或者下划线开始的变量属于局部变量。使用等于号=代入赋值。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;str = "content"
arr = [1,2,3]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一开始代入的时候不需要声明变量，另外无论变量是什么类型，代入方法都没有区别。下面的写法都是合法的。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lvar = "content"
lvar = [1,2,3]
lvar = 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当然，虽然可以这么写但是完全没有必要故意写成这样。把种类各样的对象放在一个变量中会使得代码变得晦涩难懂。现实中很少有如此的写法，在这里我们仅仅是举个例子。&lt;/p&gt;

&lt;p&gt;变量内容查询也是常用的。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;str = "content"
p(str)           # 结果显示"content"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;下面我们从变量持有对象的引用的观点来看下面的例子。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;a = "content"
b = a
c = b
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行这个程序之后，变量 a，b，c 三个局部变量指向的都是同一个对象，也就是第一行生成的"content"这个字符串对象。&lt;/p&gt;

&lt;p&gt;&lt;img src="http://i.loveruby.net/ja/rhg/book/images/ch_minimum_reference.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;图 1: Ruby 的变量拥有对对象的引用&lt;/p&gt;

&lt;p&gt;这里我们注意到，一直在说的局部变量，那么这个局部必然是针对某个范围的局部。但是请稍等片刻我们再做解释。总之我们先可以说顶层也是一个局部的作用域。&lt;/p&gt;

&lt;p&gt;####常量
名称首字母是大写的称作常量。所谓常量就是只能代入赋值一次。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Const = "content"
PI = 3.1415926535

p(Const)   # 输出"content"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;带入两次的话会发生错误。虽然按道理是这样，但是实际运行的时候却不会有错误例外发生。这个是为了保证执行 ruby 程序的应用程序，比如说在同一个开发环境下，读入两个同样的文件的时候不至于产生错误。也就是说是为了实用性而不得不做出的牺牲，万不得已才取消了错误提示。实际上在 Ruby1.1 之前是会报错的。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C = 1
C = 2   # 实际上只会给出警告，最理想的还是要做成会显示错误
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来，被常量这个称呼欺骗的人肯定有很多。常量指的是"一旦保存了所要指向的对象就不再会改变"这个意思。而常量指向的对象并不是不会发生变化。如果用英语来说，比起 constant 这个意思，还是 read only 来的比较贴切。(图 2)。顺带一提，如果要使对象自身不发生变化可以用 freeze 这个方法来实现。&lt;/p&gt;

&lt;p&gt;&lt;img src="http://i.loveruby.net/ja/rhg/book/images/ch_minimum_const.jpg" title="" alt=""&gt;
图 2：常量是 read only 的意思&lt;/p&gt;

&lt;p&gt;另外这里还没有提到常量的作用域。我们会在下一节的类的话题中来说明。&lt;/p&gt;

&lt;p&gt;####流程控制&lt;/p&gt;

&lt;p&gt;Ruby 的流程控制结构很多要列举的话举不胜举。总之就介绍一下基本的 if 和 while。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if i &amp;lt; 10 then
  # 内容
end

while i &amp;lt; 10 do
  # 内容
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在条件语句中只有 false 和 nil 两个对象是假，其他所有对象的都属于真。当然 0 和空字符串也是真。&lt;/p&gt;

&lt;p&gt;顺带一提，只有 false 的话感觉不怎么美观，于是当然也有 true，当然 true 是属于真。&lt;/p&gt;

&lt;p&gt;最纯粹的面向对象的系统中，方法都是对象的附属物。但是那毕竟只是一个理想。在普通的程序中会有大量的拥有相同方法集合的对象。如果还傻傻的以对象为单位来调用方法那就会造成内存的大大的浪费。于是一般的方法是使用类或者 multi-method 来避免重复定义。&lt;/p&gt;

&lt;p&gt;ruby 采用了传统上来连接方法和对象的结构，类。也就是所有的对象都必须属于唯一的一个类，这个对象能够调用的方法也友这个类来决定。这个时候对象通常被叫做“某某类的实例”。&lt;/p&gt;

&lt;p&gt;例如"str"就是类 String 的实例。另外，String 这个类还定义了 upcase，downcase，strip 等一系列其他方法，仿佛就是在说所有的字符串对象都可以利用这些方法一样。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 大家都属于同一个String类于是拥有相同的方法。
       "content".upcase()
"This is a pen.".upcase()
    "chapter II".upcase()

       "content".length()
"This is a pen.".length()
    "chapter II".length()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么，如果调用的方法没有事前定义会发生什么呢？在静态的语言中，编译器会报错，而在 ruby 中，执行的时候会抛出例外。我们来实际尝试一下。这点长度的话直接在命令行用-e 就好了。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;% ruby -e '"str".bad_method()'
-e:1: undefined method `bad_method' for "str":String (NoMethodError)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;找不到方法的时候会发生 NomethodError 这个错误。&lt;/p&gt;

&lt;p&gt;另外最后，每次都说一遍类似“String 的 upcase 方法”太烦了，于是我们下面就用“String#upcase”来表示“在 String 的类中定义好的 upcase 方法”。&lt;/p&gt;

&lt;p&gt;顺带一提，如果写成“String.upcase”在 ruby 中就又是另一个意思了。&lt;/p&gt;
&lt;h3 id="类的定义"&gt;类的定义&lt;/h3&gt;
&lt;p&gt;到目前为止都是针对已经定义好的类。当然我们也可以自己来定义类。定义类的时候使用 class 关键字。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class C
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样就定义了 C 这个类。定义好之后可以有以下的使用。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class C
end
c = C.new()   # 生成类C的实例带入c中。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意生成实例的写法不是&lt;code&gt;new C&lt;/code&gt;。恩，好像&lt;code&gt;C.new&lt;/code&gt;这个写法在调用方法一样呢。如果你这样想，那说明你很聪明。Ruby 生成对象的时候仅仅是调用了方法而已。&lt;/p&gt;

&lt;p&gt;首先在 Ruby 里面类名和常量名是一个概念。那么类名和同名的常量的内容到底是什么呢？实际上里面是类。在 Ruby 中一切都是对象，那么当然类也是对象了。我们姑且将其称作类对象。所有的类对象都是 Class 这个类的实例。&lt;/p&gt;

&lt;p&gt;也就是说 class 这个写法，完成的是生成新的类对象的实例，并且将类名带入和它同名的常量中这一系列操作。同时，生成实例，实际上是参照常量名，对该类对象调用方法 (通常是 new 方法) 的操作。看完下面的例子你就应该会明白生成实例和普通的调用方法根本就是一回事。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;S = "content"
class C
end

S.upcase()  # 获取常量S所指的对象并调用upcase方法。
C.new()     # 获取常量C所指的对象并调用new方法。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此，在 Ruby 里面 new 并不是保留词。
另外我们也可以用 p 来显示刚刚生成的类的实例。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class C
end

c = C.new()
p(c)       # #&amp;lt;C:0x2acbd7e4&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当然不可能像字符串和整数那样漂亮地显示出来，显示出来的是类内部的 ID。顺带一提这个 ID 其实是指向对象的指针的值。&lt;/p&gt;

&lt;p&gt;对了。差点忘了说明一下方法名的写法。&lt;code&gt;Object.new&lt;/code&gt;是类对象 Object 调用自己的方法 new 的意思。&lt;code&gt;Object#new&lt;/code&gt;和&lt;code&gt;Object.new&lt;/code&gt;完全是两码事情，必须严格区分。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;obj = Object.new()   # Object.new
obj.new()            # Object#new
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在刚刚的例子中因为还没定义&lt;code&gt;Object#new&lt;/code&gt;方法所以第二行会报错。我们就当是一个写法的例子来理解就好了。&lt;/p&gt;

&lt;p&gt;###方法定义&lt;/p&gt;

&lt;p&gt;就算定义了类如果不定义方法的话一般没有什么太大意义。我们继续定义类 C 的方法。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class C
  def myupcase( str )
    return str.upcase()
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;定义类用的是 def。这个例子中定义了 myupcase 这个方法。这个方法带一个参数 str。和变量同样，没有必要写明返回值的类型和参数的类型。另外参数的个数是没有限制的。&lt;/p&gt;

&lt;p&gt;我们来使用一下定义好的方法。方法默认可以从外部调用。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;c = C.new()
result = c.myupcase("content")
p(result)   # 输出"CONTENT"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当然习惯了之后也没必要每次都带入。下面的写法也是同样的效果。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;p(C.new().myupcase("content"))   # 同样输出"CONTENT"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;####self&lt;/p&gt;

&lt;p&gt;在执行方法的过程中经常要保存自己是谁 (调用方法的实例) 这个信息。self 可以取得这个信息。在 C++ 或者 Java 中就是 this。我们来确认一下。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class C
  def get_self()
    return self
  end
end

c = C.new()
p(c)              # #&amp;lt;C:0x40274e44&amp;gt;
p(c.get_self())   # #&amp;lt;C:0x40274e44&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如上所示，两个语句返回的是用一个对象。也就是说对实例 c 调用方法的时候 slef 就是 c 自身。&lt;/p&gt;

&lt;p&gt;那么要如何才能对自身的方法进行调用呢？首先可以考虑通过 self。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class C
  def my_p( obj )
    self.real_my_p(obj)   # 调用自身的方法。
  end

  def real_my_p( obj )
    p(obj)
  end
end

C.new().my_p(1)   # 输出1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是调用自身的方法每次都要这么表示一下实在是太麻烦。于是可以省略掉 self，调用自身的方法的时候直接就可以省略掉调用方法的对象 (receiver)。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class C
  def my_p( obj )
    real_my_p(obj)   # 无需指定receiver
  end

  def real_my_p( obj )
    p(obj)
  end
end

C.new().my_p(1)   # 输出1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;####实例变量&lt;/p&gt;

&lt;p&gt;对象的实质就是数据 + 代码。这个说法表明，仅仅定义了方法还是不够的。我们需要以对象为单位来保存数据。也就是说我们需要实例变量。在 C++ 里面就是成员变量。&lt;/p&gt;

&lt;p&gt;Ruby 的变量命名规则很简单，是由第一个字符决定的。实例变量以@开头。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class C
  def set_i(value)
    @i = value
  end

  def get_i()
    return @i
  end
end

c = C.new()
c.set_i("ok")
p(c.get_i())   # 显示"ok"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;实例变量和之前的所介绍的变量稍微有点区别，不用代入 (也无需定义) 也可以可以引用。这个时候会是什么情形呢……我们在之前代码的基础上来试试看。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;c = C.new()
p(c.get_i())   # 显示nil
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在没有 set 的情况下调用 get，会显示 nil。nil 是表示什么都没有的对象。明明自身是个对象却表示什么都没有这个的确有点奇怪，但是它就是这么一个玩意儿。&lt;/p&gt;

&lt;p&gt;nil 也可以用序列来表示。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;p(nil)   # 显示nil
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="初始化"&gt;初始化&lt;/h4&gt;
&lt;p&gt;到目前为止，正如所见，就算是刚定义好的类只要调用 new 方法就可以生成实例。的确是这样，但是有时候类也需要特殊的初始化吧。这个时候我们就不是去改变 new，而是应该定义 initialize 这个方法。这样的话，在 new 中就会调用这个方法。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class C
  def initialize()
    @i = "ok"
  end
  def get_i()
    return @i
  end
end
c = C.new()
p(c.get_i())   # 显示"ok"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;严格意义上来说这个初始化只是 new 这个方法的特殊设计而不是语言层次上的特殊设计。&lt;/p&gt;

&lt;p&gt;继承&lt;/p&gt;

&lt;p&gt;类可以继承其他类。比如说 String 类就是继承的 Object 这个类。在本书中将用下图的箭头来表示。
&lt;img src="http://i.loveruby.net/ja/rhg/book/images/ch_minimum_supersub.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;上图的情况下，被继承的类 (Object) 叫做父类或者上层类，继承的类 (String) 叫做下层类或者子类。注意这个叫法和 C++ 有所区别。但和 Java 是一样的喊法。&lt;/p&gt;

&lt;p&gt;总之我们来尝试一下，我们来定义一个继承别的类的类。要定义一个继承其他类的类的时候有以下写法。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class C &amp;lt; SuperClassName
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;到目前为止，我们凡是没有标明父类的类的定义，其实继承的都是 Object 这个父类。&lt;/p&gt;

&lt;p&gt;接下来我们考虑一下为什么要继承，当然继承是为了继承方法了。所谓继承，仿佛就是再次重复了一下在父类中第一的方法。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class C
  def hello()
    return "hello"
  end
end

class Sub &amp;lt; C
end

sub = Sub.new()
p(sub.hello())   # 输出"hello"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;虽然 hello 方法是在类 C 中定义的，但是 Sub 这个类的实例可以调用这个方法。当然这次不需要带入变量。下面的写法也是同样的效果。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;p(Sub.new().hello())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只要在子类中定义相同名字的方法就可以重载。在 C++,Object Pascal(Delphi) 使用 virtual 之类的保留字明示除了指定的方法之外无法重载，而在 Ruby 中所有的方法都可以无条件地重载。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class C
  def hello()
    return "Hello"
  end
end

class Sub &amp;lt; C
  def hello()
    return "Hello from Sub"
  end
end

p(Sub.new().hello())   # 显示"Hello from Sub"
p(C.new().hello())     # 显示"Hello"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;另外类可以多层次继承。如图 4 所示。这个时候 Fixnum 继承了 Object 和 Numeric 以及 Integer 的所有方法。如果有同名的方法则以最近的类的方法为优先。不存在因类型不同而发生的重载所以使用条件非常简单。&lt;/p&gt;

&lt;p&gt;&lt;img src="http://i.loveruby.net/ja/rhg/book/images/ch_minimum_multiinherit.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;另外 C++ 中可以定义没有继承任何类的类，但是 Ruby 的类必然是直接或者间接继承 Object 类的。也就是说继承关系是以 Object 在最顶端的一棵树。比如说，基本库中的重要的类的继承关系树如图 5 所示。&lt;/p&gt;

&lt;p&gt;&lt;img src="http://i.loveruby.net/ja/rhg/book/images/ch_minimum_classtree.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;父类被定义之后绝对不会被改变，也就是说在类树中添加新的类的时候，类的位置不会发生变化也不会被删除。&lt;/p&gt;

&lt;p&gt;####变量的继承……？&lt;/p&gt;

&lt;p&gt;Ruby 中不继承变量 (实例变量)。因为就算想继承，也无法获取类中使用的变量的情报。&lt;/p&gt;

&lt;p&gt;但是只要是继承了方法那么调用继承的方法的时候 (以子类的实例) 会发生实例变量的带入。也就是说会被定义。这样的话实例变来那个的命名空间在各个实例中是完全平坦的，无论是哪个类的方法都可以获取。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class A
  def initialize()   # 在new的时候被调用
    @i = "ok"
  end
end

class B &amp;lt; A
  def print_i()
    p(@i)
  end
end

B.new().print_i()   # 显示"ok"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果无法理解这个行为那么干脆别去考虑类和继承。就想象一下如果类 C 存在实例 obj，那么 C 的父类的所有方法都在 C 中有定义。当然也要考虑到重载的罪责。然后现在把 C 的方法和 obj 衔接在一起。(图 6)&lt;/p&gt;

&lt;p&gt;这种强烈的真实感正是 Ruby 的面向对象的特征。&lt;/p&gt;

&lt;p&gt;&lt;img src="http://i.loveruby.net/ja/rhg/book/images/ch_minimum_objimage.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;####模块&lt;/p&gt;

&lt;p&gt;父类只能指定一个，也就是说 Ruby 表面上是单一继承的。实际上因为存在模块所以 Ruby 拥有多重继承的能力。下面我们就来介绍一下模块。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module M
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样就定义了模块。在模块里定义方法和类中定义完全一样。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module M
  def myupcase( str )
    return str.upcase()
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是因为模块无法生成对象所以是无法直接调用模块里面定义的方法的。那么该怎么办？恩将模块包含进其他的类里面就是了。这样的话模块就仿佛继承了类一样可以被操作了。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module M
  def myupcase( str )
    return str.upcase()
  end
end

class C
  include M
end

p(C.new().myupcase("content"))  # 显示"CONTENT"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;虽然类 C 根本没有定义任何方法但是可以调用 myupcase 这个方法。也就是说它继承了模块 M 的方法。包含的机能和继承完全是一样的，无论是定义方法还是获取实例变量都不受限制。&lt;/p&gt;

&lt;p&gt;模块无法指定父类。但是却可以包含其它模块。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module M
end

module M2
  include M
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是说这个机能去其实和指定父类是一样的。但是模块上边是不会有类的，模块可以包含的只能是模块。&lt;/p&gt;

&lt;p&gt;下面的例子包含了方法继承。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module OneMore
  def method_OneMore()
    p("OneMore")
  end
end

module M
  include OneMore

  def method_M()
    p("M")
  end
end

class C
  include M
end

C.new().method_M()         # 输出"M"
C.new().method_OneMore()   # 输出"OneMore"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Module 也可以和类一样的继承图。
&lt;img src="http://i.loveruby.net/ja/rhg/book/images/ch_minimum_modinherit.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;话说类 C 如果本身已经存在父类，那么这下子和模块的关系会变成什么样呢？请试着考虑下面的例子。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# modcls.rb

class Cls
  def test()
    return "class"
  end
end

module Mod
  def test()
    return "module"
  end
end

class C &amp;lt; Cls
  include Mod
end

p(B.new().test())   # "class"？ "module"？
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;类 C 继承了类 Cls，并且引入了 Mod 模块。这个时候应该表示"class"呢还是表示"module"呢？换句话说，模块和类哪一个距离更近呢？Ruby 的一切都可以向 Ruby 询问。于是我们来执行看一下结果。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;% ruby modcls.rb
"module"

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;比起父类模块的优先度更高呢！&lt;/p&gt;

&lt;p&gt;一般来说，在 Ruby 中引入模块的话，会在类和父类之间夹带产生一个继承关系，如下图所示。
&lt;img src="http://i.loveruby.net/ja/rhg/book/images/ch_minimum_modclass.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;在模块中又引入模块的话会有以下的关系。
&lt;img src="http://i.loveruby.net/ja/rhg/book/images/ch_minimum_modclass2.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;###程序 (2)&lt;/p&gt;

&lt;p&gt;注意，在本节中出现的内容非常重要，并且主要讲的是一些习惯静态语言思维的人很难习惯的部分。其他的内容可以一眼扫过，而这里请格外注意。我会比较详细地说明。&lt;/p&gt;

&lt;p&gt;####常量的嵌套&lt;/p&gt;

&lt;p&gt;首先复习一下常量。以大写字母开头的是常量。可以以如下方法定义。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Const = 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;要调用这个参数的时候可以如下。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;p(Const)   # 输出3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其实写成这样也可以。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;p(::Const)   # 同样输出3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在开头添加&lt;code&gt;：：&lt;/code&gt;表示是在顶层定义的常量。你可以类比一下文件系统的路径的表示方法。在 root 目录下有 vmunix 这个文件。如果是在"/"目录下的话直接输入 vmunix 就可以获取文件。当然也可以用完整的路径"/vmunix"来表示。&lt;code&gt;Const&lt;/code&gt;和&lt;code&gt;::Const&lt;/code&gt;也是同样道理。如果是在顶层的话直接用&lt;code&gt;Const&lt;/code&gt;就 ok，当然使用完整路径版本的&lt;code&gt;::Const&lt;/code&gt;也是可以的。&lt;/p&gt;

&lt;p&gt;那么类比文件系统目录的东西，在 Ruby 中是什么呢？那就是类的定义和模块的定义了。因为每次都这么说一遍的话太烦了于是下面统一就用类的定义语句来代替。在类的定义语句中常量的等级会随之上升。(类比进入文件系统的目录)&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class SomeClass
  Const = 3
end

p(::SomeClass::Const)   # 输出3
p(  SomeClass::Const)   # 同样输出3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;SomeClass 是在顶层定义的类，也是常量。于是写成&lt;code&gt;SomeClass&lt;/code&gt;和&lt;code&gt;::SomeClass&lt;/code&gt;都可以。在其中签到的常量&lt;code&gt;Const&lt;/code&gt;的路径就变成了&lt;code&gt;::SomeClass::Const&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;就像在目录中可以继续创建目录一样，在类中也可以继续定义类。例如&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class C        # ::C
  class C2     # ::C::C2
    class C3   # ::C::C2::C3
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么问题是，在类定义语句中定义的常量就必须要用完整的路径表示吗？当然不是了。就像文件系统一样，只要在同一层的类定义语句中，就不需要&lt;code&gt;::&lt;/code&gt;来获取。如下所示。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class SomeClass
  Const = 3
  p(Const)   # 输出3
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也许你会感到奇怪。居然在类的定义语句中可以直接书写执行语句。这个也是习惯动态语言的人相当不习惯的部分了。作者当初也是大吃一惊。&lt;/p&gt;

&lt;p&gt;姑且再补充说明一点，在方法中也可以获取常量。获取的规则和在类的定义语句中 (方法之外) 是一样的。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class C
  Const = "ok"
  def test()
    p(Const)
  end
end

C.new().test()   # 输出"ok"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;####全部执行&lt;/p&gt;

&lt;p&gt;这里从整体出发来写一段代码吧。Ruby 中程序的大部分都是会被执行的。常量定义，类定义语句，方法定义语句以及其他几乎所有的东西都会以你看到的顺序依次执行。&lt;/p&gt;

&lt;p&gt;例如，请看下面的代码。这段代码使用了目前为止说到的很多结构。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 1:  p("first")
 2:
 3:  class C &amp;lt; Object
 4:    Const = "in C"
 5:
 6:    p(Const)
 7:
 8:    def myupcase(str)
 9:       return str.upcase()
10:    end
11:  end
12:
13:  p(C.new().myupcase("content"))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个代码将会按照以下的顺序执行。&lt;/p&gt;

&lt;p&gt;1: p("first")   输出"first"&lt;/p&gt;

&lt;p&gt;3: &amp;lt; Object   通过常量 Object 获取 Object 类对象。  &lt;/p&gt;

&lt;p&gt;3: class C    生成以 Object 为父类的新的类对象，并将其代入 C&lt;/p&gt;

&lt;p&gt;4: Const = "in C"   定义::C::Const。值为"in C"  &lt;/p&gt;

&lt;p&gt;6: p(Const)   输出::C::Const 的值。输出结果为"in C"。  &lt;/p&gt;

&lt;p&gt;8: def myupcase(...)...end    定义方法 C#myupcase。  &lt;/p&gt;

&lt;p&gt;13: C.new().myupcase(...)   通过常量 C 调用其 new 方法，并且调用其结果的 myupcase 方法。  &lt;/p&gt;

&lt;p&gt;9: return str.upcase()    返回"CONTENT"。 &lt;/p&gt;

&lt;p&gt;13: p(...)    输出"CONTENT"。&lt;/p&gt;

&lt;p&gt;####局部常量的作用域&lt;/p&gt;

&lt;p&gt;终于说道了局部变量的作用域。&lt;/p&gt;

&lt;p&gt;顶层，类定义语句内部，模块定义语句内部，方法自身都各自拥有完全独立的局部变量作用域。也就是说在下面的程序中 Ivar 这个变量都不一样，也没有相互往来。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lvar = 'toplevel'

class C
  lvar = 'in C'
  def method()
    lvar = 'in C#method'
  end
end

p(lvar)   # 输出"toplevel"

module M
  lvar = 'in M'
end

p(lvar)   # 输出"toplevel"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;####表示上下文的 self&lt;/p&gt;

&lt;p&gt;以前执行方法的时候自己自身 (调用方法的对象) 会成为 self。虽然这个是正确的说法，但是话只说了一半。实际上不管是在 Ruby 程序执行到哪里，self 总会被具体赋值。也就是说在顶层和在类定义语句中都会有 self 存在。&lt;/p&gt;

&lt;p&gt;顶层是存在 self 的，顶层的 self 就是 main。没有什么奇怪的规则，main 就是 Object 的实例。其实 main 也仅仅是为了 self 而存在的，并没有什么深入的含义。&lt;/p&gt;

&lt;p&gt;也就是说顶层的 self 是 main，main 则是 Object 的实例，从顶层也可以直接调用 Object 的方法。另外在 Object 中引入了 Kernel 这个模块，里面存在着诸如&lt;code&gt;p&lt;/code&gt;和&lt;code&gt;puts&lt;/code&gt;一样函数风格的方法。于是在顶层也可以调用&lt;code&gt;p&lt;/code&gt;和&lt;code&gt;puts&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;img src="http://i.loveruby.net/ja/rhg/book/images/ch_minimum_Kernel.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;当然&lt;code&gt;p&lt;/code&gt;本来就不是函数而是方法。只是因为其定义在 Kernel 中，无论在哪里，换句话说无论 self 的类是什么，都可以被当作"自己的"方法来像函数一样被调用。所以 Ruby 在真正意义上不存在函数。存在的只有方法。&lt;/p&gt;

&lt;p&gt;顺带一提，出了&lt;code&gt;p&lt;/code&gt;和&lt;code&gt;puts&lt;/code&gt;之外，带有函数风格的方法还有&lt;code&gt;print puts printf
 sprintf gets forks exec&lt;/code&gt;等等等等。大多数都是仿佛曾经在哪里见过的名字。从这个命名中大概也可以想象得出 Ruby 的性格了吧。&lt;/p&gt;

&lt;p&gt;接下来，既然 self 在那里都会被设定那么在类的定义语句里面也是一样的。在类的定义中 self 就是类自身 (类对象)。于是就会变成这样。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class C
  p(self)   # C
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样设计有什么好处？实际上有个使其好处突出得十分明显的例子。请看。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module M
end
class C
  include M
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;实际上这个 include 是针对类对象 C 的调用。虽然我们还没有提及，但是很明显 Ruby 可以省略调用方法的括号。由于到目前还没有结束类定义语句的内容，所以作者为了尽量使其看起来不像方法的调用，而把括号给去掉了。&lt;/p&gt;

&lt;p&gt;####载入&lt;/p&gt;

&lt;p&gt;Ruby 中载入库的过程也是在执行中进行。通常这样写。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require("library_name")
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为了不被看到的假象所欺骗这里再说一句，require 其实是方法，甚至都不是保留语句。这样写的话在写的地方被执行，库里面的代码得以被执行。因为 Ruby 中不存在像 Java 里面的包的概念，如果想要将库的命名空间分开来的话，可以建立一个文件夹然后将库放到文件夹里面。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require("somelib/file1")
require("somelib/file2")
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;于是在库中也可以定义普通的类和模块。顶层的常量作用域和文件并没有多大关联，而是平坦的，于是从一开始就可以获取在其他文件中定义的类。如果想要用命名空间来区分类的名字，可以用下面的方法来显式嵌入模块。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# net 库的例子来使用模块命名空间来区分类
module Net
  class SMTP
    # ...
  end
  class POP
    # ...
  end
  class HTTP
    # ...
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;###就类的话题继续深入&lt;/p&gt;

&lt;p&gt;####还是关于常量&lt;/p&gt;

&lt;p&gt;到目前为止用文件系统的例子来类比了常量的作用域。现在请你完全忘掉刚才的例子。&lt;/p&gt;

&lt;p&gt;常量里面还有各种各样的陷阱。首先，我们可以获取"外层"类的常量。&lt;/p&gt;

&lt;p&gt;Const = "ok"
class C
  p(Const)   # 输出"ok"
end&lt;/p&gt;

&lt;p&gt;为什么要这样？因为可以方便使用命名空间来调用模块。到底怎么回事？我们来用之前 Net 库的类来做进一步说明。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module Net
  class SMTP
    # 在方法中可以使用Net::SMTPHelper
  end
  class SMTPHelper   # 辅助Net::SMTP的类
  end
end

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这种场合，在 SMTP 类中只需要写上&lt;code&gt;Net::SMTPHelper&lt;/code&gt;就可以进行调用。于是就有了"外层类如果可以调用的话就很方便了"这个结论。&lt;/p&gt;

&lt;p&gt;不管外层的类进行了多少层嵌套都可以调用。在不同的嵌套层次中如果定义了同名的常量，会调用从内而外最先找到的变量。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Const = "far"
class C
  Const = "near" # 这里的Const比最外层的Const更近一些
  class C2
    class C3
      p(Const)   # 输出"near"
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;真是太特么复杂了。&lt;/p&gt;

&lt;p&gt;我们来总结一下吧。在探索常量的时候，首先探索的是外层的类，然后探索父类。例如看下面这个故意写成这么扭曲的例子。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class A1
end
class A2 &amp;lt; A1
end
class A3 &amp;lt; A2
  class B1
  end
  class B2 &amp;lt; B1
  end
  class B3 &amp;lt; B2
    class C1
    end
    class C2 &amp;lt; C1
    end
    class C3 &amp;lt; C2
      p(Const)
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 C3 内部想要获取 Const 的话，会按照下图的顺序进行探索。&lt;/p&gt;

&lt;p&gt;&lt;img src="http://i.loveruby.net/ja/rhg/book/images/ch_minimum_constref.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;注意一点，外层的类的父类，例如 A1 和 B2 是不会被探索的。探索的时候所谓的外层仅仅是向外层，父类的话也仅仅是朝着父类的方向。如果不这样的话，会造成探索的类过多，而无法正确预测这个复杂的行为。&lt;/p&gt;

&lt;p&gt;####meta 类&lt;/p&gt;

&lt;p&gt;我们说过对象可以调用方法。调用的方法则是对象的类所决定的。那么类对象也一定存在自己所属的类。&lt;/p&gt;

&lt;p&gt;&lt;img src="http://i.loveruby.net/ja/rhg/book/images/ch_minimum_classclass.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;这个时候直接和 Ruby 确认是最好的方法。返回自己所属的类的方法是&lt;code&gt;Object#class&lt;/code&gt;。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;p("string".class())   # 输出String
p(String.class())     # 输出Class
p(Object.class())     # 输出Class
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;String 貌似属术语 Class 这个类的。那么进一步 Class 所属的类是什么呢？&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;p(Class.class())      # 输出Class
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;又是 Class。也就是说不管是什么对象，沿着&lt;code&gt;.class().class().class()……&lt;/code&gt;总会到达 Class，然后陷入循环以致最后。&lt;/p&gt;

&lt;p&gt;&lt;img src="http://i.loveruby.net/ja/rhg/book/images/ch_minimum_ccc.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;Class 是类的类。我们称拥有"xx 的 xx"的这种递归构造的的东西"meta xx"。所以 Class 也被叫做"meta class(类)"。&lt;/p&gt;

&lt;p&gt;####meta 对象&lt;/p&gt;

&lt;p&gt;接下来我们改变目标，来考察一下模块。模块也是对象，当然会有其所属的类了。我们来调查一下。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module M
end
p(M.class())   # 输出Module
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;模块对象的类貌似是 Module。那么 Module 类对象类是什么呢？&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;p(Module.class())   # Class
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;答案又是 Class。&lt;/p&gt;

&lt;p&gt;接下来还个方向继续调查其中的继承关系。Class 和 Module 的父类到底是什么？Ruby 中可以通过&lt;code&gt;Class#superclass&lt;/code&gt;来进行查看。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;p(Class.superclass())    # Module
p(Module.superclass())   # Object
p(Object.superclass())   # nil
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Class 居然是 Module 下层的类。根据以上事实可以得到 Ruby 中重要类的关系图。&lt;/p&gt;

&lt;p&gt;&lt;img src="http://i.loveruby.net/ja/rhg/book/images/ch_minimum_metaobjects.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;到目前为止我们没有做任何说明就是用了 new 和 include 这些东西。现在终于可以解释清楚他们的真实面貌了。new 实际上是 Class 类定义的方法。所以无论是什么类 (因为都是 Class 类的实例) 都可以使用 new。但是 Module 里面没有定义 new 所以无法生成实例。同样，include 被定义在 Module 类中，所以不管是类还是模块都可以调用 include。&lt;/p&gt;

&lt;p&gt;####奇异方法&lt;/p&gt;

&lt;p&gt;对象可以调用方法，能调用的方法由对象所属的类来决定，到目前为止我们都这么说。但是作为设计理念我们还是希望方法都是属于对象的。说到底只是因为同样的类定义同样的方法可以省去不少麻烦。&lt;/p&gt;

&lt;p&gt;于是实际上在 Ruby 中是存在不通过类而直接给对象定义的方法的机制的。可以这样写。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;obj = Object.new()
def obj.my_first()
  puts("My first singleton method")
end
obj.my_first()   # My first singleton method
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;正如你已了解到，Object 是所有类的根目录。这么重要的类里面是不会轻易让你定义 my_first 这种奇怪名字的方法的。另外 obj 是 Object 的实例。但是却可以通过 obj 这个实例来调用 my_first 这个方法。也就是说，我们定义了和所属类完全无关的方法。这种给对象定义的方法我们称为奇异方法 (singleton method)。&lt;/p&gt;

&lt;p&gt;那么什么时候使用奇异方法呢？首先是像 Java 和 C++ 一样定义静态方法的时候。也就是说不需要生成实例就可以使用的方法。这种方法在 Ruby 里面作为 Class 类对象的奇异方法而存在。&lt;/p&gt;

&lt;p&gt;例如 UNIX 中存在 unlink 这种系统调用来删除文件的别名。Ruby 中这个方法作为 File 类的奇异方法可以直接被使用。我们来试一试。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;File.unlink("core")  # 消去core名称的dump。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每次提到都说一遍"这个是 File 对象的奇异方法 unlink"实在是太麻烦了，下面用"File.unlink"代替。注意不要写成"File#unlink"或者是将"在 File 类中定义的方法 write"错写成"File.write"。&lt;/p&gt;

&lt;p&gt;下面是写法的总结&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th style="text-align:center;"&gt;写法&lt;/th&gt;
&lt;th style="text-align:center;"&gt;调用的对象&lt;/th&gt;
&lt;th style="text-align:center;"&gt;调用的实例&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;File.unlink&lt;/td&gt;
&lt;td style="text-align:center;"&gt;File 类自身&lt;/td&gt;
&lt;td style="text-align:center;"&gt;File.unlink("core")&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;File#write&lt;/td&gt;
&lt;td style="text-align:center;"&gt;File 的实例&lt;/td&gt;
&lt;td style="text-align:center;"&gt;f.write("str")&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;####类变量&lt;/p&gt;

&lt;p&gt;类变量是 Ruby1.6 新增的内容。类变量和变量属于同一个类，可以被类以及类的实例代入，引用。来看一下例子。开头是"@@"就是类变量。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class C
  @@cvar = "ok"
  p(@@cvar)      # 输出"ok"

  def print_cvar()
    p(@@cvar)
  end
end

C.new().print_cvar()  # 输出"ok"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;类变量也是由最初的定义来赋值代入的，所以在代入之前引用出错。仿佛多加了个"@"就和一般的实例变量不一样了。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;% ruby -e '
class C
  @@cvar
end
'
-e:3: uninitialized class variable @@cvar in C (NameError)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这里直接用"-e"命令执行了程序。"'"之间三行是程序正文。&lt;/p&gt;

&lt;p&gt;另外类变量是可以继承的。换句话说，子类可以代入，引用父类的类变量。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class A
  @@cvar = "ok"
end

class B &amp;lt; A
  p(@@cvar)            # "ok"
  def print_cvar()
    p(@@cvar)
  end
end

B.new().print_cvar()   # "ok"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;####全局变量&lt;/p&gt;

&lt;p&gt;最后姑且说一句，全局变量也是存在的。无论在程序的哪里都可以代入，引用。在变量前加上"$"就成了全局变量。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$gvar = "global variable"
p($gvar)   # "global variable"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;全局变量和实例变量一样，我们可以看作所有的名称都是在代入之前就被定义好的。所以就算是代入前我们引用这个变量，只会返回 nil 而不会发生错误。&lt;/p&gt;

&lt;p&gt;(第一章完)&lt;/p&gt;

&lt;p&gt;##第二章 对象&lt;/p&gt;

&lt;p&gt;###对象&lt;/p&gt;

&lt;p&gt;####点睛&lt;/p&gt;

&lt;p&gt;本章开始我们终于要开始探索 ruby 的源码了。首先按照预告我们先从对象的构造开始。&lt;/p&gt;

&lt;p&gt;接下来我们来考虑一下对象作为对象而成立的必要条件吧。
之前已经说明过几次对象到底是什么，其实作为对象不可缺少的条件有三个，分别是&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;可以区别自己和外界。(拥有识别标志)&lt;/li&gt;
&lt;li&gt;能够对外界的行动作出反应。(方法)&lt;/li&gt;
&lt;li&gt;拥有内部状态。(实例变量)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;本章会对这三个特征顺序确认。&lt;/p&gt;

&lt;p&gt;主要注目的文件是 ruby.h，其他诸如 object.c, class.c, variable.c 也会稍微关注。&lt;/p&gt;

&lt;p&gt;####value 和对象的结构体&lt;/p&gt;

&lt;p&gt;ruby 中对象的实体是由结构体来表现的。各种处理经常要和指针打交道。
结构体根据不同的类会使用不同的类型，但是指针无论是在哪个结构体中
都是 VALUE 类型。
&lt;img src="http://i.loveruby.net/ja/rhg/book/images/ch_object_value.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;VALUE 的定义如下。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  71  typedef unsigned long VALUE;
(ruby.h)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;VALUE 实际上被用来强制转换成各种对象结构体的指针。所以当指针的长度和 unsigned long 不一致的时候 ruby 就无法正常工作。严格意义上来说，如果存在比 sizeof(unsigned long) 更长的指针类型 ruby 会无法正常工作。当然最近的系统基本上都符合上述要求，过去不符合这种要求的机器还是很多的。&lt;/p&gt;

&lt;p&gt;至于结构体，也是有很多种类。主要是按照对象的类来进行区别。&lt;/p&gt;

&lt;p&gt;struct RObject      凡是不符合以下条件的  &lt;/p&gt;

&lt;p&gt;struct RClass       Class 对象 &lt;/p&gt;

&lt;p&gt;struct RFloat       小数&lt;/p&gt;

&lt;p&gt;struct RString      字符串   &lt;/p&gt;

&lt;p&gt;struct RArray       数组&lt;/p&gt;

&lt;p&gt;struct RRegexp      正则表达式 &lt;/p&gt;

&lt;p&gt;struct RHash        哈希表   &lt;/p&gt;

&lt;p&gt;struct RFile        IO、File、Socket 之类    &lt;/p&gt;

&lt;p&gt;struct RData        上述以外所有用 C 语言定义的类    &lt;/p&gt;

&lt;p&gt;struct RStruct      Ruby 的结构体 Struct 类   &lt;/p&gt;

&lt;p&gt;struct RBignum      大整数&lt;/p&gt;

&lt;p&gt;我们来看几个对象结构体的定义的例子。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      /* 用作一般对象的结构体 */
 295  struct RObject {
 296      struct RBasic basic;
 297      struct st_table *iv_tbl;
 298  };

      /* 字符串的结构体（String实例） */
 314  struct RString {
 315      struct RBasic basic;
 316      long len;
 317      char *ptr;
 318      union {
 319          long capa;
 320          VALUE shared;
 321      } aux;
 322  };

      /* 数组（Arrayの实例）的结构体 */
 324  struct RArray {
 325      struct RBasic basic;
 326      long len;
 327      union {
 328          long capa;
 329          VALUE shared;
 330      } aux;
 331      VALUE *ptr;
 332  };

(ruby.h)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们先把逐个详细的说明放到后面，首先从整体层面上来说。&lt;/p&gt;

&lt;p&gt;首先 VALUE 被定义成 unsigned long 类型，要作为指针使用就必须进行强制转换。&lt;/p&gt;

&lt;p&gt;于是各个对象的构造函数就配有 Rxxxx() 这种形式的宏。比如 struct RString 的宏就是 RSTING(),struct RArray 的宏是 RARRAY()。这些宏的用法如下。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VALUE str = ....;
VALUE arr = ....;
RSTRING(str)-&amp;gt;len;   /* ((struct RString*)str)-&amp;gt;len */
RARRAY(arr)-&amp;gt;len;    /* ((struct RArray*)arr)-&amp;gt;len */
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来我们看到所有的对象结构体的开头都会有一个 struct RBasic 类型的成员 basic 存在。结果就是无论 VALUE 是指向何种对象的结构体的指针，只要被强制转换成 struct RBasic*，就可以访问 basic 的内容。&lt;/p&gt;

&lt;p&gt;&lt;img src="http://i.loveruby.net/ja/rhg/book/images/ch_object_rbasic.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;既然这么费劲心思作出这种设计，struct RBasic 一定存放着 Ruby 对象的重要信息。我们来看 struct RBasic 的定义。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 290  struct RBasic {
 291      unsigned long flags;
 292      VALUE klass;
 293  };

(ruby.h)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;flags 是拥有多种目的的标志，最重要的用途就是保存结构体的类型 (struct RObject 之类)。表示类型的标志用 T_xxxx 来定义。可以从 VALUE 通过宏 TYPE() 访问。比如下面的例子&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VALUE str;
str = rb_str_new();    /* 生成ruby的字符串(对应结构体为RSting) */
TYPE(str)              /* 返回值为T_STRING */

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这些标志都是 T_xxxx 的形式，struct RString 的话就是 T_STRING，struct RArray 的话就是 T_ARRAY，对应方式十分规则。&lt;/p&gt;

&lt;p&gt;struct RBasic 的另一个成员 klass 保存的是对象所属的类。klass 的类型是 VALUE，足以说明其保存的是 Ruby 的对象 (其实是对象的指针)。也就是说这个就是 class 类的对象了。&lt;/p&gt;

&lt;p&gt;&lt;img src="http://i.loveruby.net/ja/rhg/book/images/ch_object_class.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;对象和类的关系在本章“方法”这一小节中会详细说明。&lt;/p&gt;

&lt;p&gt;顺带一提成员名用 klass 代替 class 是为了防止用 C++ 的编译器编译时和保留词 class 发生冲突。&lt;/p&gt;

&lt;p&gt;####关于结构体的类型&lt;/p&gt;

&lt;p&gt;我们说过 struct Basic 的成员 flags 保存了结构体的类型。可是为什么必须保存结构体的类型呢？这是因为所有类型的结构体都是通过 VALUE 来处理的。当指向结构体的指针被转换成 VALUE 之后变量中已经不存在类型的信息，编译器也是不会特殊照顾的。于是我们只有自己管理好各自的类型了。这个也是针对所有结构体类型统一管理的一大缺陷。&lt;/p&gt;

&lt;p&gt;那么，既然使用的结构体是由类决定的，那么为什么还要把结构体和类分开保存呢？直接从类中访问结构体的类型不就好了吗？不这样做有两个理由。&lt;/p&gt;

&lt;p&gt;第一，抱歉我们要推翻刚才说过的话。实际上存在着不具有 struct RBasic 的结构体 (即不存在 klass 成员)。比如说将在第二部分登场的 struct RNode。但是这种特殊的结构体开头还是会有一个 flags 类型的成员。所以所有结构体只需要拥有 flags 就可以进行统一管理。&lt;/p&gt;

&lt;p&gt;第二，其实类和结构体不是一一对应的。比如说用户用 Ruby 语言定义的类的实例全部是使用 struct RObject 来保存。如果要从类中访问结构体的类型就必须记录下所有类和结构体的对应关系。那还不如直接将类的信息放入结构体中来的快捷便利。&lt;/p&gt;

&lt;p&gt;basic.flags 的用途&lt;/p&gt;

&lt;p&gt;谈到 basic.flags 的用途，刚才一直说是保存结构体类型，这种说法有点恶心我们还是用图来进行一下说明。此图仅仅是为了在之后遇到疑惑的时候可以方便参考，现在还不必全部理解。&lt;/p&gt;

&lt;p&gt;！&lt;a href="http://i.loveruby.net/ja/rhg/book/images/ch_object_flags.jpg" rel="nofollow" target="_blank" title=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;仅仅是看图的话我们会发现 32 比特中还有 21 比特的空余。其实那些部分被定义为 FL_USER0~FL_USER8 这一系列的标志，根据结构体不同使用目的也不相同。上图为了做示范把 FL_USER0 也放了进去。&lt;/p&gt;

&lt;p&gt;###VALUE 的填充对象&lt;/p&gt;

&lt;p&gt;我们说过 VALUE 本质上是 unsigned long。因为 VALUE 仅仅是指针，貌似 void*也能胜任，实际上不这样做是有理由的。因为 VALUE 也有不是指针的时候。非指针的 VALUE 存在以下六种情况。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;数值较小的整数&lt;/li&gt;
&lt;li&gt;符号 (symbol)&lt;/li&gt;
&lt;li&gt;true&lt;/li&gt;
&lt;li&gt;false&lt;/li&gt;
&lt;li&gt;nil&lt;/li&gt;
&lt;li&gt;Qundef&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我们按顺序来说。&lt;/p&gt;

&lt;p&gt;####数值较小的整数&lt;/p&gt;

&lt;p&gt;Ruby 中一切都是对象所以整数也是对象。但是整数的实例是在是太多了，如果每个整数都用一个结构体来表示的话那运行速度就太慢了。假如要递加从 0 到 50000 的整数，仅仅如此就要生成 50000 个对象的话会让人一瞬间陷入犹豫。&lt;/p&gt;

&lt;p&gt;那么我们来实际看一下 C 中奖 int 类型变换成 Fixnum 的宏 INT2FIX 吧，我们来确认一下 Fixnum 的确是被填埋到了 VALUE 里面。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 123  #define INT2FIX(i) ((VALUE)(((long)(i))&amp;lt;&amp;lt;1 | FIXNUM_FLAG))
 122  #define FIXNUM_FLAG 0x01

(ruby.h)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;左移一位之后和 1 相或。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;110100001000        变换前   
1101000010001       变换后   
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;就是如此，保证了保存 Fixnum 的 VALUE 总是奇数。另一方面，Ruby 对象结构体内存空间的申请使用的是 malloc()。一般总是会被分配到 4 的倍数的地址。所以地址的值和保存 Fixnum 的 VALUE 值的范围是不会重叠的。&lt;/p&gt;

&lt;p&gt;另外，将 int 和 long 变换成 VALUE 还有其他一些宏，比如 INT2NUM(),lONG2NUM()。它们都是以“○○2○○”的形式，NUM 的话可以同时处理 Fixnum 和 Bignum。比如 INT2NUM() 会把超过 Fixnum 范围的数转换成 Bignum。NUM2INT() 会把 Fixnum 和 Bignum 都转换成 int 类型。如果超出 int 的范围会发生例外，没有必要在这里进行越界检测。&lt;/p&gt;

&lt;p&gt;####符号 (symbol)&lt;/p&gt;

&lt;p&gt;符号是什么？&lt;/p&gt;

&lt;p&gt;这个问题比较麻烦，我们先来说为什么需要符号。首先我们知道 ruby 内部存在着 ID 类型的变量。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  72  typedef unsigned long ID;
(ruby.h)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个 ID 是和任意的字符串一一对应的整数。虽然话这么说，但是也不可能和这个世界上所有的字符串都一一对应吧？这种对应关系仅仅是存在于"ruby 的进程之中"。关于 ID 的访问方法我们在下一章"名称和命名表"中讲述。&lt;/p&gt;

&lt;p&gt;语言处理的程序需要处理大量的名字。变量名，常量名，类名，文件名等等。这些大量的名字如果用 char*来保存处理实在是太不容易了。如果你硬要问是哪里不容易，我可以告诉你那就是除了内存管理还是内存管理。另外名字的比较也经常会发生，如果每次都比较字符串是否相符的话实在是效率太低。于是我们不直接处理字符串，而是将其对应到别的东西上面来处理。而那“别的东西”就是整数了。因为整数的处理是最简单的。&lt;/p&gt;

&lt;p&gt;将ID带入到ruby的世界中的正是符号。ruby1.4之前直接将ID的值转换成Fixnum作为符号使用。现在也可以通过Symbol#to_i来访问它的值。然而在实际运用中逐渐发现把符号当作Fixnum处理实在不太妥当，于是在ruby1.6之后就有了独立的符号类Symbol了。&lt;/p&gt;

&lt;p&gt;符号对象由于经常作为哈希表的键使用所以数量非常多。于是 Symbol 就和 Fixnum 一样被填埋进了 VALUE 里面。我们来看一下将 ID 转换成 Symbol 的宏 ID2SYM()。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 158  #define SYMBOL_FLAG 0x0e
 160  #define ID2SYM(x) ((VALUE)(((long)(x))&amp;lt;&amp;lt;8|SYMBOL_FLAG))

(ruby.h)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;左移 8bit 就相当于乘上 256，也就是 4 的倍数。然后和 0x0e 相或 (这个时候和加法没区别)，这样的话最终结果也不会是 4 的倍数。当然也不会是奇数，也就是说不会和其他的 VALUE 类型相重叠。真是巧妙的方法。&lt;/p&gt;

&lt;p&gt;最后我们来看一下 ID2SYM() 的逆变化 SYM2ID()。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 161  #define SYM2ID(x) RSHIFT((long)x,8)
(ruby.h)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;RSHIFT 是右移。右移根据平台不同会出现整数的符号剩余，不剩余的区别，所以保险起见我们用宏来代替。&lt;/p&gt;

&lt;p&gt;####true false nil&lt;/p&gt;

&lt;p&gt;这三个是 Ruby 里面特殊的对象。分别是代表逻辑真，逻辑假，和没有对象的对象。这三者的 c 语言中的值定义如下。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 164  #define Qfalse 0        /* Rubyのfalse */
 165  #define Qtrue  2        /* Rubyのtrue */
 166  #define Qnil   4        /* Rubyのnil */

(ruby.h)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这次竟然是偶数了。但是要注意 0 和 2 作为指针使用是不可能的。所以不必担心和其他的 VALUE 值重复。因为虚拟内存空间第一块地址通常不会被分配。同时也是为了当想要访问 NULL 指针的时候程序能够迅速得出错。&lt;/p&gt;

&lt;p&gt;另外 Qfalse 因为值为 0 所以才 c 语言层次也是被当作逻辑假来使用的。实际上在 ruby 的返回逻辑真假值的函数中，返回值会被转换成 VALUE 或者 init 然后返回 Qtrue/Qfalse。这种做法很常见。&lt;/p&gt;

&lt;p&gt;至于 Qnil，有专门针对 VALUE 判断其是否为 Qnil 的宏。NIL_P()&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 170  #define NIL_P(v) ((VALUE)(v) == Qnil)

(ruby.h)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;~p 这种命名方式术语 lisp 风格。其表示进行的处理是返回真值的行为。也就是说 NIL_P 其意思"为参数是否为 nil？" p 来自于 predicate。(断言/谓语)。这种命名规则在 ruby 中被广泛使用。&lt;/p&gt;

&lt;p&gt;另外 ruby 中除了 false 和 nil 是假其他都是真。但是 c 中的话 nil(Qnil) 也是真。于是用 c 语言判定 Ruby 表达式真假的宏 RTEST() 应该如下。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;169  #define RTEST(v) (((VALUE)(v) &amp;amp; ~Qnil) != 0)

(ruby.h)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Qnil 只有第三位的比特是 1,~P 取反之后只有第三位是 0。与其 bit 和之后结果是真的只有 Qfalse 和 Qnil。&lt;/p&gt;

&lt;p&gt;加上!=0 是为了确保结果是 0 或者 1。因为 glib 这个库要求真值只能是 0 或者 1。（[ruby-dev:11049]）&lt;/p&gt;

&lt;p&gt;说来 Qnil 的 Q 是什么玩意儿？是 R 的话还可以理解，为什么是 Q 呢？向人询问的结果是因为 emacs 中是这样的。比预料中有趣呢。&lt;/p&gt;
&lt;h4 id="Qundef"&gt;Qundef&lt;/h4&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 167  #define Qundef 6                /* undefined value for placeholder */

(ruby.h)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个值在解释器内部作为“未定义值”来使用。在 Ruby 中是不会出现的。&lt;/p&gt;

&lt;p&gt;###方法&lt;/p&gt;

&lt;p&gt;Ruby 对象最重要的性质要数拥有自我身份，能够调用方法，以及按照实例存有数据这三个方面了。这个小节我们来讲第二点，对象和方法结合的方式。&lt;/p&gt;

&lt;p&gt;####struct RClass&lt;/p&gt;

&lt;p&gt;在 ruby 中类也是作为对象存在的。那当然类的对象的实体也需要一个结构体。这个结构体就是 struct RClass 了。这个结构体类型的 flag 为 T_CLASS。&lt;/p&gt;

&lt;p&gt;另外类和模块基本属于同一概念所以没有必要区分各自的实体。于是模块的结构体也是用 struct RClass 来表现的。模块的结构体 flag 被设置成 T_MODULE 来进行区别。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 300  struct RClass {
 301      struct RBasic basic;
 302      struct st_table *iv_tbl;
 303      struct st_table *m_tbl;
 304      VALUE super;
 305  };

(ruby.h)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;首先注意到 m_tbl(Method Table) 这个成员。struct st_table 是 ruby 中到处可见的哈希表。详细会在下一章“名称与命名表”中说明，总之就当作他是记录一对一关系的东西就好了。m_tbl 就是用来记录这个类所有的方法的名称 (ID) 和方法实体直接对应关系的。关于 method 实体的构造方法我们会在第二部，第三部进行解说。&lt;/p&gt;

&lt;p&gt;接下来第四个成员 super 正如字面意思，保存着父类的信息。因为是 VALUE 所以指向的是父类的类的对象 (指针)。Ruby 中不存在父类的类只有 Ojbect。(译者注：在 ruby1.6 之前是如此)&lt;/p&gt;

&lt;p&gt;实际上 Object 里面的所有方法都定义在 Kernel 这个模块中。Object 只是将其引入。这个之前我们也已经讲过了。模块的功能几乎和多重继承相当，一见似乎只是用 super 的话无法表现一些复杂的关系，ruby 中正是用了巧妙的方法使其看起来只是单一继承。这个操作我们会在第四章“类和模块”中说明。&lt;/p&gt;

&lt;p&gt;另外受这个变化的影响 Object 的结构体的 super 的内容是 Kernel 实体的 struct Rclass，后者的 super 被定义成 NULL。换句话说，super 如果是 NULL 的话 RClass 就是 Kernel 的实体。&lt;/p&gt;

&lt;p&gt;&lt;img src="http://i.loveruby.net/ja/rhg/book/images/ch_object_classtree.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;####方法的搜索&lt;/p&gt;

&lt;p&gt;既然类呈现这样的构造方式那么该如何调用方法也可以很容易想象了。首先探索对象类的 m_tbl，如果没有找到就继续顺着 super 在父类中的 m_tbl 中寻找，依次回溯。也就是说如果知道 Object 也没有找到该方法，那么该方法就是还未定义。&lt;/p&gt;

&lt;p&gt;按照以上的顺序来探索 m_tbl 的方法请见 search_method()。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 256  static NODE*
 257  search_method(klass, id, origin)
 258      VALUE klass, *origin;
 259      ID id;
 260  {
 261      NODE *body;
 262
 263      if (!klass) return 0;
 264      while (!st_lookup(RCLASS(klass)-&amp;gt;m_tbl, id, &amp;amp;body)) {
 265          klass = RCLASS(klass)-&amp;gt;super;
 266          if (!klass) return 0;
 267      }
 268
 269      if (origin) *origin = klass;
 270      return body;
 271  }

(eval.c)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个函数在类对象 klass 中寻找名称为 id 的方法。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;RCLASS(value)&lt;/code&gt;的内容为&lt;code&gt;((struct RClass*)(value))&lt;/code&gt;的宏。&lt;/p&gt;

&lt;p&gt;st_lookup() 是用来在 st_table 中检索和键对应的值的函数。如果找到值就返回真，并将找到的值写入第三个地址参数 (body)。这边这些函数在卷尾的函数参照中会有记载，如有需要可以随时参考。&lt;/p&gt;

&lt;p&gt;话说来每次都进行探索的话实在是太慢了。实际上被调用的参数会被保存到缓存中，第二次就不必每次都用 super 方法来回溯寻找了。包括缓存的搜索我们会在第十五章“方法”中进行介绍。&lt;/p&gt;

&lt;p&gt;###实例变量&lt;/p&gt;

&lt;p&gt;这章节我们介绍成为对象必须条件的第三点，实例变量的实装。&lt;/p&gt;

&lt;p&gt;####rb_ivar_set()&lt;/p&gt;

&lt;p&gt;实例变量是一种以对象为单位存放其特有的数据的方式。既然是对象特有那么好像将数据保存到对象内部 (对象的结构体) 会比较好？那么实际上是个什么情况呢？我们来看一下将实例变量带入对象中的函数 rb_ivar_set() 一探究竟。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      /* 向obj对象的成员变量id中代入val */
 984  VALUE
 985  rb_ivar_set(obj, id, val)
 986      VALUE obj;
 987      ID id;
 988      VALUE val;
 989  {
 990      if (!OBJ_TAINTED(obj) &amp;amp;&amp;amp; rb_safe_level() &amp;gt;= 4)
 991          rb_raise(rb_eSecurityError,
                       "Insecure: can't modify instance variable");
 992      if (OBJ_FROZEN(obj)) rb_error_frozen("object");
 993      switch (TYPE(obj)) {
 994        case T_OBJECT:
 995        case T_CLASS:
 996        case T_MODULE:
 997          if (!ROBJECT(obj)-&amp;gt;iv_tbl)
                  ROBJECT(obj)-&amp;gt;iv_tbl = st_init_numtable();
 998          st_insert(ROBJECT(obj)-&amp;gt;iv_tbl, id, val);
 999          break;
1000        default:
1001          generic_ivar_set(obj, id, val);
1002          break;
1003      }
1004      return val;
1005  }

(variable.c)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;rb_raise() 和 rb_error_frozen() 都是错误检测。这之后我们会反复强调，错误检测虽然在现实中是需要的，但是不是处理的本质。所以第一次读代码的时候可以将错误处理完全忽略。&lt;/p&gt;

&lt;p&gt;去掉了错误处理之后就只剩下了 swtich 语句。类似&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;switch (TYPE(obj)) {
  case T_aaaa:
  case T_bbbb:
     ：
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;的形式是 ruby 特有的书写习惯。TYPE() 返回对象构造体的类型 (T_OBJECT 和 T_STRING 之类) 的宏。类型 flag 因为是整数所以完全可以使用 switch 分支。Fixnum 和 Symbol 虽然不存在构造体，但是会在 TYPE() 中 j 进行特殊处理进而返回 T_FIXNUM 和 T_SYMBOL，所以大可不必担心。&lt;/p&gt;

&lt;p&gt;接下来我们回到&lt;code&gt;rb_ivar_set()&lt;/code&gt;。好像就只有 T_OBJECT T_CLASS T_MODULE 这三个类型的处理是分开来单独进行的。这三个被选中是因为他们的结构体的第二个成员是 iv_tbl。我们来实际确认一下。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    /* TYPE(val) == T_OBJECT */
 295  struct RObject {
 296      struct RBasic basic;
 297      struct st_table *iv_tbl;
 298  };

      /* TYPE(val) == T_CLASS or T_MODULE */
 300  struct RClass {
 301      struct RBasic basic;
 302      struct st_table *iv_tbl;
 303      struct st_table *m_tbl;
 304      VALUE super;
 305  };

(ruby.h)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;iv_tbl 对应的是 Instance Variable Table，也就是实例变量表。里面记录的是实例变量名和对应的值。&lt;/p&gt;

&lt;p&gt;我们再来贴一下 rb_ivar_set() 中，当结构体拥有 iv_tbl 成员时的处理代码。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (!ROBJECT(obj)-&amp;gt;iv_tbl)
    ROBJECT(obj)-&amp;gt;iv_tbl = st_init_numtable();
st_insert(ROBJECT(obj)-&amp;gt;iv_tbl, id, val);
break;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ROBJECT() 是用来将 VALUE 强制转换成 struct RObject*的宏。obj 指向的也有可能是 struct Rclass，但是仅仅是访问第二成员变量的话是不会发生什么问题的。&lt;/p&gt;

&lt;p&gt;st_init_numtable() 是新生成 st_table 的函数。st_insert() 是在 st_table 中生成关联的函数。&lt;/p&gt;

&lt;p&gt;综上所述这段代码所做的事情，就是当 iv_table() 不存在的时候新建一个，然后将对应的"变量名=&amp;gt;对象"记录到其中。&lt;/p&gt;

&lt;p&gt;注意一点，struct Rclass 自身是类对象的结构体，所以其变量表也是类对象自己的东西。用 ruby 程序来说，就如下面这种情况。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class C
  @ivar = "content"
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;####generic_ivar_set()&lt;/p&gt;

&lt;p&gt;向 T_OBJECT T_MODULE T_CLASS 之外的结构体代入变量会是怎么样呢？&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#没有iv_tbl的结构体
1000  default:
1001    generic_ivar_set(obj, id, val);
1002    break;

(variable.c)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个时候处理会交给 generic_ivar_set()。看具体的函数之前先把大框架说明一下吧。&lt;/p&gt;

&lt;p&gt;T_OBJECT T_MODULE T_CLASS 以外的构造体是没有 iv_tbl 成员的 (之后会说明为什么没有的理由)。但是就算是没有该成员也可以通过别的手段将实例和 struct st_table 对应起来。ruby 将这种对应关系保存在全局的 st_table，即 generic_iv_table 中来解决此问题。&lt;/p&gt;

&lt;p&gt;&lt;img src="http://i.loveruby.net/ja/rhg/book/images/ch_object_givtable.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;我们来看一下实际的代码。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 801  static st_table *generic_iv_tbl;

 830  static void
 831  generic_ivar_set(obj, id, val)
 832      VALUE obj;
 833      ID id;
 834      VALUE val;
 835  {
 836      st_table *tbl;
 837
          /* 总之可以先无视 */
 838      if (rb_special_const_p(obj)) {
 839          special_generic_ivar = 1;
 840      }
          /* 不存在generic_iv_tbl则新建 */
 841      if (!generic_iv_tbl) {
 842          generic_iv_tbl = st_init_numtable();
 843      }
 844
          /* 核心处理 */
 845      if (!st_lookup(generic_iv_tbl, obj, &amp;amp;tbl)) {
 846          FL_SET(obj, FL_EXIVAR);
 847          tbl = st_init_numtable();
 848          st_add_direct(generic_iv_tbl, obj, tbl);
 849          st_add_direct(tbl, id, val);
 850          return;
 851      }
 852      st_insert(tbl, id, val);
 853  }

(variable.c)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;rb_special_const_p() 当参数不是指针的时候返回真。这个 if 语句里面的内容如果不理解垃圾回收机制的话是无法说明的，所以我们在这里还是先省略。
请读者在阅读第五章的垃圾回收机制之后再自行理解。&lt;/p&gt;

&lt;p&gt;st_init_numtable() 刚才也出现了，是用来新建一个哈希表。&lt;/p&gt;

&lt;p&gt;st_lookup() 用来查找和 key 对应的值。这个时候会查找和 obj 所关联的实例变量表。如果找到对应的值函数整体就返回真，在第三个地址参数 (&amp;amp;tbl) 中记录找到的对应值。也就是说!st_lookup(...) 将的是当没有找到对应记录之后发生的事情。&lt;/p&gt;

&lt;p&gt;st_insert() 刚才也说过了。用来将新的对应记录保存到表中。&lt;/p&gt;

&lt;p&gt;st_add_direct() 和 st_insert() 几乎相同，区别在于后者会在追加保存记录之前先检查一下键是否已经存在。也就是说如果使用 st_add_direct() 来添加新记录的话，如果已经有相同的键存在于表中，会出现一个键对应两个记录的情况。&lt;/p&gt;

&lt;p&gt;所以能直接使用 st_add_direct() 的情况，基本上是刚刚确认过键不存在，或者是刚刚建立新的表这两种情况。这段代码是符合这些情况的。&lt;/p&gt;

&lt;p&gt;FL_SET(obj, FL_EXIVAR) 是用来设置 obj 的 basic.flags 为 FL_EXIVAR 的宏。basic.flags 的所有 flag 都是 FL_xxx 的形式，可以通过 FL_SET 来设置 flag。相反用来取消对应 flag 的宏叫做 FL_UNSET()。另外，通常认为 FL_EXIVAR 的 EXIVAR 是 external instance variable 的简称。&lt;/p&gt;

&lt;p&gt;插入这个 flag 是为了提高访问实例变量的速度。如过发现没有这只 FL_EXIVAR 这个 falg，则不用通过探索 generic_iv_tbl 也可以知道实例变量不存在。相比之下当然是 bit 的校验比起探索 struct st_table 来的速度更快。&lt;/p&gt;

&lt;p&gt;####结构体的间隙&lt;/p&gt;

&lt;p&gt;现在我们了解了实例变量的保存方式，那么为什么存在没有 iv_table 的结构体呢？比如 struct RString 和 struct RArray 就没有 iv_tbl，这个是为什么？那么干脆直接把实例变量当作 RBasic 的成员算了。&lt;/p&gt;

&lt;p&gt;就结论来说，可以这样做但是不应该如此。实际上这个和 ruby 的对象管理机制紧密相关。&lt;/p&gt;

&lt;p&gt;ruby 中字符串的数据 (char[]) 所占用的内存使用 malloc 来访问。但是对象的结构体是个例外。ruby 会统一管理分配，从而访问内存。这个时候如果结构体的种类 (大小) 参差不齐的话管理起来就十分麻烦。所以将所有的结构体用共用体 Rvalue 来申明并统一分配管理。共用体的大小会和成员中最大的保持一致，所以如果单独有一个结构体的体积非常大就会造成很大的浪费，于是我们还是希望素有的结构体的大小尽可能保持接近。&lt;/p&gt;

&lt;p&gt;最常用的结构体要数 struct RString(字符串) 了吧。接下来是 RArray(数组)，RHash(哈希)，RObject(所有用户定义的类)
。那么问题就来了。struct RObject 的内容只有 sruct RBASIC 和一个指针。而 struct RString RHash RArray 却已经使用了 struct Basic 和三个指针的空间。也就是说 struct Robject 越多，就会造成越多的两个指针空间的浪费。更有甚者，RString 如果使用了四个指针，RObject 实际上就只占用了共用体的一半不到的空间，实在是太浪费了。&lt;/p&gt;

&lt;p&gt;另一方面配置 iv_tbl 的好处主要是访问速度的提升和内存空间的节省，并且我们也不知道这个功能会不会被频繁使用。实际上在 ruby1.2 之前根本就没有导入过 generic_iv_tbl，所以像 String 和 Array 中也不能使用实例变量，就算如此也没发生什么大问题。如果仅仅因为这点好处就浪费大量的空间实在是太蠢了。&lt;/p&gt;

&lt;p&gt;所以从以上可以做出结论，因为 iv_tbl 而增加构造体的体积实在是无奈之举。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 960  VALUE
 961  rb_ivar_get(obj, id)
 962      VALUE obj;
 963      ID id;
 964  {
 965      VALUE val;
 966
 967      switch (TYPE(obj)) {
      /*（A）*/
 968        case T_OBJECT:
 969        case T_CLASS:
 970        case T_MODULE:
 971          if (ROBJECT(obj)-&amp;gt;iv_tbl &amp;amp;&amp;amp;
                  st_lookup(ROBJECT(obj)-&amp;gt;iv_tbl, id, &amp;amp;val))
 972              return val;
 973          break;
      /*（B）*/
 974        default:
 975          if (FL_TEST(obj, FL_EXIVAR) || rb_special_const_p(obj))
 976              return generic_ivar_get(obj, id);
 977          break;
 978      }
      /*（C）*/
 979      rb_warning("instance variable %s not initialized", rb_id2name(id));
 980
 981      return Qnil;
 982  }

(variable.c)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结构基本相同。&lt;/p&gt;

&lt;p&gt;(A)struct RObject 如果是 Rclass，则检索 iv_tbl。刚才说过了，有可能 iv_tbl 是 NULL，所以不先检查一下的话会出错。接着 st_lookup 会在找到相应记录的时候返回真。这个 if 语句整体来说就是"如果已经代入过此实例变量则返回其值"。&lt;/p&gt;

&lt;p&gt;(C) 如果没有找到对应的值……也就是说如果想要访问的是还没有被代入的实例变量，那么跳过 if 和 switch，直接运行下面的语句。这个时候会发生 rb_warning(),并返回 nil。这是因为 ruby 的实例变量不需要代入也可以被访问。&lt;/p&gt;

&lt;p&gt;(B) 另外，当结构体既不是 struct RObject 也不是 RClass 的时候，首先从 generic_iv_tbl 中寻找对象的实例变量表。generic_ivar_get() 实现的功能不用我说也能想到。另外需要注意的是 if 语句。&lt;/p&gt;

&lt;p&gt;刚才说过将进行过 generic_ivar_set() 处理的对象插入 FL_EXIVAR。在这里这个 flag 使得运行高速化的特征就显现出来了。&lt;/p&gt;

&lt;p&gt;rb_special_const_p() 是什么玩意儿？这个函数当 obj 不存在结构体的时候为真。因为不存在构造体所以也不需要 basic.flags(因为根本无从插入 flag)。所以 FL_xxx() 遇到这种对象总是会返回假。于是这里对待 rb_special_const_p() 为真的对象必须格外小心翼翼。&lt;/p&gt;

&lt;p&gt;###对象的结构体&lt;/p&gt;

&lt;p&gt;这小节中我们简要介绍对象结构体最重要的具体内容的的处理方法。&lt;/p&gt;

&lt;p&gt;####struct RString&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 314  struct RString {
 315      struct RBasic basic;
 316      long len;
 317      char *ptr;
 318      union {
 319          long capa;
 320          VALUE shared;
 321      } aux;
 322  };

(ruby.h)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ptr 是指向字符串的指针，len 是字符串的长度，非常直观。&lt;/p&gt;

&lt;p&gt;Ruby 的字符串与其说是字符串不如说是字符序列。因为可以包含 NUL 在内的任何字符。所以在 ruby 中就算在终端设置 NUL 也没有多大意义，不过因为 C 的函数中要求 NUL，所以为了便利还是将 NUL 设置成字符串的终端。但是 NUL 是不包含在 len 之中的。&lt;/p&gt;

&lt;p&gt;另外解释器和扩展库中处理字符串的时候可以通过&lt;code&gt;RSRTING(str)-&amp;gt;ptr&lt;/code&gt;&lt;code&gt;RSTRING(str)&lt;/code&gt;来访问 ptr 和 len。但是要注意以下几点。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;检查 str 是否确实指向 struct RString。&lt;/li&gt;
&lt;li&gt;可以访问成员但是不能改变其内容&lt;/li&gt;
&lt;li&gt;不可以将 RSTRING(str)-&amp;gt;ptr 保存到诸如临时变量之中供之后使用。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这是为什么？其中一个原因是软件工程学上的原则。不可以随便修改别人的数据。既然有接口函数就乖乖使用接口函数。不过还有其他不允许擅自访问和保存指针的理由，这和第四个成员变量 aux 有关。但是要详细说明 aux 的使用方法又必须得详细说明 ruby 字符串的一些特征。&lt;/p&gt;

&lt;p&gt;ruby 的字符串自身是可以改变的 (mutable),所谓的变化是指&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;s = "str"        # 生成字符串代入s
s.concat("ing")  # 向字符串s中追加"ing"。
p(s)             # 输出"string"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样 s 指向的对象的内容就变成了"string"。java 和 Python 的字符串是没有这种特性的。硬要说的话，这个特性和 Java 的 StringBuffer 很接近。&lt;/p&gt;

&lt;p&gt;接下来我们来看看他们之间到底有什么关系。首先既然可以发生改变自然字符串的长度 len 也会改变。既然长度发生改变那么这个时候内存的分配就会发生增减。当然也可以使用 realloc()，但是 malloc 和 realloc 这些操作都太重了，仅仅是为了变更字符串就 realloc() 一次的话实在是负担太大了。&lt;/p&gt;

&lt;p&gt;于是 ptr 所指向的内存空间通常会比 len 稍微长一点。这样的话追加的字符串正好能放入多余的内存中就可以不必调用 realloc() 了，这样的话速度就上去了。结构体中的 aux.capa 保存的就是这个多余的长度。&lt;/p&gt;

&lt;p&gt;那么另一个 aux.shared 是什么玩意儿呢。这个也是为了提高从字符串序列中生成对象的速度而采用的机制。请看下面的 ruby 程序。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;while true do  # 永远地重复
  a = "str"        # 将内容是"str"的字符串放入a
  a.concat("ing")  # 向a中追加"ing"。
  p(a)             # 输出string
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不管是循环多少次总会在第四行的 p 出输出"string"。放在一般情况下那就得每次通过"str"这个式子新建一个 char[] 类型的字符串对象。
但是对于字符串通常情况下都不会做任何改变，这个时候就会造成多次复制 char[] 的资源浪费。于是我们就期望能够共用一个 char[]。&lt;/p&gt;

&lt;p&gt;作为共用所存在的就是 aux.shared 这个东西了。使用表达式生成的字符串对象都会共用同一个 char[]。只有当真正发生变化时候才会专门去申请内存分配。使用共用的 char[] 的结构体中的 basic.flags 标志中会被设立 ELTS_SHARED 这个标志。aux.shared 会保存原来的对象。ELTS 是 elements 的简称。&lt;/p&gt;

&lt;p&gt;我们回到 RSTRING(str)-&amp;gt;ptr 的话题中。之所以可以访问但不能改变指针对象是因为这会使得 len 和 capa 的值和真实情况不符。另外如果要改变用序列表达式所新建的字符串对象的内容，则需要把对象中的 aux.shared 成员移除。&lt;/p&gt;

&lt;p&gt;最后我们来列举几个使用 RString 的例子。str 可以看成是指向 RString 的 value。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RSTRING(str)-&amp;gt;len;               /* 长度 */
RSTRING(str)-&amp;gt;ptr[0];            /* 第一个字符 */
str = rb_str_new("content", 7);  /* 生成内容是"content"的字符串。
                                    第二个参数是其长度 */
str = rb_str_new2("content");    /* 生成内容是"content"的字符串。
                                    长度会使用strlen()来计算 */
rb_str_cat2(str, "end");         /* 在Ruby字符串中后接C的字符串 */
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;###struct RArray&lt;/p&gt;

&lt;p&gt;struct RArray 是存放 Ruby 的数组实例的结构体。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 324  struct RArray {
 325      struct RBasic basic;
 326      long len;
 327      union {
 328          long capa;
 329          VALUE shared;
 330      } aux;
 331      VALUE *ptr;
 332  };

(ruby.h)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;除了 ptr 之外几乎和 struct RString 一样。ptr 指向的是数组的内容，len 是其长度。aux 的用法和 struct RString 中介绍的相同。aux.capa 是 ptr 所指向的内存的真正的长度，aux.shared 则是当数组为共用的时候指向共用数组的指针。&lt;/p&gt;

&lt;p&gt;访问成员的方法也和 RString 类似。通过 RARRAY(arr)-&amp;gt;ptr 和 RARRAY(arr)-&amp;gt;len 可以访问成员但是不能改变成员的内容。我们来看一下简单的例子。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/* 用c语言操作数组 */
VALUE ary;
ary = rb_ary_new();             /* 生成空的数组 */
rb_ary_push(ary, INT2FIX(9));   /* 将Ruby的9加入数组 */
RARRAY(ary)-&amp;gt;ptr[0];            /* 访问编号是0的元素 */
rb_p(RARRAY(ary)-&amp;gt;ptr[0]);      /* 输出ary[0]（输出9） */

# 用ruby进行操作
ary = []      # 生成空的数组
ary.push(9)   # 将Ruby的9加入数组
ary[0]        # 访问编号是0的元素
p(ary[0])     # 输出ary[0]（输出9）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;###struct RRegexp&lt;/p&gt;

&lt;p&gt;RRepexp 是存放正则表达式的结构体。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 334  struct RRegexp {
 335      struct RBasic basic;
 336      struct re_pattern_buffer *ptr;
 337      long len;
 338      char *str;
 339  };

(ruby.h)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ptr 是已经编译好的正则表达式。str 是编译前的正则表达式 (正则表达式的源码)，len 是其长度。&lt;/p&gt;

&lt;p&gt;处理 Rexgexp 对象的代码本书中将不会出现所以这里就省略了。就算要在扩展库中使用，只要不涉及很特殊的用法，参考一些接口函数就应该足够了吧。&lt;/p&gt;

&lt;p&gt;###struct RHash&lt;/p&gt;

&lt;p&gt;struct RHash 是哈希表 Hash 实例所在的结构体。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 341  struct RHash {
 342      struct RBasic basic;
 343      struct st_table *tbl;
 344      int iter_lev;
 345      VALUE ifnone;
 346  };

(ruby.h)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其实这个是 struct st_table 的 wrapper。关于 st_table 我们会在下一章"名称和命名表"中详细解说。&lt;/p&gt;

&lt;p&gt;ifnone 存放的是搜索失败时候使用的键，默认是 nil。iter_lev 是为了哈希表的 re-entrance(多进程安全) 存在的。&lt;/p&gt;

&lt;p&gt;###struct RFile&lt;/p&gt;

&lt;p&gt;struct RFile 是服务嵌入类 Io 和其后继子类实例的构造体。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 348  struct RFile {
 349      struct RBasic basic;
 350      struct OpenFile *fptr;
 351  };

(ruby.h)
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 19  typedef struct OpenFile {
  20      FILE *f;                    /* stdio ptr for read/write */
  21      FILE *f2;                   /* additional ptr for rw pipes */
  22      int mode;                   /* mode flags */
  23      int pid;                    /* child's pid (for pipes) */
  24      int lineno;                 /* number of lines read */
  25      char *path;                 /* pathname for file */
  26      void (*finalize) _((struct OpenFile*)); /* finalize proc */
  27  } OpenFile;

(rubyio.h)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成员几乎都保存在了 struct OpenFile 中。IO 对象的实例并不多所以可以这样存放。各个成员的用途都有些。基本上是 C 语言 stdio 的 warpper。&lt;/p&gt;

&lt;p&gt;###struct RData&lt;/p&gt;

&lt;p&gt;struct RData 和目前为止介绍的东西目的都不一样。这个主要是为了存放扩展类的结构体。&lt;/p&gt;

&lt;p&gt;编写扩展库的类的实体当然也需要一个结构体来存放。但是结构体的类型是生成的类所决定的，所以无法事先知道类的大小和结构。于是 ruby 提供了一个"管理用户自定义的结构体指针的结构体"，这个东西就是 struct RData 了。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 353  struct RData {
 354      struct RBasic basic;
 355      void (*dmark) _((void*));
 356      void (*dfree) _((void*));
 357      void *data;
 358  };

(ruby.h)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;data 是指向用户定义的构造体的指针。dfree 是释放自定义构造体的函数，dmark 是 mark and sweep 中进行 mark 的函数 (涉及垃圾回收)&lt;/p&gt;

&lt;p&gt;关于 struct RDdata 的说明现在还不是时候，总之先看图吧。详细的内容我们会在第五章的"垃圾回收"中介绍。&lt;/p&gt;

&lt;p&gt;&lt;img src="http://i.loveruby.net/ja/rhg/book/images/ch_object_rdata.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;(第二章完)&lt;/p&gt;

&lt;p&gt;##第三章 名称和命名表&lt;/p&gt;

&lt;p&gt;####st_table
作为存储方法的表和实例变量的表，st_table 已经出现过几次了。本章首先就 st_table 做详细的说明。&lt;/p&gt;

&lt;p&gt;###概要
我们已经说过 st_table 是哈希表。哈希表是保存一对一对应关系的数据结构。这种一对一关系可以是变量名和变量的值，也可以是函数名和函数的实体，等等。&lt;/p&gt;

&lt;p&gt;当然除了哈希表也可以用其它的数据结构来表示一一对应关系。比如可以下面这种 list 的结构体。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct entry {
    ID key;
    VALUE val;
    struct entry *next;  /* 指向下一个元素 */
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是这种方法很慢。如果存在 1000 个元素的话，最糟糕的情况下要遍历 1000 次这个链表。也就是探索的时间和元素的个数是成正比的。这样的话就会很糟糕。所以从很早就考虑了各种解决方法。哈希表就是这个解决方法的一种。也就是说哈希表并不是仅有的方法，但是能够带来高速化的处理。&lt;/p&gt;

&lt;p&gt;接下来我们实际来看 st_table。注意看，这个库并不是松本先生的原创。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  1  /* This is a public domain general purpose hash table package
         written by Peter Moore @ UCB. */

(st.c)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;……恩至少注释是这么说的。&lt;/p&gt;

&lt;p&gt;顺带一提，用谷歌检索到的其他版本的注释上说，st_table 是 string table 的简称。但是我认为 general purpose 和 string 到底是有些矛盾的……。&lt;/p&gt;

&lt;p&gt;####所谓哈希表&lt;/p&gt;

&lt;p&gt;哈希表的设想如下。首先有一个长度为 n 的数组。比如说 n=64。&lt;/p&gt;

&lt;p&gt;&lt;img src="http://i.loveruby.net/ja/rhg/book/images/ch_name_array.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;然后准备一个能够将键映射到 0 到 n-1(0~63) 的整数 i 的函数 f。这个 f 被叫做哈希函数。对于同一个键，f 必须保证每次都返回相同的 i。假设键的值是整数的话，这个整数被 64 整除的余数肯定是在 0 到 63 之间的。所以这个取余的计算可以成为 f 函数。&lt;/p&gt;

&lt;p&gt;要找到对应关系的存储位置的时候，先对键调用哈希函数 f，求得 i 的值，然后数组的第 i 个元素就好了。也就是说，因为访问数组的某个元素是十分快速的，所以只需要找到某种方法把键转换成整数就好了，这个就是 hash 的核心思想。&lt;/p&gt;

&lt;p&gt;&lt;img src="http://i.loveruby.net/ja/rhg/book/images/ch_name_aset.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;可惜世界上是没有这么理想的情况的。这个方法有个致命的问题。n 现在只有 64 个元素，所以当需要对应 64 个以上的元素键的话肯定会发生重复。就算键没超过 64 个，也有可能发生两个键对应相同的索引的情况。比如刚才用 64 取余的的方法，当键的值是 65 或者 129 的时候，对应的哈希值都是 1。我们把这个叫做哈希冲突。解决冲突的方法主要有几种。&lt;/p&gt;

&lt;p&gt;比如发生冲突的话可以按照下面的方法放入元素。这个方法叫做开放定址法。&lt;/p&gt;

&lt;p&gt;&lt;img src="http://i.loveruby.net/ja/rhg/book/images/ch_name_nexti.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;另外有一种方法，不是直接将元素存在数组中，而是在数组中存放一个个链表的指针。发生冲突的时候逐渐延长链表的长度。这个方法叫做连锁法。st_table 采用的就是这个连锁法。&lt;/p&gt;

&lt;p&gt;&lt;img src="http://i.loveruby.net/ja/rhg/book/images/ch_name_chain.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;说来如果能够实现知道键的集合的内容的话也许可以设计出一个绝对不会发生冲突的哈希函数。这个函数被叫做绝对哈希函数。然后还有针对任何的字符串的集合来生成哈希函数的工具，比如说 GNU 的 gperf。ruby 的语法解析器实际上也用到了这个工具……还不是说这个的时候。我们会在第二部分继续介绍。&lt;/p&gt;

&lt;p&gt;###数据结构&lt;/p&gt;

&lt;p&gt;现在我们来看实际的代码。在序章中已经说过，如果同时存在数据类型和代码的话当然是先读数据类型。下面我们来看一下 st_table 实际使用的数据类型。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   9  typedef struct st_table st_table;

  16  struct st_table {
  17      struct st_hash_type *type;
  18      int num_bins;                   /* 槽的个数 */
  19      int num_entries;                /* 总共存放的元素数*/
  20      struct st_table_entry **bins;   /* 槽 */
  21  };

(st.h)
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  16  struct st_table_entry {
  17      unsigned int hash;
  18      char *key;
  19      char *record;
  20      st_table_entry *next;
  21  };

(st.c)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;st_table 是主体的表的结构，st_table_entry 是存放元素的地方。st_table_entry 有一个成员叫做 next，因为是链表所以当然需要啦。这个就是连锁法的连锁的地方。我们发现其使用了 st_hash_type 的类型，我们会稍后对此说明首先就别的地方对照下图进行逐一确认好了。&lt;/p&gt;

&lt;p&gt;&lt;img src="http://i.loveruby.net/ja/rhg/book/images/ch_name_sttable.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;接下来我们来看 st_hash_type。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  11  struct st_hash_type {
  12      int (*compare)();   /* 比较函数 */
  13      int (*hash)();      /* 哈希函数 */
  14  };

(st.h)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;毕竟才第三章我们还是认真得看下去吧。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int (*compare)()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个表示的是返回 int 的函数指针成员 compare。hash 也是同理。这种变量用下面的方法代入。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int
great_function(int n)
{
    /* ToDo */
    return n;
}

{
    int (*f)();
    f = great_function;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后用下面的方法调用。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    (*f)(7);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在回到 st_hash_type 的解说。hash compare 这个两个成员中，hash 就是之前说过的哈希函数。&lt;/p&gt;

&lt;p&gt;compare 则是用来判断键是否是同一个的函数。因为连锁法中相同的哈希值 n 的地方会存放多个要素。为了知道其中哪个元素才是我们真正需要的，这就需要有一个可以完全信任的比较函数。这个比较函数就是 compare。&lt;/p&gt;

&lt;p&gt;这个 st_hash_type 是一种很巧妙的通用化方法。哈希表是无法确定自己保存的键的类型的。比如 ruby 的 st_table 的键勊是 ID，char*，VALUE。如果每种类型都要设计一种哈希的话实在是太蠢了。因为键的类型不同而导致改变的仅仅是哈希函数的部分而已，无论是分配内存还是检测冲突的大部分代码都是相同的。于是我们仅仅把不同的地方作为函数特化起来，用函数指针来制定其具体函数来使用。这样的话就可以使本来就占据着大部分代码的哈希表的实装更加灵活。&lt;/p&gt;

&lt;p&gt;面向对象的语言本身就把对象和过程捆绑在一起所以这种构造是没有必要的。或者说这种构造作为语言的功能已经被嵌入进去了。&lt;/p&gt;

&lt;p&gt;st_hash_type 的例子&lt;/p&gt;

&lt;p&gt;st_hash_type 的结构体虽然是一种很成功的通用方法，但是也是的代码变得复杂难懂起来。不具体看一下 hash 和 compare 函数的话总是没有什么实感。于是现在就可以来看看上一章也出现的 st_init_numtable() 函数了。这个对应整数键值的哈希函数。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 182  st_table*
 183  st_init_numtable()
 184  {
 185      return st_init_table(&amp;amp;type_numhash);
 186  }

(st.c)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;st_init_table() 是给表分配内存的函数，type_numhash 的类型是 st_hash_type。type_numhash 的内容如下&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  37  static struct st_hash_type type_numhash = {
  38      numcmp,
  39      numhash,
  40  };

 552  static int
 553  numcmp(x, y)
 554      long x, y;
 555  {
 556      return x != y;
 557  }

 559  static int
 560  numhash(n)
 561      long n;
 562  {
 563      return n;
 564  }

(st.c)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;实在是太简单。ruby 的解释器用的表基本上使用的是 type_numhash。&lt;/p&gt;

&lt;p&gt;###st_lookup()&lt;/p&gt;

&lt;p&gt;接下来我们来看哈希结构体里面的函数，从开始可以先从探索函数入手。哈希表中的探索函数 st_lookup() 内容如下。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 247  int
 248  st_lookup(table, key, value)
 249      st_table *table;
 250      register char *key;
 251      char **value;
 252  {
 253      unsigned int hash_val, bin_pos;
 254      register st_table_entry *ptr;
 255
 256      hash_val = do_hash(key, table);
 257      FIND_ENTRY(table, ptr, hash_val, bin_pos);
 258
 259      if (ptr == 0) {
 260          return 0;
 261      }
 262      else {
 263          if (value != 0)  *value = ptr-&amp;gt;record;
 264          return 1;
 265      }
 266  }

(st.c)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重要的部分几乎都在 do_hash() 和 FIND_ENTRY() 里面，我们按着顺序来看。&lt;/p&gt;

&lt;p&gt;do_hash()&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  68  #define do_hash(key,table) (unsigned int)(*(table)-&amp;gt;type-&amp;gt;hash)((key))

(st.c)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;保险起见我们还是把宏里面的复杂的部分单独抽取出来&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(table)-&amp;gt;type-&amp;gt;hash
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个函数指针带上一个参数 key 之后就调用了相关的函数。*的内容不是 table(而是表示这个带参数的函数指针)。也就是说，这个宏使用按照类型定义的不同的哈希函数 type-&amp;gt;hash，带上参数 key 来求哈希值。&lt;/p&gt;

&lt;p&gt;接下去我们来看 FIND_ENTRY()。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 235  #define FIND_ENTRY(table, ptr, hash_val, bin_pos) do {\
 236      bin_pos = hash_val%(table)-&amp;gt;num_bins;\
 237      ptr = (table)-&amp;gt;bins[bin_pos];\
 238      if (PTR_NOT_EQUAL(table, ptr, hash_val, key)) {\
 239          COLLISION;\
 240          while (PTR_NOT_EQUAL(table, ptr-&amp;gt;next, hash_val, key)) {\
 241              ptr = ptr-&amp;gt;next;\
 242          }\
 243          ptr = ptr-&amp;gt;next;\
 244      }\
 245  } while (0)

 227  #define PTR_NOT_EQUAL(table, ptr, hash_val, key) ((ptr) != 0 &amp;amp;&amp;amp; \
          (ptr-&amp;gt;hash != (hash_val) || !EQUAL((table), (key), (ptr)-&amp;gt;key)))

  66  #define EQUAL(table,x,y) \
          ((x)==(y) || (*table-&amp;gt;type-&amp;gt;compare)((x),(y)) == 0)

(st.c)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;COLLISION 是用来 debug 的宏，直接无视好了。&lt;/p&gt;

&lt;p&gt;FIND_ENTRY() 的参数从前向后分别是&lt;/p&gt;

&lt;p&gt;1.st_table&lt;/p&gt;

&lt;p&gt;2.应该使用的临时变量&lt;/p&gt;

&lt;p&gt;3.哈希值&lt;/p&gt;

&lt;p&gt;4.检索的键&lt;/p&gt;

&lt;p&gt;第二个参数用来保存查询到的 st_table_entry*的值。&lt;/p&gt;

&lt;p&gt;另外最外面一层为了保证由多个式子组成的宏安全执行，使用了 do～while(0)。这个是 ruby，严格来说是 c 语言的预处理的风格。使用 if(1) 的话会不小心带上 else。而使用 while(1) 的话最后还需要 break。&lt;/p&gt;

&lt;p&gt;while(0) 的后面不接分号也是有讲究的。如果你硬要问为什么，请看&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FIND_ENTRY();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一般都是这样写，所以最后的就不会多出来一个分号。&lt;/p&gt;

&lt;p&gt;####st_add_direct()&lt;/p&gt;

&lt;p&gt;接下去我们来看在哈希表中添加新数据的函数 st_add_direct()。这个函数不检查键是否已经登录，而是无条件得追加新的项目。这个就是 direct 的意思。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 308  void
 309  st_add_direct(table, key, value)
 310      st_table *table;
 311      char *key;
 312      char *value;
 313  {
 314      unsigned int hash_val, bin_pos;
 315
 316      hash_val = do_hash(key, table);
 317      bin_pos = hash_val % table-&amp;gt;num_bins;
 318      ADD_DIRECT(table, key, value, hash_val, bin_pos);
 319  }

(st.c)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;和刚才一样为了求哈希值使用了宏 do_hash(),接下来的计算也在 FIND_ENTRY() 的开头出现过，哈希值就实际的索引号。&lt;/p&gt;

&lt;p&gt;然后插入过程自身是依靠 ADD_DIRECT 执行。从名字就可以看出这是一个宏。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 268  #define ADD_DIRECT(table, key, value, hash_val, bin_pos) \
 269  do {                                                     \
 270      st_table_entry *entry;                               \
 271      if (table-&amp;gt;num_entries / (table-&amp;gt;num_bins)           \
                              &amp;gt; ST_DEFAULT_MAX_DENSITY) {      \
 272          rehash(table);                                   \
 273          bin_pos = hash_val % table-&amp;gt;num_bins;            \
 274      }                                                    \
 275                                                           \
          /*（A）*/                                            \
 276      entry = alloc(st_table_entry);                       \
 277                                                           \
 278      entry-&amp;gt;hash = hash_val;                              \
 279      entry-&amp;gt;key = key;                                    \
 280      entry-&amp;gt;record = value;                               \
          /*（B）*/                                            \
 281      entry-&amp;gt;next = table-&amp;gt;bins[bin_pos];                  \
 282      table-&amp;gt;bins[bin_pos] = entry;                        \
 283      table-&amp;gt;num_entries++;                                \
 284  } while (0)

(st.c)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;开头的 if 是例外处理的内容，我们稍后看，首先看下面的。&lt;/p&gt;

&lt;p&gt;(A) 分配 st_table_entry，进行初始化&lt;/p&gt;

&lt;p&gt;(B) 向链表开头追加 entry。这个是处理链表的风格。也就是说通过&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;entry-&amp;gt;next = list_beg;
list_beg = entry;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以向链表的开头追加元素。这也是 Lisp 的术语"cons"的意思。就算 list_beg 是 NULL，这段代码也是可以通用的。&lt;/p&gt;

&lt;p&gt;最后看一下被我们搁置的代码。&lt;/p&gt;

&lt;p&gt;ADD_DIRECT()－rehash&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 271      if (table-&amp;gt;num_entries / (table-&amp;gt;num_bins)           \
                              &amp;gt; ST_DEFAULT_MAX_DENSITY) {      \
 272          rehash(table);                                   \
 273          bin_pos = hash_val % table-&amp;gt;num_bins;            \
 274      }                                                    \

(st.c)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;DENSITY 就是所谓浓度，也就是使用这个条件式判断哈希表是否已经趋于拥挤。st_table 中如果所在同一个 bin_pos 的值过多的话，链表就会变成，搜索速度就会变慢。所以当元素个数过多的时候，我们增加 bin 的大小来缓解这种拥挤。&lt;/p&gt;

&lt;p&gt;现在所设定的 ST_DEFAULT_MAX_DENSITY 如下&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  23  #define ST_DEFAULT_MAX_DENSITY 5

(st.c)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个浓度被设置成了 5，也就是说当所有的 bin_pos 链表的元素 st_table_entry 都已经达到 5 个的情况下，我们就要增大容量。&lt;/p&gt;

&lt;p&gt;####st_insert()&lt;/p&gt;

&lt;p&gt;st_insert() 不过是 st_add_direct() 和 st_lookup() 的组合而已。只要了解后两者就 ok 了。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 286  int
 287  st_insert(table, key, value)
 288      register st_table *table;
 289      register char *key;
 290      char *value;
 291  {
 292      unsigned int hash_val, bin_pos;
 293      register st_table_entry *ptr;
 294
 295      hash_val = do_hash(key, table);
 296      FIND_ENTRY(table, ptr, hash_val, bin_pos);
 297
 298      if (ptr == 0) {
 299          ADD_DIRECT(table, key, value, hash_val, bin_pos);
 300          return 0;
 301      }
 302      else {
 303          ptr-&amp;gt;record = value;
 304          return 1;
 305      }
 306  }

(st.c)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;首先查询元素是否已经被添加到哈希表中，如果还没有，就向哈希表中添加元素，实际上添加了元素则返回真，如果没有添加就返回假。&lt;/p&gt;

&lt;p&gt;###ID 和符号&lt;/p&gt;

&lt;p&gt;我们已经说明过 ID 是什么东西。ID 是和任意的字符串一一对应的数值，可以表示各种名称。实际的 ID 的类型是 unsigned int。&lt;/p&gt;

&lt;p&gt;####从 char 到 ID&lt;/p&gt;

&lt;p&gt;字符串到 ID 的变化通过 rb_intern() 进行。这个函数略长我们省略其中一部分。&lt;/p&gt;

&lt;p&gt;rb_intern()（缩减版）&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5451  static st_table *sym_tbl;       /*  char* → ID   */
5452  static st_table *sym_rev_tbl;   /*  ID → char*   */

5469  ID
5470  rb_intern(name)
5471      const char *name;
5472  {
5473      const char *m = name;
5474      ID id;
5475      int last;
5476
          /* 既にnameに対応するIDが登録されていたらそれを返す */
5477      if (st_lookup(sym_tbl, name, &amp;amp;id))
5478          return id;

          /* 省略……新しいIDを作る */

          /* nameとIDの関連を登録する */
5538    id_regist:
5539      name = strdup(name);
5540      st_add_direct(sym_tbl, name, id);
5541      st_add_direct(sym_rev_tbl, id, name);
5542      return id;
5543  }

(parse.y)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;字符串和 ID 的一一对应使用 st_table 来实现。应该不是什么难点。&lt;/p&gt;

&lt;p&gt;要说我们省略了什么，那就是当遇到全局变量或者实例变量的时候我们会进行特殊处理插入标记位。因为 ruby 的语法解析器需要从 ID 获取变量的类型。但是这些又和 ID 的原理没多大联系所以这里就不贴出来了。&lt;/p&gt;

&lt;p&gt;####从 ID 到 char*&lt;/p&gt;

&lt;p&gt;rb_intern() 的逆向，从 ID 获取 char*使用的是 rb_id2name() 这个函数。大家应该已经明白 id2name 的 2 是 to 的意思。因为 to 和 two 发音相同所以被替代使用了。这个写法出乎意料相当常见。&lt;/p&gt;

&lt;p&gt;这个函数也是根据 ID 的种类设立各种 flag 标记，所以变得很长。我们尽量删掉无关紧要的部分来看这个函数。&lt;/p&gt;

&lt;p&gt;rb_id2name(阉割版)&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;char *
rb_id2name(id)
    ID id;
{
    char *name;

    if (st_lookup(sym_rev_tbl, id, &amp;amp;name))
        return name;
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;是不是觉得有些过于简单了。其实只是删除掉了一些小细节。&lt;/p&gt;

&lt;p&gt;这里需要注意，我们没有拷贝需要检索的 name。Ruby 的 API 的返回值不需要 free()(绝对不能 free)。另外传递参数的时候通常会通过拷贝来使用。也就是说生成和释放通常是用户或者 ruby 的一方来执行完成的。(谁生成谁释放)&lt;/p&gt;

&lt;p&gt;那么对于生成和释放无法相对应的值 (一旦被传递就无法控制) 是如何处理的呢？这个时候会要求使用 Ruby 对象。Ruby 对象会在我们不需要的时候自动释放。&lt;/p&gt;

&lt;p&gt;####VALUE 和 ID 的互相转换&lt;/p&gt;

&lt;p&gt;ID 在 Ruby 层面上是 Symbol 类的实例，&lt;code&gt;"string".intern&lt;/code&gt;会返回对应的ID。这个String#intern的实体就是rb_str_intern()。&lt;/p&gt;

&lt;p&gt;▼rb_str_intern()&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2996  static VALUE
2997  rb_str_intern(str)
2998      VALUE str;
2999  {
3000      ID id;
3001
3002      if (!RSTRING(str)-&amp;gt;ptr || RSTRING(str)-&amp;gt;len == 0) {
3003          rb_raise(rb_eArgError, "interning empty string");
3004      }
3005      if (strlen(RSTRING(str)-&amp;gt;ptr) != RSTRING(str)-&amp;gt;len)
3006          rb_raise(rb_eArgError, "string contains `\\0'");
3007      id = rb_intern(RSTRING(str)-&amp;gt;ptr);
3008      return ID2SYM(id);
3009  }

(string.c)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个函数作为 ruby 的类库的代码例子来说真是信手拈来。注意其中使用 RSTRING() 强制转换来访问构造体成员的技巧。&lt;/p&gt;

&lt;p&gt;读读代码吧。首先 rb_raise() 只是出错处理所以先无视。这个函数里面有刚才刚解释过的 rb_intern(),ID2SYM().ID2SYM() 是将 ID 转换成 Symbol 的宏。&lt;/p&gt;

&lt;p&gt;这个操作的逆向是 Symbol#to_s。其实体函数为 sym_to_s。&lt;/p&gt;

&lt;p&gt;▼sym_to_s()&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 522  static VALUE
 523  sym_to_s(sym)
 524      VALUE sym;
 525  {
 526      return rb_str_new2(rb_id2name(SYM2ID(sym)));
 527  }

(object.c)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;SYM2ID 是将 Symbol(VALUE) 转换成 ID 的的宏。&lt;/p&gt;

&lt;p&gt;看上去很不常见的写法，应该注意的是内存处理相关的地方。rb_id2name() 返回一个不允许 free() 的 char*, rb_str_new2() 将参数 char*拷贝使用 (不会改变参数)。因为贯彻了这个方针所以可以写成函数套函数的连锁形式。&lt;/p&gt;

&lt;p&gt;(第三章完)&lt;/p&gt;</description>
      <author>gyorou</author>
      <pubDate>Fri, 31 Oct 2014 18:15:18 +0800</pubDate>
      <link>https://ruby-china.org/topics/22386</link>
      <guid>https://ruby-china.org/topics/22386</guid>
    </item>
    <item>
      <title>出来混的迟早的滚回老家</title>
      <description>&lt;p&gt;注册 rubychina 有段时间了，一直算是潜水状态，偶尔会问几个问题。明年四月就要踏上社会步入码农阶级了，最近尤其百感交集，还得请各位有经验的前辈指点一二。&lt;/p&gt;

&lt;p&gt;先自我介绍一下好了。lz 现在岛国某旧帝大的自然语言处理方向硕士 2 年级，工作内定了岛国某电商。由于刚毕业毫无经验所以不敢冒然回国，还是打算先在岛国摸爬几年。不过在岛国这两年，仔细回想一下，虽然有时候觉得一个人生活很 high，但大多数时间还是非常寂寞。既不愿意放手眼前还算不错的生活质量，但是总感觉精神上不快乐。也许是想得太长远了，开始考虑工作数年之后回国的问题。&lt;/p&gt;

&lt;p&gt;平时经常会关注一下论坛内的招聘贴之类的，有些工资开的的确很高。自己觉得有 20k 应该是不错了。不过自己心里也清楚，自己还没有 20k 的实力吧。假设目标是 4 年内回国，不考虑其他素质的，就以回国之后目标 30k 月收而言，该有大概一个怎么样的努力方向呢。&lt;/p&gt;

&lt;p&gt;论坛里想必海归或者是有相当经历的前辈应该很多吧，还请分享一下自己的心路历程。&lt;/p&gt;
&lt;h2 id="目前的自身条件"&gt;目前的自身条件&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;熟悉机器学习工具和中文，日文，英文语言处理。熟悉概率论，统计学，线性代数。&lt;a href="http://github.com/lengshuiyulangcn/kurumi.git" rel="nofollow" target="_blank" title=""&gt;ruby 的中文分词器&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;熟悉 ruby 和 ruby on rails，用 rails 做过一些拙劣的东西，比如&lt;a href="http://tjjtds.me/chuochuo" rel="nofollow" target="_blank" title=""&gt;chuochuo&lt;/a&gt; &lt;a href="http://github.com/lengshuiyulangcn/chuochuo.git" rel="nofollow" target="_blank" title=""&gt;源码&lt;/a&gt; 能抄来用各种 css，javascript。&lt;/li&gt;
&lt;li&gt;略熟悉 java，实习期间接触过 solr。&lt;/li&gt;
&lt;li&gt;一堆语言证书。日老 1 级，托福 92，托业 985。&lt;/li&gt;
&lt;li&gt;单身&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>gyorou</author>
      <pubDate>Wed, 20 Aug 2014 12:36:32 +0800</pubDate>
      <link>https://ruby-china.org/topics/21134</link>
      <guid>https://ruby-china.org/topics/21134</guid>
    </item>
    <item>
      <title>这个算是 bug 么</title>
      <description>&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clone&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;delete_at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt;[[2],[3,4]]&lt;/span&gt;
&lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt;[[2],[3,4]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ruby 1.9.3p484[i-386-mingwin32]&lt;/p&gt;</description>
      <author>gyorou</author>
      <pubDate>Thu, 05 Jun 2014 18:06:48 +0800</pubDate>
      <link>https://ruby-china.org/topics/19765</link>
      <guid>https://ruby-china.org/topics/19765</guid>
    </item>
    <item>
      <title>redcarpet render lalex 公式有什么好的解决方法？</title>
      <description>&lt;p&gt;redcarpet 作者貌似由于&lt;a href="http://zh.wikipedia.org/wiki/LaTeX" rel="nofollow" target="_blank" title=""&gt;latex&lt;/a&gt;大小问题以及完全问题，无视了各种添加针对 latex 支持的要求。&lt;/p&gt;

&lt;p&gt;目前想要实现的是：扩展 redcarpet，添加一个方法来 render latex 的公式。只需要公式就 ok 了。有说使用 mathjax 之类的。目前正在琢磨。&lt;/p&gt;

&lt;p&gt;于是请问有什么好的解决方法。&lt;/p&gt;</description>
      <author>gyorou</author>
      <pubDate>Thu, 17 Apr 2014 12:49:30 +0800</pubDate>
      <link>https://ruby-china.org/topics/18659</link>
      <guid>https://ruby-china.org/topics/18659</guid>
    </item>
    <item>
      <title>自制中文分词 gem</title>
      <description>&lt;p&gt;&lt;code&gt;gem install cseg&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;使用之前须下载并安装 CRF++。（&lt;a href="http://crfpp.googlecode.com/svn/trunk/doc/index.html" rel="nofollow" target="_blank"&gt;http://crfpp.googlecode.com/svn/trunk/doc/index.html&lt;/a&gt;）&lt;/p&gt;

&lt;p&gt;使用 MIRA 学习了大量的素性。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;require 'cseg'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;result=Kurumi.segment(str)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;结果以数组保存。&lt;/p&gt;
&lt;h2 id="precision"&gt;precision&lt;/h2&gt;
&lt;p&gt;94.43%&lt;/p&gt;
&lt;h2 id="recall"&gt;recall&lt;/h2&gt;
&lt;p&gt;92.86%&lt;/p&gt;

&lt;p&gt;tested on seghan 05 pku test set&lt;/p&gt;</description>
      <author>gyorou</author>
      <pubDate>Sun, 02 Mar 2014 22:16:19 +0800</pubDate>
      <link>https://ruby-china.org/topics/17614</link>
      <guid>https://ruby-china.org/topics/17614</guid>
    </item>
    <item>
      <title>关于 carrierwave 里面设置 mount loader 的表项</title>
      <description>&lt;h2 id="新人第一帖"&gt;新人第一帖&lt;/h2&gt;
&lt;p&gt;比如，我有一个 user model 和 userinfo model
在 user 建立之后希望自动生成相应的 userinfo。在 userinfo 中有一项是 image:string 并 mount loader。
于是在 user model 中利用 after_save 初始化 userinfo 并给 image 赋默认值。
不过无论怎么试，都无法给 userinfo.image 赋值成功。
后来按照 carrierwave 的方法，在 image_uploader.rb 设置 defalut_url 才得以实现。&lt;/p&gt;
&lt;h2 id="我的猜想是因为image设置了mount loader，于是不管给image赋什么初始值都是徒劳。"&gt;我的猜想是因为 image 设置了 mount loader，于是不管给 image 赋什么初始值都是徒劳。&lt;/h2&gt;
&lt;p&gt;顺带提一下。我发现 rubychina 网页上一些字体无法正常显示。&lt;/p&gt;</description>
      <author>gyorou</author>
      <pubDate>Sun, 02 Mar 2014 21:50:21 +0800</pubDate>
      <link>https://ruby-china.org/topics/17613</link>
      <guid>https://ruby-china.org/topics/17613</guid>
    </item>
  </channel>
</rss>
