流程控制命令


請參考 F-F 乘法表Random Walk 兩個程式. 後者可與 C 版本的 Random Walk 對照. 可以在 tclsh 的命令列下用 "source 檔案名稱" 載入程式執行; 如果你用的是 UNIX 版的 Tcl/Tk, 也可以直接在 shell 的命令列下打入檔案名稱執行. (要先 "chmod u+x 檔案名稱", 並用 "which tclsh" 確定你的 tclsh 安裝的地點與我的相同.)
  1. Tcl parser 並不認識流程控制命令, 照舊把它們當做一般的命令. 就像在 puts {hello, world} 這句話當中, puts 接受一個參數一樣, 在 if {3>2} {puts hello} 這句話當中, if 接受兩個參數. 因此在 Tcl/Tk 程式中, 流程控制命令的左大括弧不可以放在換列之後, 否則會被視為另一個命令的開始.
  2. 常用流程控制命令:
    1. if 判斷條件 執行內容 elseif 判斷條件 執行內容 ... else 執行內容
    2. while 繼續條件 迴圈內容
    3. for 起始設定 繼續條件 遞增動作 迴圈內容
    4. break
      (跳出最內層迴圈)
    5. continue
      (略過最內層迴圈的目前這次, 繼續執行最內層迴圈的下一次)
    6. switch 字串 { 樣版 執行內容 樣版 執行內容 ... }
      (會 C 語言者請注意: 不需要 break)
  3. 以上 if 的 "判斷條件", while 的 "繼續條件", for 的 "繼續條件" 都是由 expr 命令處理的; 而 if 的 "執行內容", while 的 "迴圈內容", for 的 "迴圈內容", switch 的 "執行內容" 都是由 eval 命令處理的.
  4. 使用者自定命令 (即副程式): proc
    1. 語法: proc 新副程式名稱 參數列 執行內容
    2. 副程式內的變數都是局部變數, 連參數也是. 再加上參數是以值傳遞, 所以副程式可以遞迴, 例如:
                      proc fact {n} {
                          if {$n <= 0} {
                              return 1
                          } else {
                              set f [fact [expr $n - 1]]
                              return [expr $n * $f]
                          }
                      #    return [expr $n>0 ? [fact [expr $n - 1]] * $n : 1]
                      }
              
      

      要用全域變數則必須以 global 宣告.
    3. return 可以讓控制流程從副程式當中轉回呼叫程式; 如果沒有 return, 則以副程式中最後一個命令的傳回值作為副程式的傳回值.
    4. 參數的內定值: 用重疊的串列 (nested list) 表示. (講到 list 再詳述)
    5. 可以接受不定個數參數的副程式: 把最後一個參數取名為 args. (講到 list 再詳述)
    6. 如何以名傳遞 (call by name, 有點像是 C++ 當中的 call by reference, 但不完全相同)? 用 upvar. 例如想寫一個副程式 swap 用來交換兩個變數的內容:
                      錯誤示範:
                      proc swap {a b} {
                          set tmp $a
                          set a $b
                          set b $tmp
                          return ""
                      }
                      正確寫法:
                      proc swap {a b} {
                          upvar $a ra $b rb
                          set tmp $ra
                          set ra $rb
                          set rb $tmp
                          return ""
                      }
              
      

      測試: set x 5 ; set y 3 ; swap x y ; puts "$x $y"
      (特別注意何時需要 $ 何時不需要 $)
    7. 陣列無法以值傳遞, 一定要用 upvar 以名傳遞.
                      proc total arr {
                          upvar $arr rarr
                          set ans 0
                          foreach el [array names rarr] {
                              incr ans $rarr($el)
                          }
                          return $ans
                      }
              
      

      測試: set a(1) 3 ; set a(2) 18 ; set a(3) 7 ; total a
      (特別注意何時需要 $ 何時不需要 $)
  5. 參考範例使用到的其他功能
    1. 在 UNIX 的多數 shells (例如 bash 或 tcsh) 底下, 下 chmod u+x ... 命令可以把純文字檔變成可執行的批次檔. 而且檔案裡面也不一定非得是 shell 的命令不可, 只要第一列是: #!.... 就可以改變批次檔裡面所使用的語言.
    2. 在支援 ANSI escape sequence 的視窗/終端機上印 "ESC[..." 這類字串, 可以控制遊標的位置, 改變字元的顏色 ... 等等. 很多 bbs 與 mud 的效果都是用這個作出來的. (help: 幫我找一個 url 吧, 謝謝!)
    3. 在很多程式語言當中都是這樣: 因為檔案輸入輸出都經過 buffer (以便一次大量處理, 較有效率), 如果你希望印出去的 ANSI escape sequence 立即生效, 必須用 flush 強迫系統立即處理.
    4. 檢查系統內定變數 tcl_interactive 可以知道這個程式現在究竟是否正在交談模式下執行.
  6. 作業: 計算 房屋貸款付款表. (or here) 公式: A/P = i * (1+i)^n / ( (1+i)^n - 1 ) 其中
    1. A/P 為每期付款/貸款總額比例, 在範例中取 P=10000 (貸款總額一萬元)
    2. i 為每期利率. 在範例中最左列印出來的是年利率, 但是計算時每月複利一次, 所以如果要算 5.0% 那一列, i 要用 0.05/12
    3. n 為總期數. 如果要算 30 年那一行, n 要用 30*12
    參考答案可以從本頁的 source 當中找到.