Ruby 编程/数据类型
如前一章所述,Ruby 中的一切都是对象。Ruby 有 8 种主要数据类型,以及 3 种从 Numeric 超类派生的数据类型。一切都有类。不信?试试运行这段代码
h = {"hash?" => "yep, it\'s a hash!", "the answer to everything" => 42, :linux => "fun for coders."}
puts "Stringy string McString!".class
puts 1.class
puts 1.class.superclass
puts 1.class.superclass.superclass
puts 4.3.class
puts 4.3.class.superclass
puts nil.class
puts h.class
puts :symbol.class
puts [].class
puts (1..8).class
显示
String
Fixnum
Integer
Numeric
Float
Numeric
NilClass
Hash
Symbol
Array
Range
看到了吧?一切都 是对象。每个对象都有一个名为class的方法,它返回该对象的类。你几乎可以对任何东西调用方法。之前你看到了 3.times
形式的例子。(从技术上讲,当你调用一个方法时,你是在向对象发送消息,但我将把这一点的重要性留到以后。)
对我来说,这种极端的 面向对象特性非常有趣,因为所有类都是开放的,这意味着你可以在代码执行期间随时向类添加变量和方法。不过,这与数据类型讨论无关。
我们先从常量开始,因为它们很简单。关于常量,有两点需要注意
1. 常量以大写字母开头。Constant是一个常量。constant不是常量。
2. 你可以更改常量的值,但 Ruby 会给出警告。(我知道这很蠢...... 但你能怎么办呢?)
恭喜。现在你已经成为 Ruby 常量的专家了。
你是否注意到第一个代码列表中有一些奇怪的东西?“那个冒号到底是怎么回事?”嗯,恰好 Ruby 的面向对象方式有成本:大量的对象会导致代码变慢。每次你输入一个字符串时,Ruby 都会创建一个新对象。无论两个字符串是否相同,Ruby 都将每个实例视为一个新对象。你的代码中可能有一次出现“live long and prosper”,然后再次出现,Ruby 甚至不会意识到它们几乎是一样的东西。下面是一个示例 irb 会话,演示了这一事实
irb> "live long and prosper".object_id
=> -507772268
irb> "live long and prosper".object_id
=> -507776538
请注意,irb Ruby 返回的 对象 ID 即使对于相同的两个字符串也是不同的。
为了解决这种内存消耗问题,Ruby 提供了“符号”。Symbol
是轻量级对象,最适合用于比较和内部逻辑。如果用户永远看不到它,为什么不使用符号而不是字符串呢?你的代码会感谢你的。让我们尝试使用符号而不是字符串运行上面的代码
irb> :my_symbol.object_id
=> 150808
irb> :my_symbol.object_id
=> 150808
符号由前面的冒号表示,如下所示::symbol_name
哈希在某种程度上就像字典。你有一个键,一个引用,你查找它以找到关联的对象,即定义。
我认为,最好的说明方法是通过快速演示
hash = { :leia => "Princess from Alderaan", :han => "Rebel without a cause", :luke => "Farmboy turned Jedi"}
puts hash[:leia]
puts hash[:han]
puts hash[:luke]
显示
Princess from Alderaan
Rebel without a cause
Farmboy turned Jedi
我也可以这样写
hash = { :leia => "Princess from Alderaan", :han => "Rebel without a cause", :luke => "Farmboy turned Jedi"}
hash.each do |key, value|
puts value
end
这段代码遍历哈希中的每个元素,将键放在 key
变量中,将值放在 value
变量中,然后显示该值
Princess of Alderaan
Rebel without a cause
Farmboy turned Jedi
我本可以更详细地定义我的哈希;我可以这样写
hash = Hash.[](:leia => "Princess from Alderaan", :han => "Rebel without a cause", :luke => "Farmboy turned Jedi")
hash.each do |key, value|
puts value
end
如果我想杀了卢克,我可以这样做
hash.delete(:luke)
现在卢克不再在哈希中了。或者假设我讨厌所有农场男孩。我可以这样做
hash.delete_if {|key, value| value.downcase.match("farmboy")}
这将遍历每个键值对并将其删除,但前提是它后面的代码块返回 true
。在代码块中,我将值转换为小写(以防农场男孩开始做诸如“FaRmBoY!1!”之类的事情),然后检查“farmboy”是否与它的内容中的任何内容匹配。我本可以使用正则表达式,但那是另一回事了。
我可以通过将新值分配给哈希来将兰多加入进来
hash[:lando] = "Dashing and debonair city administrator."
我可以使用 hash.length
测量哈希。我可以使用 hash.keys
方法查看只有键,该方法将哈希的键作为 Array
返回。说到这个......
Array
很像 Hash
,除了键总是连续的数字,并且总是从 0 开始。在一个包含五个项目的 Array
中,最后一个元素将位于 array[4]
,而第一个元素将位于 array[0]
。此外,你刚学到的所有 Hash
方法也可以应用于 Array
。
以下两种方法可以创建 Array
array1 = ["hello", "this", "is", "an", "array!"]
array2 = []
array2 << "This" # index 0
array2 << "is" # index 1
array2 << "also" # index 2
array2 << "an" # index 3
array2 << "array!" # index 4
正如你可能猜到的那样,<<
运算符将值推送到 Array
的末尾。如果我在声明这两个 Array
后编写 puts array2[4]
,则输出将为 array!
。当然,如果我想同时获得 array!
并将其从数组中删除,我可以简单地 Array.pop
它。 Array.pop
方法返回数组中的最后一个元素,然后立即将其从该数组中删除
string = array2.pop
然后 string
将保存 array!
,而 array2
将减少一个元素。
如果我一直这样做,array2 将不再包含任何元素。我可以通过调用 Array.empty?
方法检查此条件。例如,以下代码段将所有元素从一个 Array
移动到另一个 Array
array1 << array2.pop until array2.empty?
这里有一件让我非常兴奋的事:Array
可以互相减去和加起来。我不能保证所有语言都这样,但我确信如果我尝试执行以下代码段,Java、C++、C# 和 perl 会觉得我疯了
array3 = array1 - array2
array4 = array1 + array2
在评估该代码后,以下所有内容都为真
array3
包含array1
中的所有元素,除了那些也存在于array2
中的元素。- 现在
array3
包含了array1
中的所有元素,减去array2
中的元素。 array4
现在包含了array1
和array2
中的所有元素。
你可以使用 Array.include?
方法在 array1
变量中搜索特定值:array1.include?("Is this in here?")
如果你只想将整个 Array
转换为 String
,你可以
string = array2.join(" ")
如果 array2
包含我们在上一个示例中声明的值,那么 string
的值将是
This is also an array!
我们可以不带任何参数调用 Array.join
方法
string = array2.join
string
的值现在将是
Thisisalsoanarray!
字符串
[edit | edit source]如果你还没有阅读过关于 字符串 和 替代引号 的章节,我建议你立即阅读。本章将涵盖一些关于 String
的非常酷的东西,并假设你已经了解这两章中的信息。
在 Ruby 中,有一些非常酷的内置函数与 String
相关。例如,你可以将它们相乘
"Danger, Will Robinson!" * 5
产生
Danger, Will Robinson!Danger, Will Robinson!Danger, Will Robinson!Danger, Will Robinson!Danger, Will Robinson!
String
也可以进行比较
"a" < "b"
产生
true
前面的计算实际上比较了字符的 ASCII 值。但是,你可能会问,给定字符的 ASCII 值是什么?在 1.9 之前的 Ruby 版本中,你可以使用以下方法查找字符的 ASCII 值
puts ?A
但是,在 Ruby 1.9 或更高版本中,这不再有效。相反,你可以尝试使用 String.ord
方法
puts "A".ord
无论哪种方法都会显示
65
这是A的 ASCII 值。只需将A替换为你想查询的任何字符。
要执行相反的转换(从65到A,例如),使用 Integer.chr
方法
puts 65.chr
显示
A
连接的工作方式与大多数其他语言相同:在两个 String
之间放置一个 +
字符将产生一个新的 String
,其值与其他值相同,一个接一个。
"Hi, this is " + "a concatenated string!"
产生
Hi, this is a concatenated string!
要处理讨厌的 String
变量而不使用连接运算符,你可以使用插值。在以下代码块中,string1
、string2
和 string3
是相同的
thing1 = "Red fish, "
thing2 = "blue fish."
string1 = thing1 + thing2 + " And so on and so forth."
string2 = "#{thing1 + thing2} And so on and so forth."
string3 = "#{thing1}#{thing2} And so on and so forth."
如果你需要遍历(即,逐步浏览)String
对象中的每个字母,可以使用 String.scan
方法
thing = "Red fish"
thing.scan(/./) {|letter| puts letter}
显示 thing
中的每个字母(puts 会在每次调用后自动添加一个换行符)
R
e
d
f
i
s
h
但是参数中的那个奇怪的 "/./
" 是什么?朋友,那是所谓的 正则表达式。它们是有用的小东西,非常强大,但超出了本次讨论的范围。你现在只需要知道 /./
是 "regex" 的说法,意思是 "任何一个字符"。如果我们使用的是 /../
,那么 Ruby 将遍历每组两个字符,并且会错过最后一个字符,因为字符数量是奇数!
正则表达式的另一个用途可以在以下操作中找到=~运算符。你可以使用匹配运算符 =~
检查 String
是否与正则表达式匹配
puts "Yeah, there's a number in this one." if "C3-P0, human-cyborg relations" =~ /[0-9]/
显示
Yeah, there's a number in this one.
String.match
方法的工作方式基本相同,除了它还可以接受一个 String
作为参数。如果你从代码外部获取正则表达式,这很有用。以下是它的实际应用
puts "Yep, they mentioned Jabba in this one." if "Jabba the Hutt".match("Jabba")
好了,关于正则表达式的介绍就到这里。即使你可以在接下来的两个示例中使用正则表达式,我们也将只使用普通的旧 String
。假设你在真理部工作,你需要用另一个词替换 String
中的一个词。你可以尝试以下操作
string1 = "2 + 2 = 4"
string2 = string1.sub("4", "5")
现在 string2
包含 2 + 2 = 5
。但是,如果 String
包含很多像你刚刚修正的谎言一样的谎言呢?String.sub
只替换第一个出现的词!我想你可以使用 String.match
方法和 while
循环来遍历 String
,但有一种更有效的方法可以实现这一点
winston = %q{ Down with Big Brother!
Down with Big Brother!
Down with Big Brother!
Down with Big Brother!
Down with Big Brother!}
winston.gsub("Down with", "Long live")
老大哥会非常高兴!String.gsub
是 "全局替代" 函数。所有出现 "Down with
" 的地方现在都被替换成了 "Long live" 所以现在winston只宣称它对老大哥的爱,而不是对老大哥的厌恶。
在这个快乐的音符上,让我们继续讨论 Integer
和 Float
。如果你想了解更多关于 String
类中的方法的信息,请查看本章末尾的快速参考表。
数字(整数和浮点数)
[edit | edit source]如果你了解所有标准的数字运算符,你可以跳过本段。对于那些不了解的人,这里有一个速成课程。+将两个数字加在一起。-将它们相减。/除。*乘。%返回两个除数的余数。
好了,整数是没有小数点的数字。浮点数是有小数点的数字。10 / 3产生3因为将两个整数相除会产生一个整数。由于整数没有小数点,你得到的只是3。如果你尝试10.0 / 3你将得到3.33333...如果你在混合中有一个浮点数,你将得到一个浮点数。明白了吗?
好了,让我们进入有趣的部分。Ruby 中的一切都是对象,让我重申一遍。这意味着几乎所有东西至少都有一种方法。整数和浮点数也不例外。首先我会展示一些整数方法。
这里我们有久负盛名的times方法。无论何时你想做某事不止一次,都可以使用它。示例
puts "I will now count to 99..."
100.times {|number| puts number}
5.times {puts "Guess what?"}
puts "I'm done!"
这将打印出数字 0 到 99,打印出猜猜看?五次,然后说我完成了!它基本上是一个简化的for循环。它比一个for循环慢几百分之一秒;如果你要为 NASA 编写 Ruby 代码,请记住这一点。;-)
好了,我们快完成了,还有六种方法要介绍。以下是其中的三种
# First a visit from The Count...
1.upto(10) {|number| puts "#{number} Ruby loops, ah-ah-ah!"}
# Then a quick stop at NASA...
puts "T-minus..."
10.downto(1) {|x| puts x}
puts "Blast-off!"
# Finally we'll settle down with an obscure Schoolhouse Rock video...
5.step(50, 5) {|x| puts x}
好了,这应该是有道理的。如果你不明白,upto从它被调用的数字开始,一直计数到它的参数中传递的数字。downto做同样的事情,只是它向下计数而不是向上计数。最后,step从它被调用的数字开始,一直计数到它参数中的第一个数字,每次计数增加它参数中的第二个数字。所以5.step(25, 5) {|x| puts x}将输出从五开始,以二十五结束的每个五的倍数。
最后三种方法
string1 = 451.to_s
string2 = 98.6.to_s
int = 4.5.to_i
float = 5.to_f
to_s将浮点数和整数转换为字符串。to_i将浮点数转换为整数。to_f将整数转换为浮点数。这就是 Ruby 中所有数据类型的概况。现在,这里是我向你承诺的字符串方法的快速参考表。
其他字符串方法
[edit | edit source]# Outputs 1585761545
"Mary J".hash
# Outputs "concatenate"
"concat" + "enate"
# Outputs "Washington"
"washington".capitalize
# Outputs "uppercase"
"UPPERCASE".downcase
# Outputs "LOWERCASE"
"lowercase".upcase
# Outputs "Henry VII"
"Henry VIII".chop
# Outputs "rorriM"
"Mirror".reverse
# Outputs 810
"All Fears".sum
# Outputs cRaZyWaTeRs
"CrAzYwAtErS".swapcase
# Outputs "Nexu" (next advances the word up one value, as if it were a number.)
"Next".next
# After this, nxt == "Neyn" (to help you understand the trippiness of next)
nxt = "Next"
20.times {nxt = nxt.next}