第七章、變數初探

「學長,幫小深夏這個程式笨蛋補習的時間到啦!」

就在桐仁剛按下筆記型電腦上的電源按鈕的那一瞬間,社辦的門隨著極大的碰撞聲和音羽那怎麼看都精力過盛的聲線被撞了開來,而那個明顯地唸錯科系,而且怎麼看都像隻小寵物的深夏,則是一隻手被音羽勞勞地抓著。

「都、都說了我自己會走了啦!而且我也不想被二一啊……」深夏一邊爭扎著一邊嘟囔道。

桐仁或許是已經習慣了這兩人的互動模式,所以只是聳了聳肩,示意她們兩人先準備好座位,畢竟這間社辦實在是雜亂到有點不像話,經常是連坐的地方也沒有。而且很神奇的是之前幫深夏補完習後,桐仁明明就有稍微整理了一下,但今早桐仁進到社辦後,卻發現明明才不到一個星期的時間,但社辦裡又變得幾乎連能夠好好坐下來寫程式的地方都沒有了。

「那咱們就開始吧!」桐仁看音羽和深夏準備得差不多之後,便這樣說道。

「我們今天就從『變數』這個概念開始吧。話說深夏同學的數學成績好像不錯的樣子,那麼聽到『變數』這個詞,深夏同學妳會想到什麼東西呢?」

「呣……就代數運算裡的 x、y、z 那些東西吧?我記得曾經在數學史的書裡面看過,這好像是十六世紀左右的數學家引入的,然後笛卡爾習慣用 x、y、z 來代表未知數,a、b、c 來代表已經知道的數值。」深夏回答道。

「沒錯,不愧是愛看書的文學少女,知道很多東西呢。不過其實在程式語言裡面,『變數』這東西雖然名字一樣,但和數學裡的變數是完全不一樣的,所以等下我們講到變數的時候,妳先不要把他和數學裡的變數連結在一起,不然會覺得莫名其妙,抓不清楚這東西的特性喲!」

「學長,你廢話很多耶,快點開始啦……再不抓緊時間,讓小深夏被當的話,我就……」

「妳很吵耶!這個拿去啦,今天的講議。」

還沒等到音羽講完,桐仁便把拿在手上的一疊 A4 紙捲成筒狀,「啪」地一聲往音羽的頭上敲下去,不過不知道是力道沒有拿捏好所以真的很痛,或是音羽太愛演,只見她整人個雙手抱著頭縮在桌子上,嘴上呢喃地喊著類似「反對暴力」等等的口號,並且像小孩子在鬧脾氣似地把雙腳來回擺晃著。

「深夏同學,別管那邊那個笨蛋了,我們開始吧。」桐仁把筆記型電腦的螢幕轉了個方向,將它推到深夏面前,而深夏則是輕輕地點了點頭。

「如同剛剛說的,今天要講的東西是變數,不過這東西卻又和數學上的變數完全不一樣,所以學習這部份最好的方法,就是先完全不要去管數學上的變數喲!」

「好……好的。」

「那麼你先在 Scala REPL 上輸入 var x: Int = 30 試試看吧。」

深夏在鍵盤上敲下這行指令,接著又再鍵盤上敲下了 30 後按下 enter 鍵,並且轉頭看像桐仁,帶著有些疑惑的眼神問道:「這和上次直接輸入整數後的反應好像喔?」

cala version 2.11.1 (OpenJDK 64-Bit Server VM, Java 1.7.0_55).
Type in expressions to have them evaluated.
Type :help for more information.

scala> var x: Int = 30
x: Int = 30

scala> 30
res0: Int = 30

「不錯嘛!所以我說小深夏很有資質啊,應該只是還沒開竅而已啊。沒錯,這是因為兩個東西要表達的東西差不多,妳還記得 res0: Int 代表什麼嗎?」

「運算的結果是整數型別?」

「沒錯,同樣的,當我們打 var x: Int = 30 的時候,其實是在說『宣告一個變數叫做 x,他的型別是 Int,他的值是 30』這句話,這是這行程式的每個組成和它和剛剛那句話的對應。」桐仁解釋著的同時,也在白板上畫下一個圖表。

var x : Int = 30
宣告變數 變數名稱 型別標註   初始值

