第六章、向世界打招呼(輸出、四則運算與資料型態)

Note

從本章開始,將會開始正式進入程式設計教學,因此請您先準備好相關的開發工具,進行本章前建議您可以:

  1. 依照『安裝 Scala 開發環境』中的說明,安裝 Scala 開發環境,以方便一邊閱讀一邊實際操作撰寫程式(強列推薦)。
  2. 如果您只是想要先試試看寫程式是什麼感覺,也可以連線到 Simply Scala 網站,將文章中的程式碼輸入至中間的空白區域,並按下 Evalute 按鈕觀看結果。

「那我們接下來就直接開始來寫程式吧,是說筆記型電腦的畫面太小了不太方便,妳們先等我一下,另外……」

桐仁走擺在一旁的鐵櫃,將擺在最下層的投影機搬到桌上,同時順手將擺在鐵櫃上的一份影印資料交給音羽後說道:「要學寫程式,實際練習也是很重要的。這是我之前寫的開發環境安裝教學,音羽同學,妳可以幫忙在今天回去後,教深夏同學按照這份教學在自己的電腦上安裝開發環境嗎?這樣以後教起來會比較方便。」

「沒問題,包在我身上!咦……?學長,可是這上面寫的是 Scala 安裝教學耶,Scala 是什麼啊?」

「Scala 也是一種程式語言。應該還記得剛剛講的 Java 程式語言的執行模式吧?因為 Java Virtual Machine (JVM) 執行的實際上是 Bytecode,所以只要其他程式語言能夠編譯出 Java 虛擬機器的 Bytecode,就可以交由 JVM 執行,Scala 就是這類型的程式語言。這次我打算先讓深夏同學直接用這個程式語言練習……」

「可是桐仁學長剛剛不是說老師下學期教的會是 Java 嗎?」

「就是說啊,學長!小深夏是個程式笨蛋耶,連 C 語言都學不會了,現在不快點教下學期的東西,她下學期一定會被當的啦!我絕對不能允許這樣的事發生,所以……」

正當音羽正準備繼續發表長篇大論時,桐仁趕忙伸手制止她並說道:「音羽同學妳先冷靜一下……我剛剛也說過了,C 語言和 Java 雖然是業界常用的程式語言,卻不適合用來教初學者對吧。再說,其實學寫程式,重要的反而不是學什麼樣的程式語言和語法喲,所以我覺得直接教深夏同學 Scala 反而比較適合。」

「可是我們在寫程式作業的時候,明明只要少打了一個括號之類的,就連編譯都無法通過啊?」音羽露出不解的眼神問道。

「沒錯,要寫出能夠執行的程式確實語法一定要正確才行。不過如果重點是要學會寫程式的話,更重要的反而是心智模型的建立喲,畢竟一理通,百理同嘛。」

「心智模型?」

「就是怎麼樣看待以及理解像你們常常在程式設計的課上面聽到的變數迴圈遞迴這類的概念,還有怎麼樣把要解決的問題轉化成用電腦的角度來思考,語法什麼的反而是枝微末節了。也因為這樣,所以會寫程式的人基本上不管接觸什麼程式語言,都能夠很快上手的。」

看到音羽和深夏很明顯地就是有聽沒有懂,桐仁只能尷尬地笑了笑後說道:「嘛……這個對現在的妳們可能比較難懂吧,反正先就先試試如何,深夏同學?」

「嗯……好吧。」

深夏猶豫了一會兒後點頭說道,而桐仁則坐到位置上開始調整投影機投射出的電腦畫面,將接在 USB 插槽上的外接鍵盤推到深夏面前後,打開終端機視窗並在上頭輸入了 scala 這個指令並按下 Enter 鍵。[1]

接著視窗上出現了一小段的文字訊息:

Welcome to Scala version 2.9.1.final (OpenJDK 64-Bit Server VM, Java 1.6.0_22).
Type in expressions to have them evaluated.
Type :help for more information.

scala>

「好,我們開始吧。深夏同學,你先在上面輸入 println ("Hello World") 後按下 Enter 試試看……」

聽到桐仁這麼說,深夏慢慢地在鍵盤上輸入桐仁所說的程式碼,雖然按了幾次 Backspace 鍵做修正,但最後還是順利地輸入了。就在深夏吁了一口氣時,畫面上也多了一行 Hello World 的文字,除此之外,畫面最底下也再次印出了 scala> 的字樣。

