« 1. 關於Perl | Perl 學習手札目录 | 3. 串列與陣列 »

2. 純量變數(Scalar)

2. 純量變數(Scalar)
在Perl的世界裡,變數其實是以非常簡單的形式存在。至少比起你必須記憶一大堆int,char等等的資料形態算是方便許多了。對於所有只需要儲存單一變數值的資料結構,在Perl裡面都是使用純量數值來進行。所以你在寫Perl的時候不需要去考慮你的某個變數是要儲存數字或字串,大多數的時候你也不需要煩惱著要進行數字與字串間的轉換(註一)。
2.1 關於純量
一般的資料型態中,大多就是數值與字串兩種型態。當然,其中的數值也還可以分成整數型態跟浮點數型態,字串則是由一個或多個字元組合而成。在許多程式語言中,對這方面的定義非常的嚴格,你不但要考慮程式的流程,還必須隨時注意是否該對你的變數進行資料型態的轉換。不過在Perl中,對於所有這一類型的變數一律一視同仁,因此不論你所要儲存的是數值或字串,在Perl中都只需要使用純量變數。
其實就和我們現實生活中相當接近,我們在使用自然語言時,並不會特別去聲明接下來要使用甚麼樣的描述或解釋,而大多數是取決於當時的語境。而Larry Wall既然是一位自然語言學家,顯然對於這一方面特別在行,而這也造就了Perl能夠以非常接近口語的方式表達電腦程式語言的重要原因。
2.1.1 數值
數值對於Perl的意義在於「整數值」跟「浮點數值」,Perl其實對於數字的看待方式是以倍精度的方式去運算,對於目前大多數的系統而言,這樣的精確度顯然可以應付大部份的需求。否則許多券商都使用Perl進行系統開發,甚至太空科學或DNA運算也都大量的使用Perl,難道他們會想拿石頭砸自己的腳?何況,如果需要的話,Perl也可以支援精確度無限的BigNum(「大數」)運算。
你可以很容易的在Perl中使用數值,不論是整數或浮點數。例如下面都是非常典型的數值表達方式:


  1   1.2   1.0   1.2e3   -1   -1.2   -1e3

其中的1.2e3是表示1.2乘以10的三次方,這也許在科學或數學上的使用比較多,不過你多知道一些也是有幫助的。
另外,也許你希望使用類似1,300,000來清楚的顯示一個數值的長度,很可惜,這在Perl中會造成誤解,當然也不會達到你的要求。如果你真的期待以區隔的方式來表現數字的長度,你可以嘗試使用1_300_300的形式。不過我個人並不經常使用,也許我處理數字長度都還不足以使用這樣的表達方式。你可以看看在Perl裡面數值的表達方式:


#!/usr/local/bin/perl $a = 1_300_300; $b = 1.3e6; print "$a\n"; print "$b\n"; 執行結果會像這樣 1300300 1300000

你也許會希望使用非十進位的表示方式,例如十六進位的數字就可以在你撰寫網路相關程式的時候提供相當的幫助。這時候,Perl會有一些特殊的方式來幫助你完成這樣的工作。例如:


#!/usr/local/bin/perl $a = 0266; $b = 0xff; print "$a, $b\n"; printf "%lo, %lx\n", $a, $b; 我們可以看到結果就會像是: 182, 255 266, ff

其中,printf 是以格式化的形式列印,我們稍後會提到。在這裡,我們只要知道這樣的方式可以印出八進位跟十六進位的數字就可以了。

2.1.2 字串
在純量的資料形態中,除了數字,另一個重要的部份則是字串。事實上,Perl的純量值就不外乎這兩種資料型態。在Perl中使用字串也非常的容易,你只需要在所要表達的字串前後加上一對的單引號或雙引號就可以了。


#!/usr/local/bin/perl $a = "perl"; $b = 'perl'; $c = "在\tPerl\t中使用字串非常容易"; $d = '在\tPerl\t中使用字串非常容易'; $e = "23"; $f = ""; print "$a\n"; print "$b\n"; print "$c\n"; print "$d\n"; print "$e\n"; print "$f\n"; 於是可以印出結果: perl perl 在 Perl 中使用字串非常容易 在\tPerl\t中使用字串非常容易 23

