Effective Perl-chapter2

由于perl語言的設計者是一位語言學家,所以和任何一種人類語言相同,perl也有很多習語。

1.為優(yōu)雅、簡潔而使用 '美元下劃線'

$_是許多操作符的默認參數(shù),有時也是一些控制語句的默認參數(shù)
# $_ 作為默認參數(shù)
print $_;
print; # the same thing

print "found it" if $_ =~ /Rosebud/;
print "found it" if /Rsosebud/;  # same thing

$mod_time = -M $_; # most filehandle tests
$mod_time = -M; #same thing

foreach $_ (@list) {do_something($_)};
foreach (@list) {do_something($_)}; # same thing

while (defined($_ = <STDIN>)) {
    print $_;
}
while (<STDIN>) {print} # same thing

2. 了解其它的默認參數(shù)

@_ 作為默認參數(shù)

sub foo {
  my $x = shift;
}
在子程序中會默認使用@_作為參數(shù)
bar (\@bletch);

sub bar {
  my  @a = @{shift};  # wrong
}
如果你使用use strict;將會報錯,因為perl將{}內(nèi)的shift 理解成一個變量,而不是一個函數(shù)
Error : Global symbol @shift requires explicit package name
因此,你需要在括號內(nèi)加點什么,讓perl知道shift不是一個變量名,而是一個函數(shù)

sub bar {
  my @a = @{shift()}  # true (最常見的做法)
}

sub bar {
  my @a = @{+shift}  # '+' 相當于占位符
}

@ARGV作為默認參數(shù)
在子程序之外,shift 會把@ARGV作為默認參數(shù),知道了這一點我們可以對命令行作自定義處理

比如把所有以連字符開頭的當作開關選項,其他參數(shù)當作文件名

foreach (shfit) {
  if (/^-(.*)/) {
    process_option($1);
  }else {
    process_file($_);
  }
}
當然你不需要自己處理命令行參數(shù),perl中有很多現(xiàn)成的模塊處理命令行參數(shù),如Getopt::Long

其他使用'美元下劃線'的函數(shù)

- X filetests (expect for -t)
abs
chomp
chop
defined
glob
lc
print
reverse (in scalar context only)
rmdir
split
unlink
等等一些其他的內(nèi)建函數(shù)

默認使用STDIN
多數(shù)文件測試符都以$_作為默認參數(shù),但-t卻以STDIN文件句柄作為默認參數(shù)

3.常用簡寫和雙關語

使用列表賦值進行變量對調(diào)
perl并沒有特殊的操作符用于兩個變量值的互換,perl 會先計算等號右邊的表達式,然后按對應位置賦值

($a,$b) = ($b,$a);  #swap $a and $b
($c,$a,$b) = ($a,$b,$c);
# 切片讓你隨意交換數(shù)組內(nèi)容
@a[1,2,3] = @a[3,2,1];