scala> println ("Hello World")
Hello World

scala>

「恭禧妳,深夏同學妳已經完成程式設計的第一堂課囉。」

「咦耶?」

深夏瞪大了眼睛,懷疑自己是不是聽錯了,畢竟自己只是輸入一段不明所以的英文啊,已經完成第一堂課是怎麼一回事呢?

看著這樣的深夏,桐仁繼續笑著說道:「呵呵,我可沒有騙妳喲,如果妳去書店隨便翻一本程式設計的教學書籍,第一個隻的程式十之八九都是在螢莫上印出 Hello World 的字樣,而妳剛剛輸入的那一行 println ("Hello Wrold") 就是個完整的 Scala 程式囉,妳可以把 println 想像成類似小人電腦裡的 OUTPUT 指令,會將括號裡面的東西印在螢幕上。」

「可、可是學長,我記得之前老師上課時寫的第一隻 C 語言程式有很多東西耶,又是主函式又是什麼的,而且這隻程式也沒經過編譯啊。」

「嗯,這是因為 Scala 同時支援了編譯和直譯模式,妳現在看到的就是直譯模式下的 REPL 交談式介面[2],它會讀取妳輸入的指令,一句一句翻譯成 bytecode 後再交給 JVM 執行,最後將執行和運算的結果顯示在畫面上。那個 scala 的字樣就是在告訴妳,它已經準備好,所以妳可以輸入下一個指令了。」

桐仁繼續說道:「接下來妳先分別在每行輸入 121 + 2 試試看。」

「好、好的。」深夏應道,並且在畫面上輸入相對應的程式碼,然而與剛剛只是出現單純的 Hello World 字樣不同,畫面上多了一些深夏沒看過的訊息。

scala> 1
res1: Int = 1

scala> 2
res2: Int = 2

scala> 1 + 2
res3: Int = 3

scala>

「咦,只是輸入運算式就會印出結果嗎?!這東西好像好好玩的感覺耶。」雖然操作的是深夏,不過音羽似乎對可以直接看到結果感到滿興奮的。

「運……運算式?」

「簡單的說,運算式就是可以進一步化約成某一個特定數值的程式指令,像第一行的 1 + 2 經過數學計算後會是 3,所以我們會說 1 + 2 是一個運算式,當然,像 12 這種本身就是數值的東西,也算是運算式的一種。深夏同學,妳再試著輸入這些試試看。」

深夏照著桐仁的指示,依序鍵入下面的這四個指令,而終端機視窗上也出現相對應的計算結果。[3]

  • 0.5
  • 3
  • 3 * 0.5 + 1
  • 3 * (0.5 + 1)
scala> 0.5
res3: Double = 0.5

scala> 3
res4: Int = 3

scala> 3 * 0.5 + 1
res5: Double = 2.5

scala> 3 * (0.5 + 1)
res6: Double = 4.5

「學長,最後一個指令好像也是先乘除後加減,然後括號裡的先算耶,所以說運算式就是數學上的算式囉?」深夏轉過頭向桐仁問道。

「唔嗯……」桐仁將下巴靠在握成拳狀的右手上,沉吟了一會兒後說道:「好像有點微妙的不同呢,深夏同學妳先輸入 5 / 2"Hello " + "World" 這兩個運算式試試看。」[4]

「嗯。」深夏輕輕應了一聲後,繼續輸入桐仁所說的程式碼。

scala> 5 / 2
res7: Int = 2

scala> "Hello " + "World"
res8: java.lang.String = Hello World

「如何,有發現什麼和數學不一樣的地方嗎?」

「5 除 2 不是應該等於 2.5 嗎?還有……數學上的加號左右兩邊,只能有數值,但這邊的 "Hello " 和 "World" 並不是數耶,另外這次多了個 java.lang.String 是什麼啊?」

「Bigno~桐仁學長,小深夏好像變得比較聰明了耶?!」

「呵呵,我還是從頭來詳細說明吧。首先 5 / 2 會得出 2 而不是 2.5 的原因,是因為除號兩邊的資料型態都是整數,而在 Scala 中,整數和整數相除,算出的結果只會是整數,所有除不盡的部份會直接被吃掉,所以 5 / 2 的答案會是 2。」

