DAY 2
第二天学习继续,今天重点学习函数,集合和类等编程的基本要素。
2.1 函数
2.1.1 简单定义
<code class="hljs ruby has-numbering"> <span class="hljs-function"><span class="hljs-keyword">def</span> </span>tell_me puts <span class="hljs-keyword">true</span> <span class="hljs-keyword">end</span></code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li></ul>
- 定义一个简单的函数(无参无返回值)很简单,使用def end包裹函数体即可。
2.1.2 参数
<code class="hljs ruby has-numbering"><span class="hljs-function"><span class="hljs-keyword">def</span> </span>tell_me(a) puts a <span class="hljs-keyword">end</span> tell_me <span class="hljs-number">1</span> <span class="hljs-number">1</span> => <span class="hljs-keyword">nil</span></code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li></ul>
- 使用参数和其他语言类似,加括号加变量名即可。
2.1.2.1 参数默认值
同样可以指定参数默认值:
<code class="hljs haml has-numbering">def tell_me(a='1',b) puts a puts b end =<span class="ruby">> <span class="hljs-keyword">nil</span> </span>tell_me 2 1 2 =<span class="ruby">> <span class="hljs-keyword">nil</span> </span>tell_me 3,4 3 4 =<span class="ruby">> <span class="hljs-keyword">nil</span></span></code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li></ul>
我们看到指定默认值的方式跟其他语言类似。
有些苦恼的是,看起来我们需要记住每一个函数的参数数量和含义。
2.1.2.2 可变参数
Ruby同样支持可变参数,而且因为不受类型的限制,使用相当自由:
<code class="hljs ruby has-numbering"><span class="hljs-function"><span class="hljs-keyword">def</span> </span>test(*tt) <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>...tt.length puts tt[i] <span class="hljs-keyword">end</span> <span class="hljs-keyword">end</span> => <span class="hljs-keyword">nil</span> test <span class="hljs-number">1</span>,<span class="hljs-number">2</span> <span class="hljs-number">1</span> <span class="hljs-number">2</span> => <span class="hljs-number">0</span>...<span class="hljs-number">2</span> test <span class="hljs-string">'a'</span> a => <span class="hljs-number">0</span>...<span class="hljs-number">1</span></code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li></ul>
- 我们看到只需要将参数前加*即可,收到的参数就将是一个数组。
2.1.3 返回值
2.1.3.1 默认返回值
- 昨天的学习里看到过,其实所有的Ruby语句都有返回值,那么其实我们及时不给函数指定返回值,函数也会返回他执行的最后一个语句的值。
看下面的例子:
<code class="hljs haml has-numbering">def give_me true end =<span class="ruby">> <span class="hljs-keyword">nil</span> </span> i=give_me =<span class="ruby">> <span class="hljs-keyword">true</span> </span> i =<span class="ruby">> <span class="hljs-keyword">true</span></span></code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li></ul>
这个函数我们并没有指定返回值,但是函数还是返回了最后执行的语句true的值。
2.1.3.2 return语句
<code class="hljs php has-numbering">$ irb def test a=<span class="hljs-number">1</span> b=<span class="hljs-number">2</span> <span class="hljs-keyword">return</span> a,b end => nil <span class="hljs-keyword">var</span>=test => [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>] <span class="hljs-keyword">var</span>.class => <span class="hljs-keyword">Array</span> <span class="hljs-keyword">var</span> => [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>]</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li></ul>
-
Ruby可以通过return语句指定返回值,而且跟C家族语言不同的是,Ruby可以同时返回多个返回值。
-
因为动态语言的特性,Ruby看起来如此自由,但是看起来这就更要求调用者对他们的调用负责。
2.2 代码块和yield
2.2.1 代码块的使用
代码块是没有名字的函数。
<code class="hljs perl has-numbering"><span class="hljs-number">3</span>.<span class="hljs-keyword">times</span>{puts <span class="hljs-string">'hi'</span>}</code><ul style="display: block;" class="pre-numbering"><li>1</li></ul>
- times是Fixnum类的方法,它可以执行后面的代码块n次。
- 后面大括号中内容就是代码块,代码块既可以用{}来包裹,也可以用do/end来包裹,就像下面的的代码。
<code class="hljs livecodeserver has-numbering"><span class="hljs-number">3.</span>times <span class="hljs-built_in">do</span> puts <span class="hljs-string">'hello'</span> <span class="hljs-function"><span class="hljs-keyword">end</span></span></code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li></ul>
昨天在区间一节中提到的each函数后面其实也是代码块:
<code class="hljs livecodeserver has-numbering">names=[<span class="hljs-string">'lee'</span>,<span class="hljs-string">'tom'</span>,<span class="hljs-string">'jim'</span>] names.<span class="hljs-keyword">each</span>{|<span class="hljs-operator">a</span>| puts <span class="hljs-operator">a</span>}</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li></ul>
2.2.2 yield执行代码块
下面我们来看下自定义的方法来执行代码块:
<code class="hljs matlab has-numbering">def my_times(<span class="hljs-built_in">i</span>=<span class="hljs-number">5</span>) <span class="hljs-keyword">while</span> <span class="hljs-built_in">i</span>><span class="hljs-number">0</span> <span class="hljs-built_in">i</span>=<span class="hljs-built_in">i</span>-<span class="hljs-number">1</span> yield <span class="hljs-keyword">end</span> <span class="hljs-keyword">end</span> my_times(<span class="hljs-number">3</span>)<span class="hljs-cell">{puts <span class="hljs-string">'a'</span>}</span></code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li></ul>
我们看到这个函数根据传入参数,调用了一定数量的代码块。
- 函数中定义的yield表示执行代码块中的代码。
2.2.3 代码块作为函数参数
我们也可以将代码块作为参数一直传递下去。
<code class="hljs oxygene has-numbering">def call_block(&<span class="hljs-keyword">block</span>) <span class="hljs-keyword">block</span>.call <span class="hljs-keyword">end</span> def pass_block(&<span class="hljs-keyword">block</span>) call_block(&<span class="hljs-keyword">block</span>) <span class="hljs-keyword">end</span> pass_block<span class="hljs-comment">{puts 'hello'}</span></code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li></ul>
- 在Ruby中,参数名之前加一个“&”,表示将代码块作为闭包传递给函数。闭包就是把函数以及变量包起来,使得变量的生存周期延长。
2.3 数组
<code class="hljs bash has-numbering">animals = [<span class="hljs-string">'lions'</span>, <span class="hljs-string">'tigers'</span>, <span class="hljs-string">'bears'</span>] puts animals[<span class="hljs-number">1</span>] puts animals[-<span class="hljs-number">1</span>] puts animals[<span class="hljs-number">0</span>..<span class="hljs-number">1</span>]</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li></ul>
- 上面这段代码有个语法糖,-1表示返回倒数第一个元素;当然1和其他语言一样,表示第二个元素
- animals[0..1]用了昨天学过的区间,表示从0到1个元素
<code class="hljs haml has-numbering">irb(main):002:0> [].class =<span class="ruby">> <span class="hljs-constant">Array</span> </span>irb(main):003:0> a =[] =<span class="ruby">> [] </span>irb(main):004:0> a[1] =<span class="ruby">> <span class="hljs-keyword">nil</span> </span>irb(main):005:0> b[1] NameError: undefined local variable or method `b' for main:Object from (irb):5 from /usr/bin/irb:12:in `<main>' irb(main):006:0> a[1] = 1 =<span class="ruby">> <span class="hljs-number">1</span> </span>irb(main):007:0> a[0] = 'zero' =<span class="ruby">> <span class="hljs-string">"zero"</span> </span>irb(main):009:0> a[2]=['2','3',[4,5]] =<span class="ruby">> [<span class="hljs-string">"2"</span>, <span class="hljs-string">"3"</span>, [<span class="hljs-number">4</span>, <span class="hljs-number">5</span>]] </span>irb(main):010:0> a =<span class="ruby">> [<span class="hljs-string">"zero"</span>, <span class="hljs-number">1</span>, [<span class="hljs-string">"2"</span>, <span class="hljs-string">"3"</span>, [<span class="hljs-number">4</span>, <span class="hljs-number">5</span>]]] </span>irb(main):011:0> a[2][2] =<span class="ruby">> [<span class="hljs-number">4</span>, <span class="hljs-number">5</span>] </span>irb(main):012:0> a[2][2][1] =<span class="ruby">> <span class="hljs-number">5</span> </span>irb(main):021:0> a=[] =<span class="ruby">> [] </span>irb(main):022:0> a[1]=8 =<span class="ruby">> <span class="hljs-number">8</span> </span>irb(main):023:0> a[3]=6 =<span class="ruby">> <span class="hljs-number">6</span> </span>irb(main):024:0> a =<span class="ruby">> [<span class="hljs-keyword">nil</span>, <span class="hljs-number">8</span>, <span class="hljs-keyword">nil</span>, <span class="hljs-number">6</span>]</span></code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li></ul>
比起大多数语言的数组,Ruby的数组使用很自由,从上面这些代码可以看出来。
- 数组元素不必有相同类型
- 可以只定义部分数组元素,其余位置将默认为nil值
- 需要注意的是,在给数组元素赋值时,需要首先使用a =[]类似语法让a的类型变为数组类型,否则出现上面直接运行 b[1]时报的错误
<code class="hljs haml has-numbering">irb(main):025:0> a.push(4) =<span class="ruby">> [<span class="hljs-keyword">nil</span>, <span class="hljs-number">8</span>, <span class="hljs-keyword">nil</span>, <span class="hljs-number">6</span>, <span class="hljs-number">4</span>] </span>irb(main):026:0> a.pop =<span class="ruby">> <span class="hljs-number">4</span> </span>irb(main):027:0> a =<span class="ruby">> [<span class="hljs-keyword">nil</span>, <span class="hljs-number">8</span>, <span class="hljs-keyword">nil</span>, <span class="hljs-number">6</span>]</span></code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li></ul>
数组同样支持push和pop,栈顶位于数组尾部。
2.3.4 散列表
散列表就是键值对,对应java里面的map容器。
<code class="hljs php has-numbering">irb(main):<span class="hljs-number">032</span>:<span class="hljs-number">0</span>> map={<span class="hljs-number">1</span>=><span class="hljs-string">'one'</span>,<span class="hljs-number">2</span>=><span class="hljs-string">'two'</span>} => {<span class="hljs-number">1</span>=><span class="hljs-string">"one"</span>, <span class="hljs-number">2</span>=><span class="hljs-string">"two"</span>} irb(main):<span class="hljs-number">033</span>:<span class="hljs-number">0</span>> map => {<span class="hljs-number">1</span>=><span class="hljs-string">"one"</span>, <span class="hljs-number">2</span>=><span class="hljs-string">"two"</span>} irb(main):<span class="hljs-number">034</span>:<span class="hljs-number">0</span>> map[<span class="hljs-number">1</span>] => <span class="hljs-string">"one"</span> irb(main):<span class="hljs-number">035</span>:<span class="hljs-number">0</span>> map[<span class="hljs-number">3</span>] => nil</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li></ul>
其实和数组类似,关键的不同就在于一个=>符号,符号前面是键,后面是值。
<code class="hljs php has-numbering">map={<span class="hljs-number">1</span>=><span class="hljs-string">'one'</span>,<span class="hljs-number">2</span>=><span class="hljs-string">'two'</span>} a = map.to_a puts a</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li></ul>
- 散列表可以转化为数组:其中每个元素是一个key, value组成的数组
<code class="hljs haml has-numbering">map.each { |k,v| p "#{<span class="ruby">k}</span>=>#{<span class="ruby">v}</span>" }</code><ul style="display: block;" class="pre-numbering"><li>1</li></ul>
散列表同样可以遍历
2.5 类
所有的类都有一个共同的祖先,BasicObject。
<code class="hljs ruby has-numbering"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TreeTest</span></span> <span class="hljs-keyword">attr_accessor</span> <span class="hljs-symbol">:children</span>,<span class="hljs-symbol">:node_name</span> <span class="hljs-function"><span class="hljs-keyword">def</span> </span>initialize(name,children=[]) <span class="hljs-variable">@children</span> = children <span class="hljs-variable">@node_name</span> = name <span class="hljs-keyword">end</span> <span class="hljs-keyword">end</span> ruby_tree = <span class="hljs-constant">Tree</span>.new(<span class="hljs-string">"ruby"</span>,[<span class="hljs-constant">Tree</span>.new(<span class="hljs-string">'1'</span>),<span class="hljs-constant">Tree</span>.new(<span class="hljs-string">'2'</span>)])</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li></ul>
上面代码定义了一个简单的类。
- 类命名一般使用骆驼命名法,变量和方法名采用小写下划线命名法,而常量采用全大写形式;用于逻辑测试的函数一般用?结尾
- 实例变量前需要加@(类似java的this?),而类变量前需要加@@(类似java的类名?)
- attr关键字可用来定义实例变量。它有几种版本,其中最常用的版本是attr和attr_accessor。attr定义实例变量和访问变量的同名方法,而attr_accessor定义实例变量、访问方法和设置方法。
- initialize方法有特殊含义,类在初始化一个新对象的时候,会调用这个方法
- 类初始化时采用类名加.new的方法来初始化
2.6 小结
今天主要学习了Ruby的函数,类,数组的基本使用,最大的感触是Ruby的实现充满了符合直觉的特性,在掌握了基本语法后,很多代码只需要按照尝试着写就可以执行。
2.7实践
今天实践在前面Tree类的基础上加上遍历访问的功能,同时我修改了初始化方法,接受散列表和数组嵌套的结构:
<code class="hljs ruby has-numbering"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Tree</span></span> <span class="hljs-keyword">attr_accessor</span> <span class="hljs-symbol">:children</span>,<span class="hljs-symbol">:node_name</span> <span class="hljs-function"><span class="hljs-keyword">def</span> </span>initialize(map={}) <span class="hljs-variable">@children</span> = [] map.each <span class="hljs-keyword">do</span> |k,v| <span class="hljs-variable">@node_name</span> = k v.each { |key,value| <span class="hljs-variable">@children</span>.push(<span class="hljs-constant">Tree</span>.new({key=>value}))} <span class="hljs-keyword">if</span> v!=<span class="hljs-keyword">nil</span> <span class="hljs-keyword">end</span> <span class="hljs-keyword">end</span> <span class="hljs-function"><span class="hljs-keyword">def</span> </span>visit_all(&block) visit(&block) children.each{|c| c.visit_all(&block)} <span class="hljs-keyword">end</span> <span class="hljs-function"><span class="hljs-keyword">def</span> </span>visit(&block) block.call(<span class="hljs-keyword">self</span>) <span class="hljs-keyword">end</span> <span class="hljs-keyword">end</span> ruby_tree = <span class="hljs-constant">Tree</span>.new({<span class="hljs-string">'grandpa'</span> => { <span class="hljs-string">'dad'</span> => {<span class="hljs-string">'child 1'</span> => {}, <span class="hljs-string">'child 2'</span> => {} }, <span class="hljs-string">'uncle'</span>=> {<span class="hljs-string">'child 3'</span> => {}, <span class="hljs-string">'child 4'</span> => {} } } } ) ruby_tree.visit {|node| puts node.node_name} ruby_tree.visit_all {|node| puts node.node_name}</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li></ul>
visit方法传递了代码块,调用了代码块并用self做参数(所以看起来代码块是通过这种方式获得参数?),其实就是打印了调用实例的node_name。
visit_all先对当前节点调用了visit方法,然后对子节点进行了递归。