# 五倍先修課程-Ruby
ruby較慢,比起C語言 但不比Pyson以及PHP差
* ruby是一種泛用型的程式語言(不限定於用來開發網站)
* 腳本式程式語言
* 發明人:松本行弘,發表於1995年
### Ruby是什麼?
* 是一種物件導向語言
*幾乎所有東西都是物件
* Rails 不是一種程式語言
* CRuby = 正宗Ruby 又稱 MRI(Matz's Ruby Interpreter)
* JRuby = Ruby on JVM
* mRuby = 迷你版Ruby
<br/><br/>
---
## 環境安裝
### Windows 安裝WSL方式
1. 以系統管理員身分執行Powershell並輸入下列指令↓
```
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux
```
2. 開啟microsoft store並下載ubuntu
3. 執行ubuntu 並輸入user、password
4. 至RVM官網輸入指令安裝RVM
5. 使用指令 rvm list known 找尋可安裝Ruby版本
6. 並在RVM上輸入 rvm install 2.6.5(這裡是ruby版本)
<br/><br/>
---
## Ruby基礎篇
### 如何印出Hello world 的三種方法
1. 使用終端機指令 輸入 $ ruby -e puts"'hello world'"。
2. 使用irb(interactive ruby)。
3. 輸入code . 叫出vscode 並新增一份rb檔後 回終端機輸入 $ ruby xxx.rb。
### 印出Hello world的三種指令
1. puts "hello world" 印出 **hello world 並換行且無回傳值**
2. print "hello world" 印出 **hello world**
3. p "hello world" 印出 **包含雙引號的"hello world"並換行有回傳值**
REPL(READ-EVAL-PRINT-LOOP)撰寫[RUBY網站](https://repl.it/~)
### RUBY註解方式(comment)
#### 多行註解
```ruby
=begin
.
...
=end
```
#### 單行註解
加上#字號即可
### 何為GEM?
套件管理工具,能夠在終端機上輸入 $RUBY GEM INSTALL XXXX(小工具)後即可自動安裝此工具。
所有工具都在RUBYGEMS網站上搜尋下載並安裝。
<br/><br/>
---
## 變數與常數(Variable and constant)
### 為什麼要使用變數!?
如果要透過電腦程式存取一個名字(如:LEO)必須先給他一個容器將此名字裝進去並在外面的容器貼上一個標籤寫「NAME」,以後只要去拿容器就能夠取得容器內的名字,那NAME這個標籤就稱為變數,變數本身無任何型態。
name=leo
puts name => 印出"leo"而不是name
### 變數種類
| | 區域變數 | 全域變數 | 實體變數 | 類別變數 |
|:-------:|:-------:|:--------:|:--------:|:---------:|
| 命名樣式 | username | $username | @username | @@username |
### 變數指定
<p style="color:red;font-weight:bold;"> "="和一般程式語言不相同,並不是等於的意思而是指定!!!!</p>
a = 1
name = "leo"
#### 指定多個變數
x,y,z=[1,2,3]
a=a+2 省略寫法 a+=2
||=or的意思
a=a || 2 省略寫法 a||=2為 若a為true則為a值 否則值為2的意思
### 常數
大寫字母開頭就是常數。**在RUBY的世界裡常數是能夠被修改的,其他程式語言是不行的。**
```ruby=
BOOK="ruby book"
User="hello,user"
```
### 常數和變數有何差別?
常數有大寫開頭的限制,變數則沒有。
<br/><br/>
---
## 字串與數字
字串使用雙引號或單引號包裹住文字、數字即為字串。
例如: ```name="leo" or name='leo'```
**需注意的是,單引號不會幫你做帶入或翻譯。**
Ruby字串串接方法,使用#{}
例如: ```puts "hi,I'm #{name}"```
若外面是用單引號的話印出的結果會是 **hi,I'm #{name}**
小技巧:
若要寫 puts 'I'm leo' 因電腦會判斷出三個單引號,答案會出錯
所以可以利用"\\"讓電腦判讀此單引號為文字
寫法為 puts 'I\\'m leo ' 或 puts %q(I'm leo)
<p style="color:red;font-weight:bold;">* %q為單引號 %Q則視為雙引號!!!!!</p>
### 數字分為整數以及浮點數
#### 浮點數
puts 3.55.round #轉成整數 四捨五入
puts 3.55.floor #轉成整數 無條件捨去
puts 3.55.ceil #轉成整數 無條件進位
puts 3.55.to_i #轉成整數 無條件捨去
#### 整數除法
在Ruby裡整數除以整數會得到整數並不會有任何的小數。
例如: puts 10/3 會得到3而不是3.333
若希望得到小數可以將其中一個整數擁有小數點
例如:puts 10.0/3 or 10/3.0 or 10.0/3.0 皆可得到3.333的答案。
<h3 style="text-align:center;font-weight:bold;color:red;">在Ruby裡,數字其實也是物件</h3>
---
## 邏輯判斷以及流程控制
### IF
例如:
```ruby=
weather="下雨"
if weather=="下雨"
puts="宅在家裡"
end
```
等同於下列寫法
(IF倒裝句)
```ruby=
puts "宅在家裡" if weather=="下雨"
```
<p style="color:red">*一個等號=指定 兩個等號=比對 三個等號=也可以做比對或別的事情</p>
<p style="color:red">*在Ruby裡只有false和nail是假的,其他都是真的</p>
#### if 的evil twins是"unless"
unless = if not
unless not = if
```ruby=
weather="太陽"
if not weather=="下雨"
puts "出去玩!!!"
end
```
等同於
```ruby=
weather="太陽"
unless weather=="下雨"
puts "出去玩!!!"
end
```
unless依然也有倒裝句
```ruby=
puts "出去玩!!!" unless weather=="下雨"
```
if else 二分法
```ruby=
age=19
if age>=18
status="成年"
else
status="未成年"
end
```
也能夠使用三元運算子寫
```ruby=
status=(age>=18) ?("已成年"):("未成年")
```
if...elsif...else
```ruby=
weather="太陽"
if weather=="太陽"
puts "出去玩!!!"
elsif weather=="下雨"
puts "宅在家裡!!!"
else
puts "睡覺"
end
```
另一種寫法 case...then
```ruby=
weather="下雨"
case weather
when "下雨"
puts "宅在家裡!!!"
when "太陽"
puts "出去玩!!!"
else
puts "睡覺"
end
```
在Ruby裡,範圍可以使用". ."來標示例如
```ruby=
age>=0 && age<=3 可以標示為 0..3
```
<br/><br/>
---
## 迴圈與迭代
### 迴圈種類
* For迴圈
* while迴圈
* loop迴圈
* method式迴圈 #ruby特有
* 迭代式迴圈 #ruby常用
<p style="color:red;font-weight:bold "> *迴圈與迭代有何不同?</p>
迴圈:你就跑個五圈吧!
迭代:你把這五個元素全部都看過一次吧!
#### while迴圈
例:
```ruby=
x=0
while x<10
puts x
x+=1
end
```
#### while迴圈 evil twins 是 "until"
until=while not
while=until not
#### loop迴圈
```ruby=
i=0
loop do
puts i
i+=1
break if i>10
end
```
do...end或{ } 命名為 block
#### method式迴圈
```ruby=
5.times do
puts i
end
```
```ruby=
1.upto(10) do |i|
puts "hello ruby #{i} "
end
```
```ruby=
10.downto(1) do |i|
puts "hello ruby #{i} "
end
```
為何數字能夠使用像是times的用法?
Answer:因ruby將數字也列為數字物件之一,數字物件上有綁定幾種方法在上面像是times、upto、downto。
#### 迭代式迴圈
```ruby=
names = ["eddie", "joanne", "john", "sherly"]
names.each do |name|
puts name
end
```
若想在名字前列出索引值外鄉人寫法
```ruby=
names = ["eddie", "joanne", "john", "sherly"]
x = 0
names.each do |name|
puts "#{x} #{name}"
x += 1
end
```
內行人寫法為
```ruby=
names = ["eddie", "joanne", "john", "sherly"]
x = 0
names.each.with_index do |name,x|
puts "#{x} #{name}"
x += 1
end
#.each.with_index 給陣列加上序號
```
<br/><br/>
---
## 陣列與範圍
### 建立陣列
使用Array或是[中括號]的類別建立
a=Array.new
b=[]
```ruby=
list = %w(ruby php python)
p list # 印出 ["ruby", "php", "python"]
```
%w即可視為中括號裡面的內容會視為字串
#### 使用陣列
```ruby=
heroes = ['孫悟空', '魯夫', '宇智波佐助', '一拳超人', '流川楓', '黑崎一護', '劍心'];
puts heroes[0] # 印出 孫悟空
puts heroes[1] # 印出 魯夫
puts heroes[-1] # 印出 劍心
puts heroes[-2] # 印出 黑崎一護
```
陣列都由0開始計算,由前呼叫陣列輸入[0] 若由後往前的話輸入負數即可
在Ruby裡有撰寫的特殊用法-使用First、last *(在rails裡才有second的用法)
```ruby=
heroes = ['孫悟空', '魯夫', '宇智波佐助', '一拳超人', '流川楓', '黑崎一護', '劍心'];
puts heroes.first # 印出 孫悟空
puts heroes.last # 印出 劍心
```
如何知道陣列長度-使用.length
```ruby=
heroes = ['孫悟空', '魯夫', '宇智波佐助', '一拳超人', '流川楓', '黑崎一護', '劍心'];
puts heroes.length #印出7
```
若需要增加陣列人數可以使用.push or << 在最後面新增
```ruby=
heroes = ['孫悟空', '魯夫', '宇智波佐助', '一拳超人', '流川楓', '黑崎一護', '劍心'];
heroes << '鳴人'
或是
heroes.push ('布羅利')
```
map(等同於collect)用法
對集合裡的每一個元素進行計算,並收集成一個新的集合
```ruby=
list=[1,2,3,4,5]
p list.map {|x| x*2} #印出[2,4,6,8,10]
```
select(等同於filter,反義為reject)用法
挑選集合裡符合條件的元素,並收集成一個新的集合
```ruby=
p (1..10).select {|x| x<5} #印出[1,2,3,4]
```
reduce(等同於inject)用法
對集合裡的每個元素進行計算,並將所有計算歸納成單一結果
```ruby=
p (1..10).reduce { |sum , x| sum + x } #印出55
```
##### compact用法
取消陣列裡的空集合(nil)
##### sort用法
將陣列裡的元素由小至大排序
##### uniq用法
將陣列裡重複的元素刪除
<br/><br/>
---
### 範圍RANGE
(1..10) #範圍1~10
('A'..'Z') #範圍A-Z
```ruby=
p (1..10).to_a #印出1~10
p (1..9).to_a #印出1~9
```
平常使用兩點即可較不容易出錯 to_a表示轉型,a表示陣列
練習題1.如何印出1~100之間的所有單數
```ruby=
p (1..100).select {|x| x % 2 ==1} #或是RUBY裡能夠寫成
p (1..100).select {|x| x.odd?}
```
練習題2.如何印出總數1~100
```ruby=
p (1..100).reduce {|sum,x| sum+x} #或是RUBY裡能夠寫成
p (1..100).sum
```
練習題3.如何印出五個不重複且亂數的數字
```ruby=
p (1..100).to_a.shuffle.first(5) #或是RUBY裡能夠寫成
p (1..100).to_a.sample(5)
```
shuffle用法為將陣列打成亂數
sample用法是直接將陣列裡取樣出來
<br/><br/>
---
## 雜湊
同時擁有key與value值
a=hash.new 或使用大括號 B={}
例如:p profile = {name:'bb' , age= 18} 印出的結果卻是
profile={:name=>'bb' , :age=>18 } 為何呢?
因Hash有兩種寫法
#舊式hash寫法
1.profile={:name=>'bb' , :age=>18 }
#ruby1.9版本後新式寫法(類似json)
2.profile = {name:'bb' , age= 18}
*:age為一符號,後面會提及到
keys&values用法
profile.keys #印出 [:name , :age]
profile.values #印出 ['bb' , 18]
<br/><br/>
---
## 符號(Symbol)
Symbol 是什麼?
Symbol 有些人認為它就是的變數,或就是只是個名字,但事實上它不就是變數或名字這麼簡單,你可以想像它是一個「帶有名字的物件。
符號並不是一個變數而是一個“值”。舉個例子:數字2就是一個2的值並非變數,符號也是。如果能夠理解符號跟數字很像的話,會更簡單!
```ruby=
p 2 #本身就是一個值
p:hello #這個符號的值就是hello
```
符號也能成為變數的值
```
名稱=:名稱 等於
名稱= 2
```
但符號不能變成變數,不能將符號指定在等號的左手邊
```ruby=
:name = “bb” #這樣就錯了其實能理解成
2 = “bb”
```
但大家時常可以重置字串和符號替換,這邊就足以一下符號與字串又有某種不同的地方?
1. 串可以改變,但符號不可變
2. 符號在程式裡是固定的記憶體位置,和字串不同,字串每一次印出來的記憶體位置都不相同,因此可知符號的效能會比字串好一些些。
```ruby=
p“ name” .object_id#46955469089860 #但每次都不同
p:name.object_id#88348 #每次都相同
```
3. 字串可以使用 ` []= ` 方法改變字串內的內容,符號不行
4. 字串與符號能夠互相轉換,使用`to_s`,`to_sym`
```ruby=
name =“ kk”
p name.to_sym #:kk
p:name.to_s #“name”
```
<br/><br/>
---
## 方法(method)
如何定義方法?
```ruby=
def method_name(param1 , param2) #def 方法名稱(參數1 , 參數2)
. . . . .
end
```
如何使用?
```ruby=
def say_hello_to(someone)
puts "hi , #{someone} "
end
say_hello_to("leo") #印出 hi,leo
say_hello_to "leo"
#小括號可以省略
```
若變數與方法同名,則變數>方法會先印出變數的值
```ruby=
age = 18
def age
return 20
end
puts age #印出18
#若希望印出方法age 增加小括號即可
puts age() #印出20
```
參數預設值
```ruby=
def say_hello_to(someone = "LEO")
puts "hi , #{someone} "
end
say_hello_to #印出 LEO
```
<br/><br/>
---
## 問號與驚嘆號
### 問號
表示通常會回傳真假值
```ruby=
def is_adult?(age)
if age>=18
return true
else
return fasle
end
end
p is_adult?(20) #印出true
```
### 驚嘆號
通常表示這個需要注意
```ruby=
list = [9,5,2,7]
p list.sort #印出 [2,5,7,9]且不影響原本的list
p list.sort! #印出 [2,5,7,9]但會影響原本的list
#所以現在如果印
p list #因為sort!的緣故,會印出[2,5,7,9]而不是[9,5,2,7]
```
<br/><br/>
---
## 模組化
不可能將所有程式碼寫入同一個檔案,所以時常會需要呼叫其他檔案的時候
```ruby=
#在a.rb檔裡
def bmi_calculator(height, weight)
weight / (height * height)
end
```
如何將a.rb檔帶入main.rb檔? 使用require、load
### require
```ruby=
#在main.rb檔裡
require './a.rb'
p bmi_calculator(178,60)
```
### load
```ruby=
#在main.rb檔裡
load './a.rb'
p bmi_calculator(178,60)
```
那兩者又有什麼差別?
* require 可以省略.rb的副檔名,load是明確的載入某一個檔案
* require 只會載入一次,load每執行一次就會載入一次
<br/><br/>
---
## Block(區塊)
{ } 或 do..end兩種寫法,稱為Block,是一段不會被主動執行的程式碼。
### 控制權
下列這段程式碼here並不會被印出來,為何?
```ruby=
def say_hello
puts "hi"
end
say_hello { puts "here" } #here並不會被印出來 為何?
puts "there"
```
因為block不會主動執行,必須依附在方法上面,他會不會執行必須看宿主臉色。
那要如何讓這段程式碼執行? 使用yield
### yield
暫時將控制權交給block。
```ruby=
def say_hello
puts "Hello, 你好"
yield #將控制權交給下方say hello的block
puts "Hello, 大家好"
end
say_hello {
puts "here!"
}
puts "there!"
```
不僅如此yield不只能交給控制權還能帶伴手禮還可以帶不只一個!!!
```ruby=
def say_hello
yield 3 #將控制權交給下方say hello的block且帶一個3伴手禮給他
end
say_hello { |n| puts "n" } #印出 3
```
block會將最後執行的結果回傳給yield
```ruby=
def test_two
if yield(3)
puts "yes, it is 2"
else
puts "no, it is not 2"
end
end
test_two { |n|
n == 2
} #這邊將3帶入 結果為Fasle 回傳後可知 puts的結果為no...
```
如果沒有Block但卻yield的話會怎麼樣?
```ruby=
def say_hello
yield
end
say_hello
#顯示錯誤訊息 in `say_hello': no block given (yield) (LocalJumpError)
```
這時可以使用 Ruby內建方法 block_given?
```ruby=
def say_hello
if block_given? #若有Block就執行否則不執行yield
yield
end
end
say_hello
```
大括號與do...end 寫法有何不同?
* 結合率強度不一樣 大括號強於do..end
```ruby=
list =[1,2,3,4,5]
p list.map { |x|
x*2
} #大括號結合率比p強所以印出正確答案 [2,4,6,8,10]
p list.map do |x|
x*2
end #do..end結合率較p弱導致印出#<Enumerator: [1, 2, 3, 4, 5]:map> 錯誤訊息
```
### 物件化的block
block不能單獨存活但可以讓它變成物件就可以單獨存活。可以使用下列三種方法
#### proc
```ruby=
add_two = Proc.new {|n| n+2 }
p add_two.call(3)
p add_two[3]
p add_two.(3)
p add_two.===(3) # 以上皆印出 5
```
#### lambda
```ruby=
add_two = lambda {|n| n+2 }
p add_two.call(3)
```
#### ->
```ruby=
add_two = -> (n) { n+2 }
p add_two.call(3)
```
<br/><br/>
---
## 物件導向程式設計(Object-Oriented Programming)
什麼是物件? 物件=狀態(名詞) + 行為(動詞)
在Ruby裡,幾乎所有的東西都是物件,那有哪些不是物件? -> block
### 類別與實體(class & instance)
比如雞蛋糕,雞蛋糕的烤盤就是類別,而雞蛋糕就是實體!
### 定義類別(class)
```ruby=
class Cat #定義一個貓的類別
def eat(food)
puts "#{food} 好好吃!!"
end
end
```
* 定義類別必須要使用常數!!!!!(第一個字母需大寫)
如何產生實體? 使用 類別.new
```ruby=
class Cat #定義一個貓的類別
def eat(food)
puts "#{food} 好好吃!!"
end
end
kitty = Cat.new
kitty.eat "魚" #印出 魚 好好吃!!
```
### 繼承(inheritance)
```ruby=
class 動物 #定義一個貓的類別
end
class 靈長目 < 動物 #靈長目被分類再動物底下
def 五指對握
end
class 人 < 靈長目 #人的類別被分類再靈長目底下
end
#所以只要是靈長目下的類別(人)都可以使用五指對握這個方法)
```
## 物件初始化(initialize)
讓一個物件就算沒有呼叫也會執行自己初始化設定的內容
```ruby=
class Cat
def initialize
puts "hi"
end
end
kitty = Cat.new #就算沒有呼叫也都會印出HI
```
初始化也能夠帶入參數
```ruby=
class Cat
def initialize(name , age)
@name = name
@age = age
puts "hi #{name} #{age}"
end
end
kitty = Cat.new("kk" , 18)
```
如果沒有帶入引數,會引發數個錯誤切記!!!
## 實體方法與類別方法
### 實體方法
作用在實體上的方法
```ruby=
class Cat
def say_hello
puts "你好"
end
end
kitty = Cat.new
kitty.say_hello #實體方法
```
### 類別方法
作用在類別上的方法
```ruby=
class Cat
def self.say_hello
puts "hi"
end
end
Cat.say_hello #類別方法
```
## 實體變數與類別變數
| | 區域變數 | 全域變數 | 實體變數 | 類別變數 |
|:-------:|:-------:|:--------:|:--------:|:---------:|
| 命名樣式 | username | $username | @username | @@username |
### 實體變數
前面必須有一個@,並且存活在每個獨立的實體內,在實體裡可自由取用的變數
```ruby=
class Cat
def initialize(name)
@name =name
end
def say_hello
return @name
end
end
kitty= Cat.new("kitty")
puts kitty.say_hello #回傳@name值也就是kitty 所以會印出kitty
```
但實體變數在實體外就不能取用了
```ruby=
class Cat
def initialize(name)
@name =name
end
def say_hello
return @name
end
end
kitty= Cat.new("kitty")
puts kitty.name #是在呼叫 .name()的方法
```
所以要在設定一個name的方法才會沒有錯誤
```ruby=
class Cat
def initialize(name)
@name =name
end
def name #getter
return @name
end
end
kitty= Cat.new("kitty")
puts kitty.name #印出 kitty
```
那再來看如果再新增 `kitty.name = nancy` 又會有什麼問題出現?
```ruby=
class Cat
def initialize(name)
@name =name
end
def name #getter
return @name
end
end
kitty= Cat.new("kitty")
puts kitty.name #印出 kitty
kitty.name = "nancy" #會視為呼叫 .name= 的方法
#出現錯誤訊息 undefined method `name=' for #<Cat:0x000055e73d1b51a0
# @name="kitty"
```
```ruby=
kitty.name = "nancy"
kitty.name="nancy"
kitty.name=("nancy") #三者皆為同一種語法
```
所以一樣要定義一個name=的方法出來才能夠正常執行
```ruby=
class Cat
def initialize(name)
@name =name
end
def name #getter
return @name
end
def name=(new_name) #setter
return @name
end
end
kitty= Cat.new("kitty")
puts kitty.name
puts kitty.name=("nancy") #正確印出nancy
```
### attr_writer 、 attr_reader 、 attr_accessor
#### attr_reader 取得
`attr_reader :name`
等同於上述程式碼
```ruby=
def name #getter
return @name
end
```
#### attr_writer 設定
`attr_writer :name`
等同於上述程式碼
```ruby=
def name=(new_name) #setter
return @name
end
```
#### attr_accessor 可讀可寫並且等於reader+writer
`attr_accessor :name`
等同於
```ruby=
attr_writer :name
attr_reader :name
```
所以程式碼可以簡寫為
```ruby=
class Cat
attr_accessor :name
def initialize(name)
@name =name
end
end
kitty= Cat.new("kitty")
puts kitty.name
puts kitty.name=("nancy")
```
### 類別變數
前面必須有一個@@,在類別裡可自由取用的變數
```ruby=
class Cat
@@counter = 0
def initialize
@@counter += 1
end
def self.counter
@@counter
end
end
5.times { Cat.new }
p Cat.counter #印出5
```
## 開放類別
若遇到重複定義的類別,不會衝突而是會融合!!!
```ruby=
class Cat
def hello
end
end
class Cat
def world
end
end
kitty = Cat.new
kitty.hello # 可正常運作
kitty.world # 可正常運作
```
內建類別也能夠定義為全新的類別。如:重新定義String 使字串後方都能夠接方法
```ruby=
class String
def say_hello
"オッス!オラ#{self}"
end
end
puts "悟空".say_hello #=> 印出「オッス!オラ悟空」
```
又或是重新定義length
```ruby=
class String
def length
100 #蓋掉原本lenth的定義
end
end
puts "悟空".length #=> 印出 100
```
那麼如果重新定義Integer
```ruby=
class Integer
def +(n)
100 #蓋掉原本+號的定義
end
end
puts 1+2 #=> 印出 100
```
* puts 1+2 在ruby裡其實可以看成 puts 1.+(2)