「咕嗯……整數我知道,不過資料型態是什麼啊,學長?」

「計算機概論的課堂上應該有提過吧?現在的電腦都是使用二進制,實際上儲存的東西都是 0101 這樣的……」

「老師好像是有提過沒錯。」深夏點頭說道。

「但實際上在寫程式的時候,我們可能會想使用十進位整數、實數和處理某些文件的資料,而所謂的資料型態就是指程式要如何去解讀這些 0101 的東西。另外,所有的運算式簡化到最後,都會算出某種資料類型的結果,而 Scala REPL 也會把它的結果和型態印出來。」

「咦?有嗎?」

「嗯,剛剛的運算式被輸入後,不是都會出現一個長得像 resOO: XXXX = NNNNN 的東西嗎?其中 OO 的部份是指這是這次輸入的第幾個運算式,XXXX 的部份就是這個運算式算出來的結果的資料型態,當然等號後面的就是運算式的結果囉。」

桐仁說到這兒,開始操作起電腦,打開一份放在桌面上的文件,並將充滿表格的視窗拉到原本的終端機視窗旁後繼續說道:「吶,像剛剛 res1res2 的部份,因為輸入的是整數,而在 Scala 中表示整數的型態是 Int,所以 res1res2 的資料型態就是 Int。而整數和整數相加得出的也是整數,所以 res3 的型態是 Int……依此類推,res5 也會是 Int。」

整數資料型態

資料型態 慣用翻譯 寫法 範圍 佔用記憶體 [6]
Byte   127 -128 ~ 127 1 byte
Short   3273 -32768 ~ 32767 2 byte
Int 整數 65535 -2147483648 ~ 2147483647 4 byte
Long 長整數 100000L -9223372036854775808 ~ 9223372036854775807 8 byte

「是說這個表上面怎麼光整數就有四種啊?他們有什麼不同啊?」

「小深夏,妳輸入 2147483647 + 1 試試看。」

scala> 2147483647 + 1
res9: Int = -2147483648

「喔……奇怪,為什麼正數加一後會變負數啊?不是應該是 2147483648 嗎?」深夏一頭霧水的說道。

「小深夏,妳再仔細看一下 Int 那行,它的範圍是多?」

「唔……剛好是 2147483647……所以是因為這個 Int 沒辦法表達出 2147483648 這個數字嗎?」

「沒錯,」桐仁補充說道:「這個就叫溢位,因為 Int 可以表達的最大數字就是 2147483647,如果繼續往上加,就會從最小的值 -2147483648 開始往上算,這是由於它是用二補數來表達的關係,是說現在應該不用太計較這些小細節,只要記得如果運算式最後算出來的結果很奇怪,有可能是溢位造成的就可以了。」[5]

「那如果需要計算 2147483647 以上的數字呢?」深夏問道。

「小深夏妳果然還是笨蛋沒錯,這個時候當然就直接用長整數來算就好啦。」

「嗯……深夏同學,你再試著輸入 2147483647L + 100 看看。」

scala> 2147483647L + 100
res10: Long = 2147483747

「啊,這次好像正常了。」

「沒錯,而且有沒有注意到 res10 的型態變成 Long 了對吧?如果要把一個整數當做 Long 來看的話,只要在整數後面加上 Ll 就可以了。」

「不過學長,我還是不懂耶……既然 Long 可以表示的範圍這麼比較大,為什麼不直接把所有整數都用 Long 來表示,還要分成四種不同的資料型態呢?」

「好問題……這是因為在以前記憶體的容量不像現在都是以 GB 為單位來計算那麼大,如果程式需要表達的數字不需要那麼大,那麼用 4 或 8 個 byte 來儲存整數,就顯得很浪費記憶體空間了。」

「就是說啊,我還記得小時候家裡的電腦只有 1MB 的記憶體,後來聽說那已經是很高級的電腦囉。」音羽附和著說道。

「好吧,我們接下來看看 FloatDouble 這兩個資料型態吧,這兩個資料型態通常稱做浮點數,因為他們可以表達帶有小數點的數字……」

「所以說就是實數囉?」

「和實數有一點不一樣咧,深夏同學,妳輸入 0.6910 - 0.6900 後,看看和妳想的答案一不一樣。」

「好,我試一下……」