「妳覺得如果我們接下來輸入 x + 5 這個運算的話,會出現什麼結果呢?」

「3...35 吧?因為 x 現在是 30,那麼加上 5 之後就會是 35……」深夏一邊說,一邊在鍵盤上敲下這個運算式,看到電腦畫面上吐出的數字後這才鬆了一口氣。

scala> x + 5
res1: Int = 35

「接著試試看輸入 x = 40x * 3 試試看吧!」

scala> x = 40
x: Int = 40

scala> x + 5
res2: Int = 45

「學長,這是什麼意思啊?剛剛 x 明明是 30,現在怎麼可以等於 40 啊?!而且為什麼 x + 5 會是 45 呢?」深夏瞪大著眼睛,滿臉疑惑著的表情。

「你看……我就和你說小深夏是笨蛋了嘛!上學期我教她 C 語言的時,已經跟她說了幾百遍這邊的 = 不是等於,但她還是不懂,結果就被當了啦……」

「妳先安靜吧,這的確是初學者常見的問題。不過其實我覺得最根本的問題是教的人根本沒有幫忙被教的人把已經有的知識融會貫通,換句話說,問題是出在妳身上啦!深夏同學明明就很有潛力的呢。」桐仁忍不住

「是說深夏同學,你還記得我們上次講的 Von Nuemeann 架構和小人電腦的指令集嗎?」

「嗯,記得啊,就是 LOAD / STORE / JUMP 那些嘛,我還有印象喔。」

「很好,那你還記得資料是存在哪,和 LOAD 和 STORE 指令接收的參數又是做什麼用的嗎?」

「我想一下喲……資料是存在記憶體裡,記憶體就像抽屜一樣一格一格的有編號,然後我們可以用 LOAD 和 STORE 指令把 ALU 暫存器上的值放到相對應的記憶體格子裡。」

「記得很清楚耶,就是這樣。不過我們也說過,用像小人電腦的指令集來寫程式太麻煩了,使用起來也沒那麼直覺,像存取記憶體只能用編號就是很麻煩的一件事情呢,所以後來發展出來的高階程式語言,就想到了可以幫記憶體的位置取一個名字,這樣我們就不用去記編號,而可以用有意義的名稱來代表這些格子了。」

「呣……所以說,是像幫忙把把抽替貼上某個標籤,然後請其他人從抽屜拿東西的時候,只要和其他人說『從標識成公文的抽屜裡拿某個東西出來』,而不用說『從上面數來第四個的抽屜』這樣子囉?」深夏閉起了眼睛,似乎是在腦中想像著小人電腦的的工作畫面。

「喔,因為是櫃子的編號,所以其實 x = 30 是把 30 這個數字放到貼上了 x 這個標籤的抽屜裡,然後 x + 5 這句程式碼會在看到 x 的時候其實是把 x 這個抽屜裡的東西拿出來,也就是說是 LOAD x,讓 ALU 上的軌存器是 30,然後再用 ADD 5 這個指令,所以軌存器變成 35。之後 x = 40 實際上是把 40 這個整數放到標籤是 x 的抽屜裡,接下來這行的 x + 5 的 x 會被翻譯成 LOAD x 這樣,變成 ALU 的軌存器上是 40,最後是 ADD 5 的指令,所以現在 ALU 的暫存器上的結果會是 45。咦?!學長,好像很合理耶。」

「一百分的答案喲,那麼妳再想想看,我們一開始輸入的 var x: Int = 30的時候,我們有說到這是在宣告『x 的型別是 Int』,那妳覺得我們為什麼要特別說出 x 的型別是 Int 這件事呢?同樣的,用前幾次我們提到的東西就可以說明了喲!」

「我想想喔……是不是因為 x 只是某個記憶體格子的標籤,但因為電腦裡記憶體存的只有 0101 這種二進位的資料,但我們又要想辦法區分這些存在記憶體裡的二進位資料到底是代表整數或其他種類的資料,所以要告訴電腦標上 x 這個格子裡到底是哪種資料,否則電腦會不知道要把記憶體格子裡面存的一連串 0101 解釋成什麼資料呢?」

「你看,我就說深夏同學很有天份吧。」

