Lua 教學 -- 迴圈
 

for 迴圈

電腦程式最重要的工作之一,就是重覆執行相似的工作。例如,假設我們想要知道 1*2*3*4*... *9*10 是多少(也就是 10 的階乘),當然,可以寫成:

print(1*2*3*4*5*6*7*8*9*10)

不過,當想要計算的數字變得很大的時候,這樣就不太方便了。而且,如果想要寫一個程式,讓使用者輸入一個數字,就可以計算出它的階乘,就不可能這樣寫了。

在程式語言中,「迴圈」(loop)就是用來進行這種重覆的工作。最簡單的迴圈是 for 迴圈(for loop)。在 Lua 中有兩種不同的 for 迴圈,我們先看第一種:

for i = 1,10 do
  print(i)
end

上面的程式,會印出 1 至 10 之間的數字。

for i = 1, 10 的意思是說,建立一個暫時性的變數 i,它的值從 1 開始,一直加 1 直到 10,重覆執行 doend 之間的程式。所以,一開始 i 的值是 1,執行 print(i),接著 i 的值變成 2,再執行 print(i),依此類推直到 i 的值是 10 為止。因此,上面的程式,會印出 1 至 10 之間的數字。

要把上面的程式改成計算階乘,相當容易:

fac = 1
n = 10
for i = 1,n do
  fac = fac * i
end
print(fac)

上面的程式,會執行 fac = fac * 1fac = fac * 2、…、直到 fac = fac * n 為止。因此,fac 變數的值就會是 1*2*3* ... *n 了。

把上面的程式稍微修改一下,使它可以接受使用者的輸入,如下:

io.write("n = ? ")
n = io.read()
fac = 1
for i = 1,n do
  fac = fac * i
end
print(n .. "! = " .. fac)

執行這個程式的結果如下:

n = ? 10
10! = 3628800

有關 for 迴圈,還有幾個要注意的地方。首先,它當然不一定要從 1 開始。例如,也可以寫

for i = 10,20 do
  print(i)
end

它會印出 10 至 20 之間的數字。另外,如果不想要每次是加 1,可以這樣寫:

for i = 10,20,2 do
  print(i)
end

在 10,20 後面的 2 表示每次不是加 1,而是加 2。因此,上面的程式會印出 10、12、14、16、18、20。

同理,如果想要倒過來,可以要求它每次加上 -1。例如:

for i = 20,10,-1 do
  print(i)
end

它會印出 20、19、18、…、11、10。

如果在 for 迴圈中指定的範圍不合理的話,for 迴圈將不會執行。例如:

for i = 10,5 do ... end

不會做任何事,因為 10 比 5 大,所以 10 一直加上 1 並不會變成 5。同理:

for i = 10,20,-1 do ... end

也不會執行,因為 10 一直加上 -1 並不會達到 20。

不過,下面的迴圈是會執行的:

for i = 10, 20, 3 do ... end

此時 i 的值會分別是 10、13、16、19。同樣的:

for i = 20, 10, -3 do ... end

也會執行,i 的值會分別是 20、17、14、11。

 for 迴圈的注意事項

使用 for 迴圈的時候,有幾件事情要特別注意:

首先,for 迴圈所使用的變數(例如前面例子中的 i ) 只是一個暫時的變數。它只在該 for 迴圈中有效,而且在 for 迴圈中不可以修改它的值。例如,下面的程式碼是錯誤的:

for i = 1,10 do
     i = i + 1
end

像這樣的程式碼,Lua 是不會保證其執行結果的。同樣的,下面的程式碼

i = 100
for i = 1,10 do
     print(i)
end
print(i)      -- 會印出 100

由於 for 迴圈中使用的 i 只在 for 迴圈中有效,因此,上面的程式中 print(i) 會印出之前 i 的值,也就是 100。

最後一個要注意的地方是,當 for 迴圈的範圍是變數或運算結果的時候,它只會被計算一次。例如,下面的程式碼:

n = 10
for i = 1,n do
    print(i)
    n = n + 10
end
print(n)      -- 會印出 110

它仍只會印出 1 至 10 的數字,而不會因為 for 迴圈中修改了 n 的值,使迴圈的執行次數改變。

從迴圈中跳出

有時可能會想要中途從迴圈中跳出來。例如,假設我們現在要寫一個程式,可以讓使用者輸出一堆數字,計算出它們的和,和平均值。我們可以這樣寫:

io.write("Input n: ")
n = io.read()
io.write("Enter " .. n .. " numbers:\n")
sum = 0
for i = 1,n do
    a = io.read()
    sum = sum + a
