語言基本要素


加法範例程式

        # 數字版                        # for 迴圈版 (自找麻煩版)
        print "13+5=", 13+5, "\n";      use strict;
                                        my (@x, $sum, $i);
        # 純量變數版                    @x = (13, 5, 1, 7);
        $x = 13;                        $sum = 0;
        $y = 5;                         for ($i=0; $i<=$#x; ++$i) {
        print $x + $y, "\n";                $sum += $x[$i];
                                        }
        # 變數宣告版                    print $sum, "\n";
        use strict;                     
        my ($x, $y);                    # 好用版: 數字從命令列參數讀入
        $x = 13;                        print $ARGV[0] + $ARGV[1], "\n";
        $y = 5;                         
        print $x + $y, "\n";            # 實用版: 讀命令列上所有的參數
                                        use strict;
        # 陣列版                        my ($sum, $t);
        use strict;                     $sum = 0;
        my (@x, $sum, $t);              foreach $t (@ARGV) {
        @x = (13, 5, 1, 7);                 $sum += $t;
        $sum = 0;                       }
        foreach $t (@x) {               print "$sum\n";
            $sum += $t;
        }
        print "$sum\n";
  

簡單的加法程式

剛開始學一種語言, 請自己用手照著打一遍。 (嘿嘿嘿, 上面程式的排版方式, 就是故意不讓讀者輕易剪貼... 除非您會用 vim 的 visual block mode 。) 如果出現錯誤訊息, 不要只是把程式改對就算了, 更一定要注意看 錯誤訊息 說什麼。 這樣以後遇到相同的錯誤訊息, 你才能很快地想起來是不是少打了分號還是變數前面忘記加錢號。

數字版心得:

  1. 從 "#" 開始, 一直到該列結束, 都是註解。
  2. 放在雙引號裡面的是字串, 會原封不動地印出來。
  3. print 後面可以接一串東西, 用逗點分開。 如果你自己沒有在字串頭尾加空格, 印出來的結果就會黏在一起。 print 不會自動幫你加空格。
  4. 每句話都以分號結尾。

純量變數版與變數宣告版心得:

  1. 在 perl 裡面, 變數前面都要加錢號。 這種簡單的變數叫做 純量變數 scalar variable
  2. 原本 perl 允許你直接使用變數, 不必宣告。 不過良藥苦口利於病, 忠言逆耳利於行 -- 與其等到執行的時候才錯得不明不白, 還不如在直譯時就多聽 perl 嘮叨一些, 至少它印的錯誤訊息會告訴你第幾行出錯 ( " ... at 你的程式檔名 line 6")
  3. 加上 use strict; 之後, perl 就會要求使用變數之前必須宣告。
  4. my (...); 宣告變數。

陣列版心得:

  1. 以小老鼠開頭的變數是 陣列變數 array variable。 注意它設定初始值的方法與 print 後面放的東西類似 -- 都是一串東西用逗點分開。 只不過外面必須多一對括弧。 (其實 print 後面那一串東西, 外面多包一對括弧一樣也可以。)
  2. 如果你心裡想的是 "我要把陣列 @x 裡面的每個元素 $e 都拿來..." 那就直接翻譯成 foreach $e (@x) { ... 處理 $e ... } 這樣迴圈每執行一次, @x 裡面的每個元素就會輪流變身成為 $e 讓你在迴圈裡面處理。
  3. $sum += $t 其實就是 $sum = $sum + $t
  4. 其實雙引號字串 "..." 裡面的東西, 並非完全原封不動地印出。 例如 "\n" 會印換列; 而字串裡面如果有純量變數, 則會把變數的值代換進去。 請試試看將 print 後面的字串改為單引號字串 '...' 看會怎麼樣。

自找麻煩版心得: 熟悉 C 的同學可能比較習慣用 for 迴圈。 這個 perl 也有, 而且語法和 C 一模一樣: for (初始設定; 應該繼續; 準備下一步) { ... } 效果相當於:

                初始設定;
                while (應該繼續) {
                    ...
                    準備下一步;
                }
        
  

但如果要這樣寫, 你就必須知道迴圈要做幾次。 陣列 @x 的元素個數是 $#x + 1 也就是說, $#x 是陣列 @x 最後一個元素的註標 (因為 perl 與 c 一樣, 從第 0 個元素開始數起。)

結論是: 可以很自然使用 foreach 的場合, 就不要自找麻煩使用 for。 foreach 比較簡潔, 又更能夠口語化地直接反應出我們心中想的邏輯。 寫程式之前先將你想做的事用中文說出來, 如果你聽到自己喃喃唸著 "把陣列當中的每個元素抓出來...", 那麼就可以直接翻譯成 foreach 語法。