在這裡,我們看到一些比較有趣的東西。也是在Perl裡面對字串處理的一些特性:
1. Perl雖然可以使用單引號或雙引號來表示字串,但是兩者的使用卻有些許不同。像我們在$c跟$d兩個變數中所看到的一樣,兩個字串表面上看起來一樣,但是卻因為使用了單引號跟雙引號的差別,使得兩個出來的結果就產生了差異。因為在Perl的單引號中,是無法使用像\n(換行字元),\t(跳格字元)這些特殊字元的,因此在單引號中的這些字元都會被忠實的呈現。就是我們在變數$d的結果所看到的。而這些特殊字元還包括了:


\a:會發出嗶的警鈴聲 \d:代表一個數字的字元 \D:代表一個非數字的字元 \e:跳脫符號 (escape) \f:換頁 \n:換行 \s:一個空白字元 (包括空行,換頁,跳格鍵也都屬於空白字元) \S:非空白字元 \t:跳格字元 (Tab) \w:一個字母,包括了a-z,A-Z,底線跟數字 \W:非字母

除此之外,我們可以在雙引號中內插變數,可是卻無法在單引號中使用這樣的方式。比如在程式中,我們使用了print這個函數,後面接著字串,字串裡面包含了變數名稱跟換行字元。恰巧這兩者都是無法以單引號呈現的,因此我們可以看看下面的程式表現出他們的差異:


#!/usr/local/bin/perl $c = "在Perl中使用字串非常容易"; print '$c\n'; print "$c\n"; 我們會得到這樣的結果 [hcchien@Apple]% perl ch2.pl $c\n在Perl中使用字串非常容易

很明顯的看到第一行的輸出結果就單純的把單引號的內容表現出來,這樣的差異對之後程式的寫作其實有著相當的影響。
2. 利用引號將數字括住,雖然可以清楚的表達你希望將這個數值以字串的方式運算,但是其實這樣的方式有點囉唆,而且Perl的程式設計師並不會希望自己在寫程式時要弄的這麼複雜。因此只要在變數的使用時根據語境的不同而有不同的運算方式。也許可以看看以下的例子:


#!/usr/local/bin/perl $a = "number"; $b = 3674; $c = "4358"; $d = $a.$b; $e = $b+$c; print "$d\n"; print "$e\n"; 可以得到: number3674 8032

所以你可以看到變數$c,我們利用引號把數字括起來,但是當我們把變數$b跟變數$c相加時,Perl就會根據語意,自動把$c轉成數字後再進行加法的運算。也因此,如果你的程式把變數寫成像上例的變數$c一樣,雖然語法上不會有錯誤,不過對於有經驗的Perl程式設計師而言,反而會顯得很不習慣。如果他們看到這樣的程式寫法,也許還會發出會心的一笑。
3. 我們可以在定義變數時給定初值,就像上述的例子所使用的方式。而在Perl中,如果你只是定義了變數,而沒有給定初值,那麼這個變數會被Perl視為undef,也就是「尚未定義」的意思。
2.1.3 數字與字串轉換
毫無疑問,大部份我們在寫Perl的時候是不需要特定對數字與字串進行轉換,因為Perl通常會幫我們處理這一類的事情。例如你對兩個包含數字的純量變數進行加法運算以及連接運算則會產生不同的結果,我們可以作個實驗:


$a = 1357; $b = 2468; print $a+$b,"\n"; print $a.$b,"\n"; 就可以看到印出: 3825 13572468
不過有時候你必須強制要求Perl使用某種資料型態,那麼就可以使用轉換函式,你可以利用int()函數把變數強制使用整數的型態。不過這樣的機會並不多,因此你只需要注意使用這些變數時用的算符,避免讓Perl產生誤會就可以了。

2.2 使用你自己的變數
在一般的情況下,Perl並不需要事先定義變數後才能使用,因此就像我們的範例中所看到的,你可以直接指定一個數值到某個變數名稱,而Perl大多也會欣然接受這樣的方式。可是在大部份的狀況,程式設計師所出現錯誤的機會遠大於Perl出現錯誤的可能。比如你可能會犯下所有程式員都會犯的錯誤:


$foo = 3; $f00 = 6; print $foo;
程式執行之後會得到'3'這樣的結果,這也許是你想要的,也許不是。不過如果你在編譯訊息裡面要求Perl送出編譯訊息給你(註二),你也許會得到這樣的訊息:

Name "main::f00" used only once: possible typo at ch2.pl line 4.

這並不是錯誤,你也許因為打錯字而造成的結果還不足以影響Perl的執行,但是卻會影響你希望產生的結果。不過,使用編譯的警告參數還有其他用法。你可以在你的程式中加上"use warnings",所不同的是,你在程式的一開始就使用"-w"這個參數,是要求Perl對你程式中每一行都進行檢查。可是有時候會因為使用不同的版本,讓原來一切正常的程式在換為其他版本時產生出警告訊息。因此有些時候你可以單單對於某個區塊進行檢查的動作,這樣的強況下,你就可以使用"use warnings"這樣的方式來要求Perl幫忙。相對於此,我們還有其他的方式來跳過某個區塊的警告訊息。就像這樣:


#!/usr/local/bin/perl use warnings; { no warnings; $foo = 3; $f00 = 6; } print $foo;

我們在程式的一開始就使用了"use warnings"這個選項來對我們的程式進行編譯的檢查。不過卻在某個區塊中定義了"no warnings",讓Perl跳過這個區塊進行檢查。這樣一來,我們在執行程式時,Perl就不會再發出上面的那些警告訊息。當然,除非你真的知道自己在做甚麼,否則還是盡量不要省略這樣的檢查才是正途。

另外一個良好的書寫習慣,就是在程式的前面加上use strict的描述,告訴Perl你希望使用比較嚴謹的方式來對程式進行編譯。而一但使用use strict來對你自己的Perl程式進行嚴謹的規則時,你就需要用my這個關鍵字來定義你自己所需要的區域變數。因此假設我們剛剛的程式會寫成這樣:


use strict; my $foo = 3; $f00 = 6; print $foo; 那麼一但我們想要執行這支程式,Perl就會發生錯誤: Global symbol "$f00" requires explicit package name at ch2.pl line 5. Execution of ch2.pl aborted due to compilation errors.

因為我們沒有定義$f00這個變數,Perl不知道你是忘了,或者只是打錯字,這是相當有用的,尤其在你的程式長度已經超過一個螢幕的長度時。因為我們打錯字的機會確實還不少,而且這樣的程式錯誤是非常難以除錯的。因此能夠在程式的一開始就強制使用嚴謹的定義是比較正確的作法。

2.2.1 變數的命名
其實我們已經看了好多例子,裡面都包含了Perl的純量變數,因此相信大家應該都不算陌生了。Perl的變數是以字母,底線,數字為基本元素,你可以用字母或底線作為變數的開始,然後接著其他的字母,底線以及數字。可是在Perl的規定中,是不能以數字開始一個變數名稱的。
而在Perl中,純量變數則是以$符號作為辨識,因此像之前看到的都是屬於純量變數的範圍。
當然,怎麼幫你的變數名稱命名也是必須注意的,因為在Perl中,大小寫的字母是會被視為不同的。因此你如果用了$foo跟$fOO,這在Perl中是屬於兩個不同的變數,只是我們十分不建議這樣的命名方式。否則將來可能維護你的程式碼的人也許會默默的咒罵你,那可別怪我們沒事先警告了。
另外,你應該讓其他看你的程式設計師看到某個變數都大概可以猜出這個變數的作用,你自己想想,如果你看到某支程式裡面的變數名稱是像這樣子:$11,$22,$33,或者像這樣:$100000001,$100000002....。你會不會想要殺了這程式的作者呢?
至於大小寫也是在定義變數時可以運用的另一項特點,例如有人就習慣利用像這樣的方式來定義變數名稱:$ChangeMe。這樣可以避免某些因為連字時造成的混淆,不過一般而言,只要能清楚的表現變數的特性,大小寫與否則視個人的習慣。不過這一切都是為了將來程式維護上的方便,對Perl來說,這些變數的命名對他並沒有特別的意義。不過如果將來維護程式的可能是你自己,還是別找自己的麻煩吧!
2.3 賦值
既然變數就像一個容器,是拿來存放變數,能夠賦予變數他的內容就是非常重要,而且非常基本的一件事了。在程式中,我們經常會對於變數進行賦值運算,否則我們只需要一個定數就好,何必大費周章的使用變數呢?一般來說,要指定一個值給某一個變數的動作並不複雜,而且大概有這兩種主要的方式。
2.3.1 直接設定
這是直接用等號(=)來將某個數值或運算結果指定給變數。例如我們也許會這麼寫:

$foo = 3; $foo = 2 + 1; $bar = "bar"; $foo = $foo + 2; $bar = $bar." or foo"; 就可以看到印出的結果 8 bar or foo

在後面的兩個式子中我們看到左,右兩邊分別出現了兩次相同的變數名稱。這樣的方式是非常常見的,我們在右邊先取變數原來的數值,經過運算之後,把得到的結果指定給左邊的變數。因為Perl的自由度非常高,也許會有人想要把許多賦值的工作一次完成,那麼他可能把程式寫成這樣:


use strict; my $foo = 3; my $bar; $foo = 3 + $bar = 2; print "$foo\n"; 那麼應該會看到這樣的結果: Can't modify addition (+) in scalar assignment at ch2.pl line 6, near "2;" Execution of ch2.pl aborted due to compilation errors.

沒錯,我們雖然說過Perl的語法其實相當自由,但是這樣的寫法確實會讓Perl搞混了。當然,我們可以把程式改寫成:


use strict; my $foo = 3; $foo = 3 + (my $bar = 2); print "$foo\n";
Perl也會輸出結果為5,可是這樣雖然Perl可以清楚的了解你要表達的意思,只怕其他看程式的人還是會一頭霧水,而且這樣並不會讓你的程式執行得更快,當然無法靠這種方式讓你贏得Perl Golf(註三),所以除非你有很好的理由,否則還是少用吧!

2.3.2 還可以這樣
就像我們剛剛看到的,我們可以在賦值前先在等號右邊取出變數的值進行運算,就像這樣:$foo=$foo+3。可是其實某些二元算符可以有更方便的賦值方式,我們可以寫成這樣:


use strict; my $foo = 3; print "$foo\n"; $foo+=3; # 其實就是 $foo = $foo + 3 print "$foo\n"; $foo*=3; # 乘號也可以這樣使用 print "$foo\n"; $foo/=3; # 其實所有的二元運算符都可以這麼用 print "$foo\n"; 可以發現這樣的形式確實非常方便: 3 6 18 6

2.4 運算
其實變數的運算就跟一般的數值是一樣的,我們可以利用大部份我們所熟知的運算符號來對變數進行運算。例如我們當然可以把兩個變數相加,然後賦值給另一個變數,就像這樣:$third=$first+$second。或者更複雜的運算,這從過去我們學到的數學中都可以看到。當然,在Perl裡面也的運算式也符合現實生活中的規則,Perl會先乘號,除號進行運算,然後在把結果作相加或相減(如果你的運算式內有這些算符的話)。所以$foo=3*8+2*4就應該是32,而不是106。
不過Perl裡面並沒有數學中的中括號或大括號,而所有你希望先行計算的部份,都是由小括號將他括起來,例如你可以改寫剛剛的算式成:$foo=3*(8+2)*4,那麼結果顯然就變成120了。而運算符的優先順序正是你需要進行運算時非常最要的部份,雖然你已經知道乘號與除號的優先順序高於加號跟減號。而在這個時候,你還可能需要知道的某些算符的優先順序依照他們的優先性大概有下列幾種:


++, -- ** *,/,%,x +,-,. & && || +=,-=,*=,/=...

當然,Perl的算符並不只有這些,不過我們後面陸續會提到。如果你現在想要知道更多關於Perl算符的說明,可以看一下perldoc perlop這份文件。

2.5 變數的輸出/輸入
當我們寫了一堆程式之後,我們當然希望程式運算的結果可以被看到,否則即使程式運作的結果讓人非常滿意,你也無從得知。當然,換個角度想,如果你的程式錯的一踏糊塗,也不會有人知道。不過如果如此,那何必還花了大量的時間寫這支程式呢?如果你無法從程式得到任何結果。
最簡單的輸出方式,其實我們已經看了很多了,那就是利用print這個Perl的內建函數。而且用法非常直覺,你只需要把你要的結果透過print送出到標準輸出(STDOUT),當然,通常標準輸出指的就是螢幕,除非你自己動了甚麼手腳。我們可以來看看下面的例子會有甚麼結果:


use strict; my $foo = 3; print $foo; print $foo*3; print "列印字串\n"; print $foo, $foo+3, $foo*3; 很簡單的,我們就可以看到: 39列印字串 369

從範例中我們很清楚的就發現,我們可以單純的列印一個變數,一個運算式,一個字串,或者一堆用逗點(,)分隔開來的運算式。因為我們可以在print後面連接一個運算式,所以我們當然也可以寫成這樣:
print "foo = ", $foo;
或者你希望最後的輸出結果還可以換行,那麼你可以這麼寫:
print "foo = ", $foo, "\n";
可是如果你有三個變數,那麼你寫起來也許會像這樣:
print "first = ", $first, "second = ", $second, "third = ", $third, "\n";
好吧,雖然吃力,而且可能容易產生錯誤,不過畢竟你做到了。只是如果現在又多了一倍,那困難度可又增加了不少。

2.5.1 變數內插
我想你大概可以慢慢感受到,以Perl的程式設計師的個性,他們絕對不希望這樣的事情發生,因為這些程式設計師總是不希望自己的時間浪費在打字這件事情上,因此當然要有方法能夠少打一些字,又容易維持程式的正確性。而變數的內插就提供了這樣的福音。
我們之前提過對於字串的表示中,單引號與雙引號之間的差異。其實這兩者之間還有一個重要的差異,就是雙引號中可以進行內插變數,而單引號依然很真實的呈現引號內的字串內容。我們可以看看其中的差異:


my $foo = 3; print "foo = $foo\n"; print 'foo = $foo\n'; 很明顯的,輸出後就有了極大的不同: foo = 3 foo = $foo\n

在雙引號中,不但特殊字元\n會被轉換為換行字元,變數名稱 $foo 也會被取代為變數的值後輸出。反觀利用單引號的時候,不論變數名稱或特殊字元都會被完整而原始的表示。不過如果你希望輸出這樣的字串呢?
print "$ 表示錢字符號\n"
在雙引號中,如果你希望正確的表達某些符號,例如用來提示特殊字元的倒斜線,表示變數的符號時,你必須用以一個倒斜線來讓原來符號的特殊意義消失,看看下面的寫法:
print "\$ 用來提示純量變數, \@ 則是陣列";
那麼你就可以正確的顯示你要的結果。除此之外,我們也許還有一些好玩的技巧,記得Perl的名言嗎?「辦法不只一種」。
讓我們來看下面的程式:


print "\$ 用來提示純量變數, \@ 則是陣列,還有 \"\n"; print qq/\$ 用來提示純量變數, \@ 則是陣列,還有 \"\n/; print qq|\$ 用來提示純量變數, \@ 則是陣列,還有 \"\n|; 結果看來都是一樣的 $ 用來提示純量變數, @ 則是陣列,還有 "

這確實非常神奇,首先我們提示一下,為了避免Perl誤以為你要結束某個字串,因此如果你要印出雙引號時,記得先讓Perl知道,於是就是利用\"的方式來解除雙引號原來的作用。可是一但如此,你的字串內也許會變得難以判讀,尤其常常雙引號又是成雙的出現時。這時候,你可以利用qq來描述字串,而在範例中,我們用了qq//,qq||,其實qq後面可以接任何成對的符號。如果有興趣也可以自己動手試試。
接下來,我們可以來談談怎麼接受使用者的輸入,也就是讓程式可以根據使用者的需求而有不同的反應。
經常被使用的方式應該是程式在進行時,停下來等使用者輸入,當接收到換行字元時,程式就繼續往下執行。這時候我們就是大多就是依賴的方式。很顯然的就是STDOUT的對應,也就是所謂的標準輸入,而我們常用的應該大多就是鍵盤。因此,Perl在遇到時便會等待輸入,我們可以用這個簡單的例子來試試:


print "please enter your name:"; my $name = ; print "\n"; print "hello, $name\n"; 當我們執行時,就會有這樣的結果: please enter your name:hcchien hello, hcchien

