語言基本要素


加法範例程式

        # 數字版                        # 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
    

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

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