Perl中的執行上下文

perl中的上下文

在perl中,不少地方會切換上下文。所謂上下文,它的重點在於同一個表達式出如今不一樣地方,獲得的結果不一樣。換句話說,同一個表達式,它表達的值不是固定的。這就像是同一個單詞,在不一樣語境下的意思不一樣。python

例如,運算操做符決定數值是一個數字仍是一個字符串。shell

2 * 3
2 x 3

2 * 3中的2和3都是數值,由於操做符*是算術運算符,它要求兩邊都是數字。而2 x 3中的2是字符串,3是數字,由於操做符x是這樣要求的。數組

還有,對數組@arr的兩種操做:函數

@arr=qw{perl,python,shell};
print @arr,"\n";    # 返回:perlpythonshell
print @arr."\n";    # 返回:3

使用逗號分隔@arr\n是產生一個列表,這時的@arr會替換爲該數組中的元素值。使用點號鏈接@arr\n,這時點號要求兩邊的都是字符串,數組在這種環境下(標量上下文)返回的是它的元素個數,因此@arr返回一個數值(但實際上是字符串)。操作系統

在perl解析表達式的時候,你要麼但願它返回一個標量,要麼但願它返回一個列表(其實還有不少種上下文,但至今無人知曉有多少種上下文,perl長老團也不知道)。因此perl中常見的兩種上下文是:標量上下文和列表上下文,除此以外還有一個很常見的上下文類型:空上下文(void context)。scala

  1. 標量上下文:表達式在標量上下文中返回一個標量
  2. 列表上下文:期待表達式返回一個列表
  3. 空上下文:不使用表達式的返回結果,即返回結果被丟棄

上下文不只決定了表達式的返回結果類型,還限制了某些環境下只能使用某些上下文。好比,須要傳遞一個列表的時候卻傳遞了一個標量,這可能會報錯誤。code

例如:排序

42 + something;     # 這裏的something必須是標量
sort something;     # 這裏的something必須是列表

數組@arr爲例:內存

@arr=qw(perl shell python);
@sorted=sort @arr;           # 列表上下文:返回(perl python shell)
@num=@arr + 42;              # 標量上下文:返回45

即使是賦值這種操做,都有不一樣的上下文:字符串

@arr1=@arr;      # 列表上下文,賦值@arr給另外一個數組@arr1
$arr1=@arr;      # 標量上下文,賦值@arr的元素個數給變量arr1

比較悲劇的是,沒法總結一個通用的規則來解釋何時用什麼上下文,只能經過一些經驗來感覺它。

列表操做切換到標量上下文

那些操做列表的表達式(如sort)用在標量上下文會如何?沒人知道會如何。不一樣的操做,返回的內容沒有規律可言。

例如,對列表排序的sort操做放在標量上下文只會返回undef,reverse操做放在標量上下文則是返回字符的逆排序(先將全部元素按照字符串格式鏈接起來,再對總體進行反轉)。

@arr=qw(perl python shell);
$sorted=sort @arr;         # 返回undef
$reversed=reverse @arr;    # perlpythonshell-->llehsnohtyplrep
print $sorted,"\n";
print $reversed,"\n";

如下是常見的上下文:

$var = something;             # 標量上下文
@arr = something;             # 列表上下文
($var1,$var2) = something;    # 列表上下文
($var1) = something;          # 列表上下文

如下是常見的標量上下文:

$var = something;
$arr[3] = something;
123 + something;
if (something) {...}
wihle(something) {...}
$var[something] = something;

如下是常見的列表上下文:

@arr = something;
($var1,$var2) = something;
($var1) = something;
push @arr,something;
foreach $var (something){...}
sort something;
reverse something;
print something;

須要注意的幾點,將數組賦值給標量變量,獲得的是數組的長度(元素個數),將列表賦值給標量變量,獲得的是最後一個元素,除了最後一個元素外,其它元素都被丟棄,也就是放進了void context。

@arr = qw(a b c d);
$x = @arr;           # 結果:$x=4

$y = qw(a b c d);    # 結果:$y=d,開啓了warnings的狀況下會警告
($y) = qw(a b c d);  # 結果:$y=d,不會警告
($a,$b,$c,$d) = qw(a b c d)  # 結果:$a=a,$b=b,$c=c,$d=d

標量操做切換到列表上下文

這種狀況很簡單,若是某個操做的返回結果是標量值,但卻在列表上下文中,則直接生成一個包含此返回值的列表。

@arr = 6 * 7;    # 結果:@arr=(42)
@arr = "hello".' '.'world';  # 結果:@arr=("hello world")

但關於undef和空列表有一個陷阱:

@arr1 = undef;
@arr2 = ();

上面的undef是一個標量值,因此賦值後@arr1=(undef),它不會清空數組;而()是空列表,它表示未定義的,因此賦值後@arr2被清空。

強制指定標量上下文

有時候若是想要強制指定標量上下文,可使用僞函數scalar進行強制切換,它會告訴perl這裏要切換到標量上下文。

@arr=(perl python shell);
print "How many subject do you learn?\n";
print "I learn ",@arr," subjects!\n";         # 錯誤,這裏會輸出課程名稱
print "I learn ",scalar @arr," subjects!\n";  # 正確,這裏輸出課程數量

另外一種切換爲標量上下文的方式是使用~~,這是兩個比特位取反操做,由於比特位操做環境是標量上下文,兩次位取反至關於不作任何操做,因此將環境變成了標量上下文。這在寫精簡程序時可能會用上,並且由於~~是符號,能夠和函數之間省略空格分隔符:

my @arr = qw(Shell Perl Python);
print ~~@arr;
print~~@arr;

還可使用下面的技巧從列表上下文切換成標量上下文:

$var = () = expr

Perl中賦值操做老是先評估右邊,因此上面等價於$var = (() = expr)() = expr表示轉換成列表上下文,使得expr以列表上下文的環境工做。最後的賦值操做,因爲左邊是$var,會將列表轉換成標量上下文。

(若是不理解,暫時跳過)這種技巧比較常見的是$num = () = <>,由於<>在不一樣上下文環境下工做的方式是不同的,這個表達式表示以列表上下文環境讀取全部行,而後賦值給標量,因此賦值給標量的是列表的元素個數,也就是文件的行數。

它等價於$num = @tmp = <>。並且徹底可使用scalar()替代scalar(@tmp = <>)

只有強制切換到標量上下文的僞函數scalar,沒有切換到列表上下文的函數。由於根本用不到。

列表上下文中的STDIN

<STDIN>放在列表上下文時,會 一次性 讀取全部輸入(文件/鍵盤等)。一次性意味着大文件須要大量內存,通常400M的文件,perl可能會花上1G內存,由於perl會事先分配好富裕的空間避免過後問題。

例如:

@lines=<STDIN>;
foreach (@lines){
    print $_;
}

在目前來講,這正是咱們所需的方式。能夠讀取每行,並對每行進行操做。但由於是一次性讀取,對於大文件來講,這種方法不可取,應該想其它方法。

<STDIN>放在列表上下文時,它會一行一行讀取,直到讀取到文件結尾(EOF)。可是,若是是讀取鍵盤輸入,如何給出EOF?在Linux中,按下CTRL+D(Windows下是CTRL+Z)便可,默認它會發送EOF給操做系統,操做系統會通知perl到了文件結尾。

由於<STDIN>讀取的每一行中默認就帶有換行符,在列表上下文中,一樣可使用chomp()函數來去除每一行的換行符。

@lines=<STDIN>;
chomp(@lines);

或者採用更簡潔的方式:

chomp(@lines=<STDIN>);
相關文章
相關標籤/搜索