看起來,王子跟公主似乎過著幸福,快樂的日子。可是唯一小小的缺憾,卻是Perl連我們在結束字串輸入的換行字元也一併當成字串的一部份了。這樣的動作有時候會有很大的影響,因此我們也許要考慮把這樣的錯誤彌補過來。這時候,chomp函數就派上用場了。這個函數生下來似乎就只為了進行這項工作,至少我們再也不用擔心使用者的換行字元該怎麼辦。他的用法顯然也不特別困難,只要把你需要修正的字串當成傳入值就可以了。
chomp($name);
所以我們只要把這一行加到剛剛的程式裡面,我想你應該就會發現一些變化。沒錯,原來我們的輸出最後還多了一行空行,那是因為我們輸入時打了最後一個換行字元,不過經過chomp的修正,那個字元果然就沒有了。那麼chomp有沒有甚麼資訊可以讓我們參考呢?既然他是一個函數,他就會有一個回傳值,而chomp的回傳值就是被移除的換行字元個數。例如我們如果有一個字串:
my $name = "hcchien\n";
那麼一但我執行了
chomp($name);
理論上就會傳回1的值。實際上也確實如此,那麼我們可以再來試試,如果變數$name的值變成
$name = "hcchien\n\n";
然後我們發現回傳值還是1,也就是說,chomp只對字串結尾的那個換行字元有效。因此如果我們只執行了一次chomp,並不是把字串後面的換行字元全部取消,而只是移除了一個。

2.6 Perl預設變數
很多時候,剛學Perl的程式設計師似乎常常會遇到一些問題,也就是不容易看懂其他的Perl程式。這其中的原因當然很多,例如Perl的寫作形式非常自由,同樣的需求可以利用各種方式達成,有些程式設計師常常會用非常簡略的語法而讓易讀性降低。另外,許多Perl的預設變數對於初學者也是一個問題。你常常會看到一堆符號在程式裡飛來飛去,卻完全不知道他們在說甚麼,你當然可以利用Perl的線上文件perlvar去找到你要的答案,不過我們會適時的在不同的章節提到一些Perl常用的預設變數。

2.7 defined 與 undef
你也許有過經驗,當你在寫一支程式的時候,你定義了一個變數,就像我們平常作的:
my $foo;
或者你可能寫成這樣:
my $foo = "";
於是在你的程式過程中,這樣兩種方式所定義出來的變數在你的程式並沒有產生不同。於是程式平靜的結束,你開始想像你多打了好幾個字,只為了告訴Perl $foo這個變數是個空字串。可是所有的事情一如你所預測的一般,你還是沒想想透,到底宣告變數是空字串到底有沒有意義呢?其實大部份的時候,你是不需要在定義變數時宣告為空字串,因為當這個變數被定義時,Perl會指定他為undef。而當這個變數被作為數值時,他瞬間就被當成零,同樣的,在被當成字串運算時,他則會被作為空字串。
不過,如果你的程式開啟了warnings參數,而打算列印一個undef的變數,可是會遭到警告的,因為Perl顯然很難理解你為甚麼需要列印一個沒有被定義的變數。而這很可能是你的程式有某部份發生了問題,所以千萬別隨便忽略Perl的警告,再仔細檢查你的程式吧!
所以你也許要確認你的程式在某些敘述是否正如你所期待的正確的進行了某些運算,這時候有一個函數就可以派上用場了,那就是defined()。你可以用defined來確定某個變數是否是經過定義,很簡單的就像這樣:
defined($name);
而許多程式的寫作中,也經常使用這個函數來進行判斷。例如你可以這麼寫:


my $name; if (defined($name)) { print $name; } else { print "it's undefined"; } 我們可以很清楚的看到這樣的宣告一個變數會被設為undef。

而且,undef在perl中也是個關鍵字,你可以直接指定某個變數是undef,就像你在賦值給任何變數一樣。所以你可以很簡單的寫成:
$name=undef;

習題:
1. 使用換行字元,將你的名字以每個字一行的方式印出。
2. 印出'\n', \t'字串。
3. 讓使用者輸入姓名,然後印出包含使用者姓名的招呼語(例如:hello xxx)。

註一:有些時候是必須強制進行轉換,Perl才知道你真正需要的是甚麼。
註二:你可以在程式的最前面寫成這樣「#!/usr/bin/perl -w」,告訴Perl你希望啟動編譯警告。
註三:一種長期並不定時舉行的Perl程式設計遊戲,以程式碼最短者獲勝,就像高爾夫球,最少稈者獲勝,詳細可以參考http://perlgolf.sourceforge.net/。