快速入门——Ruby
最近在学习网页制作,脚本语言自然是必不可少了,除了js,如今很热的ruby也是 Jekyll+Github一套系统里的必用语句,下面是学习记录。
安装ruby
这里安装有指导。
打开 IRB
我的是mac系统,一般我打开终端的方法是快捷键将spotlight打开,然后直接输terminal回车就好了,这也算是一个小技巧吧~
在terminal中输入irb
, 回车。接下来出现下一行命令:irb(main):001:0>
; 在后面输入:"Hello World"
;出现:=> "hello world"
刚才是怎么回事啊?难道我们刚写了世界上最短的 “Hello World” 程序吗? 不是的。第二行显示的只是 IRB 给我们的上一个命令的返回值。 如果我们要打印 “Hello World” 的话,还需要更多一点代码:
irb(main):002:0> puts "Hello World"
Hello World
=> nil
puts
是 Ruby 语言里用来打印的基本命令。那 => nil
是什么?其实是命令的返回值。 puts
命令永远返回 nil,nil 也是 Ruby 的空值。
计算器功能
加法:3+2
;乘法:3*2
;乘方:3**2
;开根:Math.sqrt(9)
(其实ruby的计算功能并不是我们这里真正想讲的,所以我只列了几个具有代表性的。真正想用计算功能的话可以看我的matlab和R语言笔记)
模块,给代码分组
上面我们开根的时候用到了Math
,这到底是什么东西呢。
Math
是一个自带的数学模块。模块在Ruby里有两个作用。第一: 把功能相似的函数放到同一个名称下。Math
模块还有 sin() 和 tan() 这样的函数。
接下来的是一个点。点是干什么用的?点是用来告诉一个接收者它所要接受的信息。 什么是信息?在这个例子里面,信息就是 sqrt(9)
,意思就是调用 sqrt
函数, 并给它 9 作为参数。当然 sqrt
就是 “square root” 的缩写。
这个函数的返回值是 3.0。您一定发现了它不光只是3,还多了小数部分。 这是因为大多数情况下,开方的结果并不是整数,所以 sqrt
始终会返回浮点数。
如果我们想记住运算结果呢?存到变量里吧。
irb(main):007:0> a = 3 ** 2
=> 9
irb(main):008:0> b = 4 ** 2
=> 16
irb(main):009:0> Math.sqrt(a+b)
=> 5.0
定义函数
如果您想说很多次”Hello”,却不想把手指累酸的话,是定义一个函数的时候啦!
irb(main):010:0> def h
irb(main):011:1> puts "Hello World!"
irb(main):012:1> end
=> nil
def h
定义一个函数。它告诉 Ruby 我们的函数名字是 h
。下一行是函数的内容, 正是我们前面看到过的那行代码:puts "Hello World!"
。最后的一行 end
告诉 Ruby 函数的定义完成了。
我们来试着把这个函数调用几次:
irb(main):013:0> h
Hello World!
=> nil
irb(main):014:0> h()
Hello World!
=> nil
很方便吧。在 Ruby 里调用函数就像提起 Ruby 的名字一样简单。如果函数不需要接受参数, 您只要提到它就够了。您可以加一对括号,但不是必需的。
如果您想对一个人而不是全世界说您好呢?只要让 h 函数接受一个参数就可以了。
irb(main):015:0> def h(name)
irb(main):016:1> puts "Hello #{name}!"
irb(main):017:1> end
=> nil
irb(main):018:0> h("LiuXin")
Hello LiuXin!
=> nil
和期待的一样。让我们再仔细看看究竟发生了什么。
在字符串中预留位置
啥是 #{name}
啊?这是 Ruby 用来往字符串中插入信息的方法。大括号里面的代码会被替换为评估后的字符串 (如果他们还不是字符串的话)然后插入到包含大括号的原始字符串中去。 您可以用这个方法将人名大写:
irb(main):019:0> def h(name = "World")
irb(main):020:1> puts "Hello #{name.capitalize}!"
irb(main):021:1> end
=> nil
irb(main):022:0> h "chris"
Hello Chris!
=> nil
irb(main):023:0> h
Hello World!
=> nil
这里还有几个小窍门。第一是我们又一次省略了函数的括号。如果我们的命令看起来意图很明显的话, 函数的括号是可以省略的。另一个是函数缺省的参数值是 World。意思就是说 “如果 name 参数没有给出的话, name 的缺省值就设置为 “World"。
建立类
让我们更有礼貌一些,不光记住您的名字,还在您到来的时候欢迎您,并且始终彬彬有礼。 您可以开始使用对象了。我们先建立 ”Greeter” (有礼貌的人) 类。
irb(main):024:0> class Greeter
irb(main):025:1> def initialize(name = "World")
irb(main):026:2> @name = name
irb(main):027:2> end
irb(main):028:1> def say_hi
irb(main):029:2> puts "Hi #{@name}!"
irb(main):030:2> end
irb(main):031:1> def say_bye
irb(main):032:2> puts "Bye #{@name}, come back soon."
irb(main):033:2> end
irb(main):034:1> end
=> nil
新的关键字 class
! 这个关键字定义了一个新的类 Greeter
和它的一些函数。 特别留意一下@name
,这是一个实例变量。类里面的任何函数都可以使用实例变量。 您可以看到 say_hi
和 say_bye
函数都使用了它。
对象
在上面的例子中,我们建立了一个名叫“Greeter”的类,接下来我们可以在这个类中建立一个 greeter 对象然后使用它:
irb(main):035:0> g = Greeter.new("Pat")
=> #<Greeter:0x16cac @name="Pat">
irb(main):036:0> g.say_hi
Hi Pat!
=> nil
irb(main):037:0> g.say_bye
Bye Pat, come back soon.
=> nil
当 g 对象被建立后,它就记住了名字属性的值 Pat. 额… 如果我们想直接读取名字的值呢?
irb(main):038:0> g.@name
SyntaxError: compile error
(irb):52: syntax error
from (irb):52
晕,做不到。
其实实例变量是被隐藏起来的,但他们并不是被完全的隐藏起来。 当您检查一个对象的时候还是可以看到他们的。Ruby采用了面向对象的思想,将内部属性保护了起来。
到底 Greeter 有哪些函数呢?
irb(main):039:0> Greeter.instance_methods
=> ["method", "send", "object_id", "singleton_methods",
"__send__", "equal?", "taint", "frozen?",
"instance_variable_get", "kind_of?", "to_a",
"instance_eval", "type", "protected_methods", "extend",
"eql?", "display", "instance_variable_set", "hash",
"is_a?", "to_s", "class", "tainted?", "private_methods",
"untaint", "say_hi", "id", "inspect", "==", "===",
"clone", "public_methods", "respond_to?", "freeze",
"say_bye", "__id__", "=~", "methods", "nil?", "dup",
"instance_variables", "instance_of?"]
哇。。。有这么多!可我们只定义了两个啊,怎么回事?这里列出的其实是 Greeter 对象包含的 所有 的函数,当然也就包括了它所继承的类的函数了。如果我们只希望列出 Greeter
自己的函数,可以提供一个 false
参数给 instance_methods
,表示我们不希望列出祖先类的函数。
irb(main):040:0> Greeter.instance_methods(false)
=> ["say_bye", "say_hi"]
看起来好多了。我们来看看 greeter 会对哪些函数作出回应:
irb(main):041:0> g.respond_to?("name")
=> false
irb(main):042:0> g.respond_to?("say_hi")
=> true
irb(main):043:0> g.respond_to?("to_s")
=> true
它知道 say_hi
和 to_s
(意思是把什么东西转换成字符串,这是每个对象都有的功能), 但是它不知道 name
。
改变已有的类
假如您想获取甚至改变名字属性呢?Ruby 提供了一个简单的方法来访问属性。
irb(main):044:0> class Greeter
irb(main):045:1> attr_accessor :name
irb(main):046:1> end
=> nil
在 Ruby 里,您可以把一个类打开然后改变它。这些改变会对以后生成的甚至是已经生成的对象产生即时效果。 下面我们来建一个新的 Greeter 对象,然后看一看它的 @name
属性。
irb(main):047:0> g = Greeter.new("Andy")
=> #<Greeter:0x3c9b0 @name="Andy">
irb(main):048:0> g.respond_to?("name")
=> true
irb(main):049:0> g.respond_to?("name=")
=> true
irb(main):050:0> g.say_hi
Hi Andy!
=> nil
irb(main):051:0> g.name="Betty"
=> "Betty"
irb(main):052:0> g
=> #<Greeter:0x3c9b0 @name="Betty">
irb(main):053:0> g.name
=> "Betty"
irb(main):054:0> g.say_hi
Hi Betty!
=> nil
attr_accessor
会自动为我们定义两个新的函数, name
用来读取变量的值, name=
用来给变量赋值。
已经建立好的这个 greeter 不是那么有新意,它一次只能向一个人问好。如果我们有一个 MegaGreeter 可以同时向世界,一个人,甚至向一群人问好,那该多好啊?
我们不再使用互动 Ruby 的解析器 IRB 了,而是把代码写到一个文件里。
输入 “exit” 或者按下 Control-D 退出 IRB。
#!/usr/bin/env ruby
class MegaGreeter
attr_accessor :names
# Create the object
def initialize(names = "World")
@names = names
end
# Say hi to everybody
def say_hi
if @names.nil?
puts "..."
elsif @names.respond_to?("each")
# @names is a list of some kind, iterate!
@names.each do |name|
puts "Hello #{name}!"
end
else
puts "Hello #{@names}!"
end
end
# Say bye to everybody
def say_bye
if @names.nil?
puts "..."
elsif @names.respond_to?("join")
# Join the list elements with commas
puts "Goodbye #{@names.join(", ")}. Come back soon!"
else
puts "Goodbye #{@names}. Come back soon!"
end
end
end
if __FILE__ == $0
mg = MegaGreeter.new
mg.say_hi
mg.say_bye
# Change name to be "Zeke"
mg.names = "Zeke"
mg.say_hi
mg.say_bye
# Change the name to an array of names
mg.names = ["Albert", "Brenda", "Charles",
"Dave", "Engelbert"]
mg.say_hi
mg.say_bye
# Change to nil
mg.names = nil
mg.say_hi
mg.say_bye
end
把这个文件储存到 “ri20min.rb”, 然后在命令行输入 “ruby ri20min.rb” 来运行它。 您应该可以看到::
Hello World!
Goodbye World. Come back soon!
Hello Zeke!
Goodbye Zeke. Come back soon!
Hello Albert!
Hello Brenda!
Hello Charles!
Hello Dave!
Hello Engelbert!
Goodbye Albert, Brenda, Charles, Dave, Engelbert. Come
back soon!
...
...
下面我们来看看这个新的程序。请注意由 (#) 开始的第一行。 在 Ruby 里面,任何代码中井字符后面的内容都会被解释器忽略。 而第一行有点特别,因为在 Unix 操作系统下, 井字符开头的第一行告诉了系统的 Shell 如何执行这个文件。其他井字符引导的注释只是起说明的作用。
我们熟悉的 say_hi 函数也了有点变化:
# Say hi to everybody
def say_hi
if @names.nil?
puts "..."
elsif @names.respond_to?("each")
# @names is a list of some kind, iterate!
@names.each do |name|
puts "Hello #{name}!"
end
else
puts "Hello #{@names}!"
end
end
它现在会根据 @names
参数的不同而采取不同的行动。如果参数是 nil
,它会打印三个点。没有理由和空气问好对吗?
循环,迭代
如果 @names
对象可以回应 each
函数,那它就是可以被迭代的, 于是我们对它做迭代,向每个人问好。最后如果 @names
是其他的类, 就把它转化为字符串,用默认的方式问好。
下面看一看这个迭代结构
@names.each do |name|
puts "Hello #{name}!"
end
each
是一个可以接受代码块的函数。它在迭代每一个元素时都会调用一次之前所接受到的代码块。 代码块像是一个不需要命名的函数,和 lambda
类似。 在| |
之间的就是传输给代码块的参数。
具体来说就是在每一次循环中,name
首先被赋值为 list
里面一个对应元素的值, 然后作为参数传递到 puts "Hello #{name}!"
这句命令里。
大多数编程语言都是用 for
循环来完成迭代的,比如在 C 里面:
for (i=0; i<number_of_elements; i++)
{
do_something_with(element[i]);
}
这样也成,不过不那么优美。您需要一个没什么意思的 i 来监控列表长度和检测循环退出的判断。 Ruby 的方法对比来看就更清爽,所有的清理工作都被隐藏在 each 函数里了, 您只需要告诉它您想做什么。在 each
函数内部,实际上会去自动调用 yield "Albert"
,yield "Brenda"
,yield "Charles"
,等等。
代码块,让 Ruby 闪亮
代码块最有用的地方是用来处理比迭代列表更繁琐的工作。除了一般家务活之外, 您可以用它来自动安装卸载或处理运行错误。真正做到让用户省心、放心。
# Say bye to everybody
def say_bye
if @names.nil?
puts "..."
elsif @names.respond_to?("join")
# Join the list elements with commas
puts "Goodbye #{@names.join(", ")}. Come back soon!"
else
puts "Goodbye #{@names}. Come back soon!"
end
end
say_bye
函数并没有用到 each
,而是查看 @names
是否支持 join
函数。如果是的话就调用,否则就简单的将变量转化为字符串。 这种不在乎类的作风就是常说的“鸭子型”,意思就是说“如果它走起来像鸭子,叫起来像鸭子, 那它一定是鸭子”。这种思想的好处就是不限制函数所支持的参数类别。 如果有人写了一个新的类,只要它像其他列表类一样回应 join
这个函数, 那它就可以被相应的其他函数所使用。
让脚本跑起来
这就是 MegaGreeter
类的所有内容了。剩下的代码就只是调用一下这个类的函数。 还有最后一点小技术在这里:
if __FILE__ == $0
__FILE__
是一个魔法值,它存有现在运行的脚本文件的名字。$0
是启动脚本的名字。 代码里的比较结构的意思是 “如果这是启动脚本的话…” 这允许代码作为库调用的时候不运行启动代码, 而在作为执行脚本的时候调用启动代码。
总结
经过我精炼后的笔记,这个教程差不多20分钟就能完成跟着做一遍,但 Ruby 还有无数值得探索的地方:代码块与 yield, 模块与 mixins,等等。在接下来做网站的日子里希望能继续学习,这是我的网站:www.nano-er.com, 蟹蟹~