scala> 0.6910 - 0.6900
res11: Double = 0.0010000000000000009

深夏揉了揉眼睛,確定自己沒看錯後這才說道:「學長,這答案不對吧,怎麼多了這麼多位數,而且最後還有個 9,不是應該是 0.001 嗎?」

「這就是浮點數和實數不一樣的地方,因為浮點數採用的是一種叫做 SEM 的表示法來用二進位表達出具有小數點的數字,不過很可惜的是這樣的方法不能表達一些像 1/3 這類的數字,所以 FloatDouble 只能表達出一定的近似值而已。」[7]

深夏很認真地想要試著去理解桐仁的話語,不過腦袋卻呈現完全打結的狀況,只隱約記得聽到 SEM 什麼的,但那是啥?

「嗯……這好像有點太複雜了,總之音羽同學,妳只要記得 DoubleFloat 運算出的結果,精確度只會到某個小數位數就好了。」

「好……我知道了。」

浮點數資料型態

資料型態 慣用翻譯 寫法 範圍 佔用記憶體
Float 浮點數 1.5F +-3.4028237*10+38 ~ +-1.30239846*10-45 4 byte
Double 雙精確度浮點數 1.5 +-1.76769313486231570*10+308 ~ 4.94065645841246544*10*10-324 8 byte

「最後這幾個就比較簡單了。」

桐仁將放著資料型態的表格往下捲到最後三列後說道:「接下來是布林值……深夏同學,高中的數學課應該有上過邏輯的東西吧?」

「學長是說若 p 則 q 這種東西嗎?」

「嗯,沒錯。布林值就是用來表示 p 成立與否的,它的資料型態名稱就叫 Boolean,妳輸入 5 > 35 < 3 看看。」

scala> 5 > 3
res12: Boolean = true

scala> 5 < 3
res13: Boolean = false

「所以條件成立的話就是 true,不成立的話就是 false 囉?」

「沒錯,另外 && 代表邏輯裡的『且(AND)』、|| 則代表『或(OR)』這兩個運算,深夏同學,妳可以把所有妳想到的布林值運算列出來嗎?」

聽到桐仁這樣講,深夏開始在 Scala REPL 裡輸入一列一列地輸入:

scala> true && true
res14: Boolean = true

scala> true && false
res15: Boolean = false

scala> true || false
res16: Boolean = true

scala> false || false
res17: Boolean = false

「我記得應該是這三種組合……吧?AND 運算是兩邊都是 true 時結果才會是 true,相較之下 OR 運算只要有一方是 true 結果就會是 true。」

「叮咚叮咚~~小深夏的數學真的不是蓋的耶。」

邏輯運算(布林運算)資料型態

資料型態 慣用翻譯 寫法 範圍 佔用記憶體
Boolean 布林值 true 或 false 只有 true 或 false 兩種 1 bit

「看來深夏同學對布林值運算還滿熟悉的嘛,布林值這邊應該沒有什麼大問題……那我們最後來看字元和字串這兩個資料型態吧。深夏同學,妳再試試看輸入 "Hello World" 看看,記得這次沒外面沒有 println 和括號喲。」

其他資料型態

資料型態 慣用翻譯 寫法 範圍 佔用記憶體
Char 字元 'A' Unicode 字元 2 byte
String 字串 "Hello World"   視字串長度而定
scala> "Hello World"
res18: java.lang.String = Hello World

「學長,這次的資料型態是 java.lang.String,好像和之前的長得不太一樣?」

「嗯,因為它不是數學和邏輯運算的資料型態,所以 Scala 把它完整的名稱寫出來了。話雖如此,我們只要看最後一個 String 就可以了……像 "Hello World" 這種被雙引號括起來的一串東西,就叫字串,資料型態是 String,而字串是由一連串的字元所組成的。」

「字、字元?」

「就是類似字母啊,像 Hello 就是由 H, e, l, l, o 這五個字元所組成的啊,是說這個程式設計的課堂上老師不是有講過嗎。是說小深夏每次上課聽不懂,就開始打瞌睡實在是很不好的習慣咧……」

聽到音羽有點調侃又帶點不滿似的語調在其他人說出自己的密秘,深夏開始覺得一陣熱氣漸漸地從脖子快速地往臉頰上竄去。