end
io.write("sum = " .. sum .. "\n")
io.write("avg = " .. sum / n .. "\n")

不過,這樣寫有個缺點:使用者要先知道他有幾個數字要輸入。如果數字很多的話,這可能會相當麻煩。因此,我們可以讓使用者在中途輸入 "end",表示他已經把所有的數字都輸入了。因此,我們會在 for 迴圈中,加入一個判斷式:

for i = 1,n do
    a = io.read()
    if a == "end" then break end
    sum = sum + a
end

上面的 if 是一個「判斷式」,表示如果條件成立的話,就執行 then 和 end 之間的指令。在這裡,條件是 a == "end"。Lua 中使用兩個等號 == 表示「相等」。也就是說,這一行指令的意義就是,如果 a 等於 "end" 則執行 break。在迴圈中執行 break,會立刻跳出迴圈的執行。

當然,上面的程式還是不對,因為,如果使用者中途跳出的話,所輸入的數字就不是 n 了。這會使得後面計算平均值的地方錯誤。因此,應該要把 n 設成正確的值:

for i = 1,n do
    a = io.read()
    if a == "end" then
       n = i - 1
       break
    end
    sum = sum + a
end

上面的程式中,如果 a 等於 "end"(即使用者要提前結束輸入數字的動作),那麼就把 n 設為 i - 1,也就是目前已經輸入的數字數目。這樣一來,後面計算平均值的時候,就不會出錯了。

最後,還有一個小問題:如果使用者一開始就輸入 "end",而沒有輸入任何數字的話,n 會是 0(因為 i 一開始是 1,而 n = i - 1 = 0)。這樣一來,在計算平均值的時候,會發生除以 0 的情形。如何修改程式,讓 n 是 0 的時候不計算平均值,就讓讀者自行嘗試吧!

while 迴圈

前面的程式中,使用者一開始還是要輸入一個 n,但是這實在顯得多餘。但是,由於我們不知道使用者會輸入多少數字,所以我們應該使用一個新的迴圈:while 迴圈。我們可以把上面的程式改寫成:

io.write("Enter numbers (enter end to quit):\n")
sum = 0
n = 0
a = io.read()
while a ~= "end" do
    sum = sum + a
    n = n + 1
    a = io.read()
end
io.write("You have entered " .. n .. " numbers.\n")
io.write("sum = " .. sum .. "\n")
io.write("avg = " .. sum / n .. "\n")

在上面的例子中,while a ~= "end" do ... end 表示,如果 a ~= "end" 成立的話(~= 表示「不等於」的意思)就執行重覆 do ... end 之間的程式。所以,一開始先讀取使用者輸入的數字到 a,如果 a 不是 "end",就執行 sum = sum + a 和 n = n + 1 (計算數字的和,和數字的數目),然後,再讀取下一個使用者輸入的數字,直到 a 是 "end" 才跳出。

和 for 迴圈相同的,while 迴圈也可以透過 break 中途跳出。

repeat 迴圈

前面提到的 while 迴圈中,條件是在進入迴圈之前就進行判斷。例如,前面的例子中,如果一開始使用者就輸入 "end",就完全不會進入到迴圈中。 有的時候,我們會希望迴圈至少執行一次以上,這時就會希望條件式是在迴圈的後面進行判斷。例如,假設我們希望前面的程式,改成每次都會顯示輸入的提示字串,以免使用者輸入太多數字後,忘了要輸入什麼字串才能結束程式。如下所示:

sum = 0
n = 0
io.write("Enter number (enter end to quit): ")
a = io.read()
while a ~= "end" do
    sum = sum + a
    n = n + 1
    io.write("Enter number (enter end to quit): ")
    a = io.read()
end

不過,這樣修改有一個缺點,就是印出提示字串的部份,重覆了兩次。這種情形是在寫程式時,會希望能儘量避免的。除了顯得冗長之外,這也可能會造成一些潛在的問題。例如,假設未來要修改程式的提示字串,就有可能只修改了其中一個,而忘了修改另一個。

要避免程式的重覆,就需要使用到 repeat 迴圈。把程式改成如下:

sum = 0
n = -1
a = 0
repeat
    sum = sum + a
    n = n + 1
    io.write("Enter number (enter end to quite): ")
    a = io.read()
until a == "end"

要注意的是,repeat ... until 的條件式,和 while 的條件式剛好相反。while 的條件式是指定迴圈執行的條件(例如上面例子中的 a ~= "end",即表示當 a 不是 "end" 時則繼續執行迴圈),但是 repeat ... until 的條件式則是指令迴圈停止的條件。例如上面的 a == "end" 是指定當 a 是 "end" 時,則結束迴圈。