「啍,一定是湊巧而已的啦,畢竟小深夏一直以來都是個笨蛋的嘛!」

「就算是湊巧,也總比被當掉二一的好吧?是說對我我也沒差啦……」

「不,如果小深夏被二一的話我會很……不、是超級困擾的!因為我深夏是屬於我的,我才絕對不會允許她從這個校園中消失呢。」

聽到桐仁這樣說,音羽立馬用自己的雙臂緊緊地將深夏給箍住,並且發出依舊聽起來相當危險的宣言。

「嗚哇!好啦好啦,我不會消失的,快放開我啦……」

「好啦,別鬧了。接下來深夏同學你試試看輸入 var y = 30var z = 25.3 吧。」

scala> var y = 30
y: Int = 30

scala> var z = 25.3
z: Double = 25.3

「學長,這次好像沒有剛剛型別標註的部份耶?這是說兩這個記憶體格子放什麼資料都可以嗎?」深夏歪著頭似乎有些疑惑地問道。

「呵呵,妳試試看輸入 y = 3.14 試試看吧?」

scala> y = 3.14
<console>:8: error: type mismatch;
 found   : Double(3.14)
 required: Int
       y = 3.14
           ^

「疑?error……這是錯誤的意思嗎?」

「沒錯,當我們寫 var y = 30 的時候,雖然省略了型別標註的部份,但並不是說 y 這個格子裡什麼型別的資料都可以放喔。這是因為當 Scala 的編譯器看到這樣的程式碼時,因為已經可以知道等號右邊的 30 的型別是 Int,所以自動幫我們補成 var y: Int = 30,同樣的, var z = 25.3 也自動幫我們補成 var z: Dobule = 25.3 。那妳覺得如果輸入 z = y 會發生什麼事呢?」

「因為寫 y 的時候會是 LOAD y,把 y 這個記憶體格子裡的資料放到 ALU 的軌存器上,也就是 30,然後再把 30 放到 z 這個格字……等等,可是 z 放的是 Dobule,所以應該是一樣會發生錯誤吧?」

scala> z = y
z: Double = 30.0

「奇怪?沒有錯誤,而且變成 30.0 了耶?」

「沒錯,這是因為雖然 z 這個變數的記憶體格子裡可以放的資料是 Double,但因為用 Double 可以完整的把 30 這個整數表達出來,所以 Scala 編譯器自動幫我們把 30 轉換成雙精浮點數的 30.0,並且放到 z 這個變數的記憶體格子裡了。但另一方面, y 放的是整數,再怎麼樣也無法正確地表達出 3.14 這個數值,所以自然就吐出錯誤囉。到這邊還跟得上吧?」

「是還可以……不過學長,這些變數到底是做什麼用的呢?」

聽到這個問題,桐仁把筆記型電腦移到自己的身前,然後在鍵盤上敲進了一行程式碼。

scala> 30 * 15
res3: Int = 450

「好問題。深夏同學,如果我寫下這個算式,你覺得我們算出來的 450 是什麼東西,代表什麼意義呢?」

「不一定吧,要看這個算是是用在哪裡。」

「對,當這樣寫我們沒有辦法看出這個 450 到底代表了什麼東西,它就只是單純的乘法運算後的結果。但是如果改成這樣呢……你覺得我們現在計算出的 450 會是什麼?」

scala> var width = 30
width: Int = 30

scala> var height = 15
height: Int = 15

scala> var area = width * height
area: Int = 450

「嗯……是四方型的面積嗎?」

「沒錯,所以說我們可以利用變數的名稱來讓運算式看起來是有意義的,可以知道運算式裡的每一個組成代表了什麼東西,這樣才不會在看程式碼的時候覺得一頭霧水,不知道為什麼要進行這樣的運算。接著,妳再試著輸入 var this = 10 試試看,妳覺得會發生什麼事呢?」

「宣告一個叫做 this 的變數的記憶體格子,然後把整數 30 放到這格變數裡面吧?」

接過桐仁手推過來的筆記型電腦,深夏依言在上面鍵入了桐仁所說的程式碼,但沒想到畫面上出現的卻是錯誤畫面。

scala> var this = 10
<console>:1: error: '.' expected but '=' found.
       var this = 10
                ^