正因為 perl 程式念起來很像英文 (畢竟發明人 Larry Wall 是語言學家嘛), 也就很可以想像為什麼網路上有人 拿 perl 作詩, 甚至舉辦 perl 寫詩比賽

當然, for 迴圈的彈性比 foreach 彈性大, 有些場合就是很難套用 foreach, 例如 f-f 乘法表 就用 for 比較自然。 而 foreach 能做的事, 也都必然可以用 for 做得到: 在簡單的情況下 (也就是說, 嚴格說起來以下並不正確), 如果你只從 @all 讀資料出來, 而不去修改它的內容, 則 foreach $var (@all) { ... } 的效果相當於:

                for ($i=0; $i<=$#all; ++$i) {
                    $var = $all[$i];
                    ...
                }
        
  

好用版與實用版心得: 你自己寫的 perl 程式也可以像系統命令一樣帥, 可以從命令列上讀參數進去, 像這樣: ./sum 5 18 2 7 9 於是 perl 會自動將用你程式的人所敲進去的 命令列參數 command line argument 放入 @ARGV 陣列 (注意大小寫! 在 perl 裡面大小寫有分別!), 你從程式裡面就可以抓出來處理。 這個陣列是內建的, 不必宣告就可以用。

計算 log 的近似值

下面這個程式計算 log 的近似值, 例如 ./log 128 會印 7, 而 ./log 4096 會印 12。 它內定以 2 為底; 不過如果命令列上出現第二個參數, 就改以第二個參數為底, 例如 ./log 4096 8 會印 4, 而 ./log 243 3 會印 5。

        #!/usr/bin/perl -w
        use strict;

        my ($base, $ans) = (2, 0);

        if ($#ARGV > 1 or $#ARGV < 0) {
            die "usage: log number [base]\n";
        } elsif ($#ARGV == 1) {
            $base = $ARGV[1];
        }

        my ($n) = $ARGV[0];

        while ($n > 1) {
            $n /= $base;
            ++$ans;
        }

        print "log($ARGV[0]) base $base is roughly $ans\n";
  

心得:

  1. 變數可以在宣告的同時, 一併設定初始值。 注意等號右邊的語法, 是不是和陣列設定初始值很像?
  2. 邏輯判斷 if-then-else, 在 perl 裡面當然也可以用。 注意 perl 多了一種子句 "elsif" 讓程式看起來比較簡單。 如果是在 c 裡面, 這必須拆成 "else { if ... }" 又多了一層縮排, 看起來比較複雜。
  3. 熟悉 c 的同學請注意: 即使大括弧裡面只有一句話, 大括弧還是一樣不可以省略。 這一點看起來好像輸給 c; 不過 perl 有其他省略的方法。
  4. 如果出現使用者輸入資料錯誤等狀況, 程式做不下去了, 可以用 die 結束程式。 記得印出有用的錯誤訊息。
  5. 隨時隨地都可以宣告變數, 也不限一次。

有關變數

Perl 裡面的變數不分整數/浮點數/字元/字串/..., 一律都叫做 scalar 純量. Perl 是我們肚子裡的蛔蟲, 它會觀察你想對這個純量變數做什麼運算, 根據你呼叫的 function 函數 或你用的 operator 運算子 來決定要把這個純量變數當做數字還是字串. 例: 請在上面範例中的 "純量變數版" 兩個數字外面加上引號, 像這樣: $x = "13"; 看看結果有何不同. 例: 請在 "陣列版" 的最後面加上一句 print length($sum), "\n"; 看看是否會出現錯誤訊息.