「另外像是一個中文字也算是一個字元,在 Scala 裡要表示字元的話是由單引號括起來,妳可以試著輸入 'A''AB' 這兩個指令試試看。」

「好、好的!」聽到桐仁這樣說,深夏趕忙回過神來,在終端機視窗內輸入這兩個指令,但卻似聽到自己的心臟正噗通噗通地跳著。

scala> 'A'
res19: Char = A

scala> 'AB'
<console>:1: error: unclosed character literal
       'AB'
          ^

「學長?」

「嗯,沒錯,因為字元應該只有單一一個字母,所以 'AB' 並不是合法的運算式,所以 Scala 才抱怨說他是 unclosed character literal,如果是改成 "AB" 那就會是字串了……」

「我試試看喲……」還沒等到桐仁說完,深夏就開始在視窗中繼續輸入。

scala> "AB"
res20: java.lang.String = AB

「其實到這邊就差不多了,不過還是再提醒一下好了。」

桐仁將滑鼠移到終端機旁的那份資料型態文件,在最下面新增了一個表格後說道:「字串裡面可以有一些特殊的控制符號,像 \n 代表換新的一行等等……另外因為 "\ 被用做特殊用途,所以需要用其他的方式來表示。」

字串裡的特殊控制符號

控制符號 說明
\ n 換行
\ t TAB 鍵
\ b 倒退鍵
\ " 雙引號
\ \

接著桐仁又在文件上空白的地方打上下面的字樣:

Hello, this        is \a\ "test".
Second line.

「深夏同學,妳試試看能不能用剛剛那張表格上的題示,用一行的字串來表示這兩行。」

「噫?!我想一下喲……」說完之後,深夏便開始嚐試在終端機視窗中輸入自己設想的程式碼。

<console>:1: error: invalid escape character
       "Hello, this\t is \a\ "test".\nSecond line."
                            ^

scala> "Hello, this\t is \\a\\ "test".\nSecond line."
<console>:8: error: value test is not a member of java.lang.String
              "Hello, this\t is \\a\\ "test".\nSecond line."
                                       ^

scala> "Hello, this\t is \\a\\ \"test\".\nSecond line."
res21: java.lang.String =
Hello, this      is \a\ "test".
Second line.

經過了兩次的錯誤之後,深夏終於完成桐仁所說的東西,露出開心的笑容轉頭對桐仁說道:「學長,是這樣沒錯吧?」

「不錯喲,就是這樣。接著試完下面這幾個指令然後觀察一下,今天就差不多囉。」

桐仁一邊說著,一邊指示深夏輸入最後的幾個程式碼。

scala> "Hello"
res22: java.lang.String = Hello

scala> println("Hello")
Hello

scala> "Hello " + "World"
res24: java.lang.String = Hello World

scala> 1 + 2
res25: Int = 3

scala> println(1+2)
3

scala> "Answer of 3*5:" + 3 * 5
res27: java.lang.String = Answer of 3*5:15

scala> println("Answer of 3*5:" + 3 * 5)
Answer of 3*5:15

「如何,有發現什麼規則嗎?」

「呣……加了 println() 會直接把運算式的值印出來,而沒有其他多餘的東西。」

「嗯,那現在知道字串的 + 號是做什麼用的了嗎?」

「看起來是把左右兩邊的東西合成一個字串……?」深夏有點不太確定地說道。

「沒錯,當 + 號的左邊是字串的時候,它會把左邊的字串和右邊的東西接成新的字串。」

桐仁抬頭看了看時間後說道:「快中午了,那今天先到這邊,下次再繼續吧。」

「噫,學長,現在還不到十二點耶。反正小深夏還醒著,那就繼續下去吧!雖然她今天看起來狀況不錯,但一定只是湊巧而已,她可是程式笨蛋耶……不快一點的話一定會來不及的!」

「嗚……」

聽見音羽這麼說,深夏的傳出了無法反駁的嗚咽聲,而桐仁則忍不住伸出手,輕輕地在深夏的頭上拍了幾下後說道:「不會啊,深夏同學好像還滿有天份的啊。再說接下來要講的東西,我也要先準備一下,所以今天還是先到這邊吧。」

「唔……好、好吧……」音羽有點不情願地說道。

註解