「這是因為就像 var 這個字被用來代表了宣告變數,程式語言裡面總有些特別的字要用來代表一些特殊的意義,這些字叫保留字,是無法拿來當變數的名稱的。喏,這是 Scala 的保留字列表,不過基本上不用特別去記他,反正如果遇到編譯器出現錯誤的時候再來檢查就好了。」桐仁把剛剛敲完音羽的頭後就順手放在桌上的講義翻開到其中的一頁,指著一個表格說道。

abstract case catch class
def do else extends
false final finally for
forSome if implicit import
lazy match new null
object override package private
protected return sealed super
this throw trait try
true type val var
while with yield
: = => <-
<: <% >: #
@      

看見深夏點了點頭表示了解了之後,桐仁接著說道:「那你再試試看輸入 width = 20 然後再輸入 width * height 試試看,看看妳有沒有辦法解釋出為什麼會出現那個答案。」

scala> width = 20
width: Int = 20

scala> width * height
res4: Int = 300

「是因為標示成 width 的這個記憶體格子裡放的數字,已經從 30 變成 20,所以乘上 15 之後會是 300 吧?」

「很好喲,看來妳已經完全理解變數是怎麼一回事了啊。就像妳說的一樣,這也是程式語的變數和數學裡最不一樣的地方,在數學裡面,我們看到 width = 30height = 15 之後,再看到 width * height 這樣的算式的話,不管他出現幾次,運算的結果都會是相同的,也就是 450。但是在程式碼裡面,就算我們看到了 var width = 30var height = 15這兩行,卻不能保證看到 width * height 一定會算出 450,妳覺得是為什麼呢?」

「因為 widthheight 的記憶體格子裡,有可能被放入和原來的 30 還有 15 不同的資料吧?」

「對,但有的人覺得這樣不太合理,我們應該要讓程式和數學的算式一樣,當看到 width * height 的時候就知道她算出的一定是 450,要達成這個目標,妳覺得要怎麼做呢?」

「把 widthheight 鎖起來,讓其他人不能把新的東西放進去?」

「完全正確,所以在宣告變數的時候,我們可以告訴編譯器說這個變數用要上了鎖的玻璃格來裝,原來的東西放進去後,雖然我們還是可以看到裡面的東西是什麼,但沒有人可以放新的東西進去。要達成這樣的效果,我們可以將剛剛用來宣告變數的 var 換成 val,這樣子就會變成格子裡只能放原來的東西,不能放新的東西進去。」

scala> val width = 30
width: Int = 30

scala> val height = 15
height: Int = 15

scala> val area = width * height
area: Int = 450

桐仁在電腦上敲上新的程式碼,然後說道:「妳再試試看輸入一次 width = 20 試試看。」

scala> width = 20
<console>:8: error: reassignment to val
       width = 20
             ^

「呃……reassign.....」

「那是重新指定的意義,在程式設計裡面,把資料放到變數的記憶體格子裡的動作叫做 assignment,而 reassignment 就是指變數的記憶體格子裡已經有資料了,但是我們用新的資料把舊的資料蓋過去。但是我們剛剛說過,用 val 宣告的變數是上鎖的,所以當我們要叫 Scala 把新的資料放進去的時候,它就會抱怨說沒辦法把資料放進新的格子裡。」

「嗯。」深夏點了點頭應道。

「肚子好餓好無聊喔。你們講完了沒啊,走啦走啦,我們先去吃午餐啦!」

伴隨著喀躂喀躂的聲響,從剛剛開始就一直百無聊賴趴在桌子上的音羽,雙腳不同地反覆踏著社辦的地板說道。

「喂……明明是妳叫我幫忙替深夏補習的耶,而且妳跟本沒必要來的嘛,一點用處也沒派上不是嗎?」

「這怎麼可以,我才不會放深夏和你一個人在一起呢,她可是我的東西,我才不會讓你用這種幫忙補習什麼的當做借口,然後偷偷做些什麼把她給搶走的!」

「算了,反正也這主題也告一段落了,那就休息一下吧。」

桐仁已經徹底失去了繼續吐嘈的力氣,只能在心中感嘆著這家伙的頭腦構造還有思考的方式,大概根本就和普通人完全不一樣吧……

回響