@a[map {$_*2 + 1,$_*2} 0 .. ($#a / 2)] = @a; # 數(shù)組奇偶元素對調(diào)

用[] or ()[] 強制列表上下文
有些時候你需要強制perl在列表上下文計算某個表達式。比如用正則表達式切分字符串

# 按+號切分$_z中:之前的部分
my ($str) = /([^:]*)/; #這里是列表上下文,標量上下文不會返回匹配到的子串
my @words = split /\+/, $str

對于上面兩行代碼,可以進行整形為一行代碼并省去中間變量$str
my @words = split /\+/, (/([^:]*)/)[0];  # 利用切片轉為列表上下文

使用=> 構造鍵值對
大箭頭操作符其實和逗號操作符差不多,唯一的區(qū)別是:如果=>左邊標識符能識別為一個單詞,那么perl 就會自動把它作為字符串,而不是函數(shù)調(diào)用

my %elements = (
  'ag' => 47,
  'au' => 79
)
在表示哈希鍵的地方可以省略引號
my %elements = (
  ag => 47,
  au => 79,
)
有時=>也可以省略,如果鍵和值都是沒有空格的子串,可以用qw操作符
my %elements = qw(
  ag  47
  au  79
)

使用=>表示方向
另一個關于=>操作符的用法,指明操作方向,比如在rename函數(shù)中,用它表示從舊文件名改為新文件名

rename "$file.c" => "$file.c.old";

小心使用{}
使用{}時請?zhí)貏e小心,它大概是perl里面功能最多的標點符號了,{}可以圈定程序塊,用作變量名分隔符,創(chuàng)建匿名哈?;蛘哌M行哈希元素的存取、解引用,等等。
如果看到{}內(nèi)有個孤零零的+號,貌似毫無必要,那它可能是有意放到那里的,可借此對語法進行修正

比如對某個返回數(shù)組引用的函數(shù)進行解引用操作時,將函數(shù)名放在{},會被當成軟引用
my @a = @{func_returning_aryref}; # wrong
下面三種方式可以給perl提示,{}內(nèi)其實是一個函數(shù)
my @a = @{func_returning_aryref()}; # ok -- 有括號
my @a = @{&func_returning_aryref}; #ok -- 有'&'
my @a = @{+func_returning_aryref}; #ok -- 有'+'
# 避免軟引用
$not_array_ref = 'buster';
@{$not_array_ref} = qw (1 2 3);   # really @buster

用@{[ ]}或 eval {} 的形式來復制列表
有時候我們希望操作的是某個列表的副本,以免原始數(shù)據(jù)遭到破壞

比如查找缺失.h的頭文件,可以先讀取所有.c文件,然后將文件名換為.h結尾,再按此列表核對哪些文件不存在
my @cfiles_copy = @cfiles;
my @missing_h = grep {s/\.c$/\.h/ and not -e } @cfiles_copy;

my @missing_h = grep {s/\.c$/\.h/ and not -e} @{ [@cfiles] };
另一種高效的方法
my @missing_h = grep {s/\.c$/\.h/ and not -e}  eval {@cfiles};

4. 避免過多的標點

無括號方式調(diào)用子程序
調(diào)用子程序時,前面的&和后面的括號都是可以省略的

&myfunc (1,2,3);
myfunc (1,2,3);
myfunc 1,2,3;  #之前需要有函數(shù)定義或聲明  

myfunc 1,2,3;  #將會出錯
sub myfunc {} 

因此可以提前聲明主程序
use subs qw(myfunc);
myfunc 1,2,3;
sub myfunc {}

用and 和or 代替&&和| |
and 和or 在操作符結合優(yōu)先級中位于最低一級,使用這兩可以免去對表達式的分組,有效減少圓括號的使用

print ("hello") && print "goodbye";
print "hello" and print "goodbye";

(my $size = -s $file) | | die "$file has zero size\n";
my $size = -s $file or die "$file has zero size\n";

此外,花括號中的最后一個分號可以省略
my @caps = map {uc $_;} @words;
my @caps = map {uc $_} @words;

還有一種是使用表達式修飾可以省略圓括號和花括號,"后向條件式用法"
if (/^_END_$/) {last} 
last if /^_END_$/;  # 非常簡便

5. 調(diào)整列表格式便于維護

雖然前面說到要避免標點符號的無謂使用,但凡事無絕對,有些情況添加標點符號很有用處

perl 允許你在列表末尾加一逗號
my @cats = ('a','b','c',); 這樣添加新元素時就不會擔心忘記添加逗號而影響數(shù)據(jù)結構
push @cats ,'d';

6. 合理使用foreach , map , grep

Perl 提供了好幾種遍歷列表元素的方法
在perl中避免使用for循環(huán)和按下標遍歷列表的方式,相比其他循環(huán)方式慢

for (my $i = 0 ; $i <= @array ; $i++) {
    print "I saw $array[$i]\n";
}

大多數(shù)喜歡使用foreach ,map和grep,這三種方法有相似之處,不過各自應用的場合不同,雖然這三種方式可以相互轉換。

通過foreach來進行列表元素的只讀遍歷
如果是僅僅遍歷列表中的所有元素,那么foreach 就可以

foreach my $cost (@cost) {
    $total += $cost;
}

foreach my $file (glob '*') {
    print "$file\n" if -T $file;
}

使用foreach的地方都可以使用for來代替
for (@lines) {
    print ,last if /^From:/;
}

用map函數(shù)從現(xiàn)有列表延展出新列表
如果從現(xiàn)有列表推導出新列表,請使用map(列表上下文)

my @sizes = map {-s } @files;
my @sizes = map -s,@files;

我們一般會直接對默認的$_變量操作,它其實相當于當前列表元素的別名
所以一旦在map表達式中修改了$_的內(nèi)容的話,原始數(shù)據(jù)也會隨之改變
請記住一個原則確實要修改原始列表內(nèi)容的話,請使用foreach

my @digitless = map {tr/0-9//d; $_} @elems;

實際上在修改原始列表,為了避免修改原始變量,可以復制$_到詞域變量再做改動處理
my @digitless = map {
    (my $x = $_) =~ tr/0-9//d;
    $x
} @elems;

用foreach修改元素內(nèi)容
如果目的是修改元素的內(nèi)容,請使用foreach循環(huán)。和map、grep一樣,循環(huán)體中的控制變量其實是列表當前元素的別名,所以修改控制變量,實際上修改原始數(shù)據(jù)

foreach my $num (@number) {
    $num *= 2;
}

grep篩選列表元素
grep一般用于篩選列表元素或對元素計數(shù)

print grep /abc/i,@lines;
在標量上下文返回符合條件的元素個數(shù)

my $selnum = grep /*.txt/, @files;

7. 掌握多種排序方式

在perl最基本的排序操作中,所用的代碼相當簡潔,僅僅使用一個sort操作符就可以對列表進行排序,它會返回排序后的新列表。

my @sortnum = sort  qw(hell liu chen)  # 默認按照UTF-8排序規(guī)則進行排序
print join ' ' ,sort   1 .. 10;
    如果不想用默認的UTF-8排序,那你就需要自己編寫用于比較的子程序

Sort子程序
所謂perl排序子程序,實際上并非完整的排序算法,可以說是比較子程序,和一般的子程序不同,排序子程序得到的參數(shù)是經(jīng)過硬編碼的兩個特殊包變量a和b,而非@_。 a 和b在排序子程序內(nèi)部是本地化的,好比一個隱形的local(a,b)語句一樣 (但不需要我們自己寫)

perl內(nèi)建的排序方式相當于用cmp操作符比較
print sort 1 .. 10;

sub utf8ly {$a cmp $b}
print sort utf8ly 1 .. 10;   #等價于內(nèi)置sort函數(shù)

print sort {$a cmp $b} 1 .. 10   # sort后面直接使用代碼塊,簡便

# $a 和 $b  的排序方法決定了最終得到的排序順序
print sort {$a <=> $b} 1 .. 10  #按數(shù)字大小進行排序

print sort {lc($a) cmp lc($b)} qw(this is a test)  # 進行大小寫無關的排序

print sort {$b <=> $a} 1 .. 10   # 調(diào)換$a和$b的位置實現(xiàn)倒序排序

print sort {-M $a <=> -M $b} @files;  #可以根據(jù)$a和$b的值計算后再作比較

my %hash = (b => 1,c => 2,d =>5);
print sort {$hash{$a} <=> $hash{$b}} keys %hash;  #根據(jù)哈希值的大小對哈希鍵進行排序 

高級排序:一般做法

有時在比較兩個值的時候需要進行大量計算,比如按照密碼文件的第三個字段進行排序
open my ($passwd),'/etc/passwd' or die "$!";
my @byuid = sort {(split /:/,$a)[2] <=> (split /:/,$b)[2] } <$passwd>
上面雖然可以實現(xiàn)目標,但是邊排序邊計算很慢,因此在比較前先計算好要比較的數(shù)據(jù),提高排序速度

# 以原始數(shù)據(jù)行為key,uid為key,然后對哈希進行排序即可
open my ($passwd),'/etc/passwd' or die "$!";
my @lines = <$passwd>;
my %hash = map {$_,(split /:/)[2]} @passwd;
my @sortlines = sort {$hash{$a} <=> $hash{$b}} keys %hash;

高級排序:更酷的做法
上面方法的缺點在于需要額外準備一條語句,建立輔助排序的數(shù)組和哈希,我們可以使用 || = 操作符來簡化

{
  my %m;
  my @files = glob '*';
  my @sorted = sort {($m{$a} ||= -M $a) <=> ($m{$b} ||= -M $b)} @files; 
}
"$m{$a} ||= -M $a"實現(xiàn)方式: 第一次遇到某個文件$a時,$m{$a}的值是undef,于是||=后的表達式-M $a求值后存入$m{$a} 

施瓦茨變換
目前為止最精簡的全能排序技術還得算是施瓦茨變換,即若干map實現(xiàn)的sort排序

my @name = glob '*';
my @sortnames = map { $_ -> [0] }  #4.提取原始文件名
                                       sort { $a -> [1] 
                                                 <=>
                                                 $b -> [1] }    #3.對[name,key]數(shù)組排序 
                                       map { [$_,-M] }   #2.創(chuàng)建[name,key]數(shù)組
                                       @name;                #1.原始數(shù)據(jù)
從下往上閱讀,你會發(fā)現(xiàn)上面的代碼巧妙,省略了不需要的中間變量

單個元素的排序也可以借鑒這個思路,只要在匿名數(shù)組中修改右邊的元素值
open IN,"/etc/passwd" or die "$!";
my @sort_uid =map { $_ -> [0] }
                                 sort {$a -> [1] <=> $b -> [1] } 
                                 map { [$_,(split /:/)[2] ] } 
                                 <IN>; 

8.通過智能匹配簡化工作

perl 5.10 引入了智能匹配操作符~~,該操作符能用最少的指令寫出功能強大的條件判斷式 ,通常和given-when一起聯(lián)合使用。注意確保perl版本在5.10.1以上

use 5.010001;

檢查哈希鍵或數(shù)組元素是否存在
if (exists $hash{$key}) { ... }
if (grep {$_ eq $name} @cats) { ... }

#智能匹配操作符
if ($key ~~ $hash) { ... }
if ($name ~~ @cats) { ... }

通過正則表達式檢查元素是否存在
my $matched = 0;
foreach my $key (keys %hash) {
  do {$matched = 1; last } if $key =~ /$regex/;
}
if ($matched) {
    print "one of the keys matched\n";
}
#智能匹配操作符
if (%hash ~~ /$regex/) {
    say "one of the keys matched";
}

if (@array ~~ /$regex/) {
    say "one of the elements matched";
}

其他通過智能匹配可以大大簡化代碼的操作有:
%hash1 ~~ %hash2   #兩個哈希是否有相同的鍵值
@array1 ~~ @array2 #數(shù)組是否相同
%hash ~~ @keys        #數(shù)組中是否存在某個鍵

9.用given-when 構造switch語句

swith語句源于c語言中,在perl 5.10 得以實現(xiàn)
更少的輸入
perl給switch起了一個新的名字,叫做given-when。

你也許已經(jīng)會用if-elsif-else語句表達
my $dog = 'spot';
if       ($dog eq 'fido')      { ... }
elsif ($dog eq 'Rover')  { ... }
elsif ($dor eq 'spot')      { ... }
else                                       { ... }
根據(jù)$dog 種類需要運行不同的代碼,這種老式寫法要求每個條件分支都重復一遍相近的判斷語句,太過繁瑣

given ($dog) {
    when {'fido'}     { ... }
    when {'Rover'} { ... }
    when {'spot'}    { ... }
    default                { ... };
};
實際上given-when工作方式是先將$dog賦值給$_,然后perl自動完成$_和給定數(shù)據(jù)間的比較

智能匹配
在上例中,perl是如何知道該用字符比較操作符?事實上,除非你明確指定比較操作符,默認情況下,when語句總是自動使用智能匹配操作符~~

when ('fido') { ... }
when ($_ ~~ 'fido') { ... }  #與上式等價

多分支處理
默認情況下,只要某個when塊得到匹配,這段程序的代碼就算運行結束了,perl不再會計算其他when塊,這點和if-elsif-else類似,好比每個when塊末尾都有一個隱形的break

given ($dog) {
    when {'fido'}     { ... ; break}
    when {'Rover'} { ... ; break}
    when {'spot'}    { ... ; break}
    default                { ... };
};

利用continue可以使程序在當前when塊運行結束后進行下一個when繼續(xù)比較
given ($dog) {
    when {/o/}     { ...; continue }
    when {/t/}     { ...; continue }
    when {/d/}    { ...; continue }
};
上述會進行所有when測試

代碼組合
if-elsif-else結構還有一個缺陷是,它不能在兩個條件中組合其他代碼。任何代碼都必須找到匹配條件后才能執(zhí)行。而在given-when結構中,你可以在when塊之間自由插入任意代碼,哪怕是中途修改主題變量也沒問題。

use 5.010;
given ($dog) {
    say "I'm working with [$_]";
    when {/o/}     { ...; continue }
    say "continuing to look for a t";
    when {/t/}     { ...; continue }
    $_ =~ tr/p/d/;
    when {/d/}    { ...; continue }
};

對列表進行分支判斷
在foreach循環(huán)中我們也能用when,這和在given中相似,只不過它是依次從列表取測試目標

my $count = 0;
foreach (@array) {
    when (/[aeiou]$/) {$vowels_count++}
    when (/^[aeiou]$/) {$count++}
}
say "\@array contains $count words ending in consonants and $vowel_count words ending in vowels";

10.用do{}創(chuàng)建內(nèi)聯(lián)子程序

do {} 這種語法能把幾條語句組合成單條表達式,這有點類似與內(nèi)聯(lián)子程序

my $file = do {
    local $/;
    open IN, "$filename" or die "$!";
    <IN>;  #最后一條表達式 會作為代碼塊的返回值返回  
};

若是不用do寫
my $file; 
{
    local $/;
    open IN, "$filename" or die "$!";
    $file = join ' ' ,<IN>;   #代碼塊中的數(shù)據(jù)周期有限,因此需要賦值才能將數(shù)據(jù)傳遞到外部
};

借助if-elsif-else結構返回不同數(shù)據(jù)的寫法,也可以歸納為do {}的形式,能省略大量代碼,且語義更加清晰
my ($thousands_sep,$decimal_sep);
if ($locale eq 'European') {
    ($thousands_sep,$decimal_sep) = qw (. ,);
}elsif ($locale eq 'English') {
    ($thousands_sep,$decimal_sep) = qw (, .);
}

my ($thousands_sep,$decimal_sep) = do {
    if ($locale eq 'European') {qw (. ,)}
    elsif ($locale eq 'English') {qw (, .)}
};

11.用List::Util和List::MoreUtils簡化列表處理

掌握列表的各項操作,是深入理解perl的必經(jīng)之路,perl內(nèi)置的map、foreach、grep語句,組合起來能完成許多復雜的列表處理。而有些列表操作極其常見,與其發(fā)明輪子,還不如直接用List::Util和List::MoreUtils這兩個c語言實現(xiàn)模塊提供的函數(shù)和來得更快
快速查找最大最
查找列表中的最大值,你自己寫的話也不算太麻煩

my @array;
my $max = $array[0];
foreach (@array) {
    $max = $_ if $_ >$max;
} 
# 不過純粹用perl實現(xiàn),相對來說性能要差些
# List::Util模塊中提供了c語言實現(xiàn)的max程序,可以直接返回列表中的最大值
use List::Util qw(max);
my $max = max(@array);  # 類似還有min函數(shù)

use List::Util qw(maxstr);  # 類似還有minstr函數(shù)
my $max_string = maxstr(@array)  #返回列表中最大的字符串

# 求和
my $sum = 0;
foreach (@array) {
    $sum += $_;
}
# 而List::Util提供的sum程序則更加方便
use List::Util qw(sum);
my $sum = sum(@aarray);

列表合并
對一系列數(shù)字求和的方法還有一個,就是利用List::Util模塊提供的reduce函數(shù)逐項迭代,執(zhí)行速度很快

use List::Util qw(reduce);
my $sum = reduce {$a + $b} @array;

#與sort類似,reduce也以代碼塊作為參數(shù),不過運行機制稍有不同,每次迭代,會從列表中抽取前兩個元素,分別設置別名$a和$b,這樣參數(shù)列表的長度就會縮短兩個元素,然后reduce把代碼塊計算的結果再壓回參數(shù)列表的頭部,如此往復,直到最后列表里只剩一個元素

my $produce = reduce {$a * $b} @array;  #累乘

# 為了方便該模塊創(chuàng)建了累乘的函數(shù)product
use List::Util qw(product);
my $produce = product @array;

提取列表頭尾元素

use List::Util qw(head);
Usage: head $size ,@list;
my @results = head 2, qw(foo bar baz);  #返回 foo bar
my @results  = head -1 qw(foo bar baz);  # foo bar 負數(shù)表示返回所有除了最后的$size個元素

use List::Util qw(tail);
Usage: tail $size, @list;
my @results = tail 2, qw(foo bar baz);  #返回 bar baz
my @results  = tail -1 qw(foo bar baz);  # baz 負數(shù)表示返回所有除了開始的$size個元素

判斷是否有元素匹配
純粹用perl實現(xiàn)的話,找出列表中第一個符合某項條件的元素,比找出所有符合條件的要麻煩一些

my $found = grep {$_ > 1000 } @array;
若@array中有許多元素,而第一個元素就是1001,上面的代碼照舊檢查每一個元素,當然我們可以自行控制退出循環(huán)
my $found = 0;
foreach (@array) {
    $found = $_ if $_ > 1000;
    last if $found;
}

# 每次寫這一串很麻煩,List::Util中的first程序就是為了解決這個問題
use List::Util qw(first);
my $found = first {$_ > 1000} @array

# 此外,List::MoreUtils模塊中,也提供了許多額外的函數(shù)
use List::MoreUtils qw(any all none notall)
my $found = any {$_ >1000} @list;
my $found = all {$_ >1000} @list;
my $found = none {$_ >1000} @list;
my $found = notall {$_  % 2} @list;

一次遍歷多個列表
有時候我們手上有幾個相互關聯(lián)的列表需要同時遍歷,最普通的做法是利用數(shù)組下標,同步提取對應與元素,計算后存入另一個列表

my @a = ( ... );
my @b = ( ... );
my @c;
foreach (0 .. $#a) {
    my ($a,$b) = ($a[$_],$b[$_])
    push @c, $a + $b;
}

#用List::MoreUtils中的pairwise子程序,以漂亮的方式呈現(xiàn)
use List::MoreUtils qw(pairwise);
my @c = pairwise {$a + $b} @a, @b;  #pairwise只適用于兩個列表

# 對于三個及以上列表的同步計算,可以使用each_array子程序
use List::MoreUtils qw(each_array);
my $ea = each_array (@a,@b,@c);
my @d;
while (my ($a,$b,$c) = $ea ->() ) {
    push @d,$a + $b + $c;
}

數(shù)組合并
合并多個數(shù)組雖然可以自己寫,但還不如List::MoreUtils中的mesh子程序方便
use List::MoreUtils qw(mesh);
my @odds = qw (1 3 5 7 9);
my @evens = qw (2 4 6 8 10);
my @numbers = mesh @odds, @evens;

對列表進行去重

use List::Util qw(uniq);
my @num = qw (ab AB AB 1 1.0 2);
my @uniq = uniq @num;  # ab AB 1 1.0 2

#uniqnum對數(shù)字去重

use List::Util qw(uniqnum);
my @num = qw (1 1.0 2 3 3 4 1);
my @uniq = uniqnum @num;  # 1 2 3 4

12.用autodie簡化錯誤處理

perl有許多內(nèi)置函數(shù)都是系統(tǒng)調(diào)用,可能會因為不可控制的原因而失敗,所以有必要對最終運行結果作檢查。加上錯誤處理的代碼會讓整個程序看上去比較亂,有一種方法可以避免手工輸入這類代碼,即用autodie編譯指令

use autodie;
opne IN,"$file";  #會自動加入die相關的檢查

# 默認情況下,autodie會對它能起作用的所有函數(shù)生效,包括絕大多數(shù)內(nèi)建的同系統(tǒng)底層交互的函數(shù)

# 也可指定對某些特定函數(shù)起作用
use autodie qw(open close);
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容