[1]使用 Windows 的讀者,請點選『開始』按鈕 -> 所有程式 -> 附屬應用程式 -> 命令提示字元,這時會出現一個黑色的視窗,請在上面輸入 scala 並按下 Enter,即可進入 scala REPL。
[2]Read–Eval–Print Loop,讀取 (Read) 使用者輸入的程式碼,執行程式碼的運算 (Eval) 工作,印出 (Print) 運結果的迴圈 (Loop)。
[3]在 Scala 中,數學上的「乘號(×)」用「*」表示,多數程式語言都是這樣。
[4]同上,數學中的「除號(÷)」用「/」表示,記憶的方法是這個符號的左邊是分子,右邊是分母。
[5]多數電腦用來表達含正負號整數的方式,可以參照維基百科二補數條目
[6]1GB = 1024MB, 1MB = 1024KB, 1KB = 1024 Bytes, 1 Byte = 8 bit
[7]有興趣的讀者請參照此網頁最下方「浮點數表示法」一節,總之請記住 Float 和 Double 運算的結果是不精確的即可。

隨堂練習

請完成下列的表格:

運算式 你覺得他會算出什麼 實際輸入後的結果是什麼
3 + 2 * 10    
(3 + 2) * 10    
10 / 3    
5 / 2.0    
4.331 - 0.3    
"A" + "B"    
"A" + 'B'    
false || true    

附錄、安裝 Scala 開發環境

Windows 的場合

  1. Java Downloads 下載 Java 安裝檔案。

    下載 Java 安裝檔案
  2. 點兩下上一步下載回來的安裝檔案,並點選「安裝」按鈕。

    點選「安裝」按鈕
  3. 喝杯咖啡,等待 Java 安裝完畢。

    等待 Java 安裝完畢 等待 Java 安裝完畢
  4. 按下「關閉」按鈕,結束 Java 安裝。

    按下「關閉」按鈕
  5. 連線到 Scala 下載頁面,下載 Windows 開發環境 (scala-2.9.1-final.zip)。

    連線到 Scala 下載頁面
  6. scala-2.9.1-final.zip 內的資料夾解壓縮至 C:\ 磁碟機。

    解壓縮 解壓縮
  7. 打開「我的電腦」,並進入 C:\scala-2.9.1-final\bin\ 這個資料夾,並將路徑複制下來。

    複制路徑
  8. 在「我的電腦」上面點選滑鼠右鍵,選擇「內容」。

    在「我的電腦」上面點選滑鼠右鍵,選擇「內容」。
  9. 點選上方的「進階」後,再點選下方的「環境變數」。

    點選上方的「進階」後,再點選下方的「環境變數」
  1. 點兩下最上面一列的 PATH 變數。

    點兩下最上面一列的 PATH 變數。
  2. 在跳出來的視窗中的「變數值」欄位,填入 C:\scala-2.9.1-final\bin,並按下「確定」按鈕,直到所有視窗消失。

    「變數值」欄位
  1. 點選「開始」按鈕,點選「所有程式」->「附屬應用程式」->「命令提示字元」

    命令提示字元
  2. 在命令提示字元視窗中輸入 scala 並按下 Enter,若出現歡迎訊息及 scala> 字樣,即為成功安裝。

    成功安裝

Linux 的場合

會使用 Linux 的朋友,相信對於如何使用自家發行版本的套件管理程式應該不陌生吧?大部份發行版本上的 Scala 套件名稱就是 scala,您可以使用您的 Linux 發行版本的套件管理程式安裝。

$ sudo apt-get install scala   # 在 Ubnutu 和 Debain SID 的場合
# emerge scala                 # 在 Gentoo 的場合

或是您可以直接從 Scala 官方網站下載頁面下載最新版本的 Scala 自行安裝,例如要安裝 Scala 2.9.1 的話,可以使用下列方式安裝。

# wget http://www.scala-lang.org/downloads/distrib/files/scala-2.9.1.final.tgz # 下載 Scala
# tar -C /opt/ scala-2.9.1.final.tgz # 解壓縮至 /opt 目錄
# 編輯 ~/.bashrc 加入以下這行

  export PATH=$PATH:/opt/scala-2.9.1.final/bin

# source ~/.bashrc

Mac OS X 的場合

蘋果電腦的使用者,可以直接使用 MacPorts 安裝:

# sudo port install scala

或者也可以參考上述的 Linux 安裝方式安裝。

回響