[變數按複雜程度分成三類]Perl 裡面的變數, 乃是根據複雜程度 -- 儲存資料的多寡來分類.

  1. 以 $ 開頭的是 scalar variable 純量變數. 一個純量變數可以放一個數字或一個字串.
  2. 以 @ 開頭的是 array 陣列, 一個陣列變數 @x 裡面放著很多個純量, 從第 0 號按順序排到第 $#x 號。 Q: 如何判斷一個陣列變數有沒有元素? 請對 @ARGV 作實驗, 驗證你的假說。 可以這樣子填初始值: @x = ("guava", "pineapple", "banana"); 而用 $x[0] 或 $x[1] 或 ... 或 $x[$#x] 存取 @x 的個別元素:
  3. 以 % 開頭的是 hash (雜湊?) 在其他語言中, 稱為 associated array 關聯陣列. 一個 hash %x 裡面放著很多對純量 (比陣列厲害吧), 沒有任何次序地散成一堆. 每一對純量當中, 一個叫 key, 功用是搜尋資料; 另一個叫 value, 它才是真的要存取的資料本身. 可以這樣子填初始值: %x = ("pig"=>98.3, "cat"=>2.5, "dog"=>5.3"); 而用 $x{"pig"} 或 $x{"cat"} 或 ... 存取 %x 的個別元素: 可以把 hash 想成是一個對照表. 也可以將它想成是具有特異功能的陣列: 一般的陣列用整數作為註標; 而 hash 則可以用任何字串作為註標.

注意:

  1. $speed 是一個純量變數; $speed[3] 也是一個純量變數, 但它和 $speed 無關, 它是 @speed 陣列內的一個元素; $speed{"bike"} 又是另一個和 $speed 無關的純量變數, 它是 %speed 這個 hash 內的一個元素.
  2. 為 array 和 hash 設定初始值時, 等號右邊都是 小括弧! 用小括弧括起來的東西叫做 list, 以後再詳述.

更詳細的討論, 請見 perldata(1) (<== 這個的意思是下 man 1 perldata)

其他常識

  1. 在雙引號內, perl 還是認得純量變數與陣列變數 (但不認得 hash) 會把它的值代換進去. 例如
            $name = "kitty";
            @animals = ("dog", "cat", "sheep", "fish");
            %x = ("pig"=>98.3, "cat"=>2.5, "dog"=>5.3);
            print "$name, @animals, %x\n";
            print @animals, "\n";
          
  2. C 語言當中常用的數學運算子, 在 perl 當中都可以用, 例如 +, -, *, /, %, <<, >>, ++, --, < <=, > >=; ==, !=, ... 注意: 比較數字是否相等, 要用 == 而比較字串是否相等, 要用 eq
  3. 邏輯運算子 and/or/not, 可以用 c 的語法 &&/||/!, 也可以用口語化的寫法。 雖然三個看起來像符號; 三個看起來像名字, 但其實都是 運算子 operator, 唯一的差別是優先順序高低不同。 (前三者低, 後三者高)
  4. 為了方便除錯, 習慣上在呼叫 perl 時加上 -w 參數, 要 perl 多產生一些警告訊息; 同時要記得 use strict 。
  5. 建議使用 Linux 環境的讀者, 學會使用 readlineless, 非常有助於加速操作.

作業

  1. 請將 "純量變數版" 裡面的 ... $x + $y ... 故意改成錯誤的 ... $x + $z ...。 然後不用 -w 執行一次; 再用 -w 執行一次。 程式有沒有執行並印出和? 有沒有印出錯誤訊息?
  2. 同樣故意將 "變數宣告版" 裡面的 $y 改成 $z。 同樣不用 -w -w 執行一次; 再用 -w 執行一次。 這次程式有沒有執行並印出和? 有沒有印出錯誤訊息? 呢? 又請這樣執行 "好用版" : perl -w test.pl 13 看看發生什麼事? 請查字典解釋印出來的錯誤訊息。
  3. 從命令列上輸入 m 與 n 兩個數字, 計算 m 的 n 次方, 像這樣: ./power 6 3 印出 216 而 ./power 2.5 -2 印出 0.16 。 可以假設 n 一定是整數 (但可能是正整數或負整數)。 (暫時不准用 ** 這個運算子)
  4. 把命令列上所有奇數加總; 又把命令列上所有偶數加總; 同時還要數數看命令列上出現幾個 0, 像這樣: ./o_e_sum 5 12 6 0 8 7 1 4 0 印出
            sum of odd numbers: 13
            sum of even numbers: 30
            number of zeros: 2
    
  5. 把命令列上第 0,2,4,6,8, ... 個參數相加; 又把命令列上第 1,3,5,7,9, ... 個參數相加, 像這樣: ./alt_sum 5 12 6 0 8 7 1 4 0 印出
            sum of all numbers at even positions: 20
            sum of all numbers at odd positions: 23
    

提醒: 不習慣寫程式的同學, 千萬不要眼高手低, 貪心想要一口氣完成。 要養成腳踏實地的習慣, 先把題目先一路簡化到到自己可以處理的程度, 再逐次增加功能。 寫一點, 就測試一下, 比較容易除錯, 也比較容易有成就感。

而且天下程式一大抄, 將問題簡化夠了, 就很容易找到類似的範例程式, 從原本就可以動的程式開始改起, 怎麼可能交白卷呢?


Perl 語言

  1. 新手上路
  2. 基本要素
  3. 餵資料
  4. 常用句型
  5. regexp
  6. 詳談變數
  7. 一語中的
  8. 副程式
  9. 模組
  10. 外界對話

附錄

  1. 參考資料
  2. scripting
  3. Windows
  4. 圖形介面
  5. big-5 碼