包
Perl中的包就是單一名字空間下的代碼集合。包與名字空間的區(qū)別是:包關(guān)注點(diǎn)是源代碼;而名字空間是Perl用來組織和管理代碼的內(nèi)部數(shù)據(jù)結(jié)構(gòu)。
使用package來聲明一個(gè)包和名字空間(創(chuàng)建了包也就創(chuàng)建了名字空間):
package MyCode;
our @boxes;
sub add_box { ... }
聲明之后,接下來所有定義的變量和函數(shù)都將處在MyCode的名字空間中。這個(gè)包聲明的管轄范圍是直到遇到下一個(gè)package聲明或者是持續(xù)直到文件結(jié)尾。
還可以使用塊來指定聲明范圍:
package Pinball::Wizard
{
our $VERSION = 1969;
}
如果沒有包聲明,默認(rèn)的都是在main包中。以前我們講過了,要訪問其他包(名字空間)里面的變量和函數(shù)要使用完全限定名。比如在main包中使用@MyCode::boxes來訪問MyCode中的boxes數(shù)組。
包除了有名字,還有版本號(hào)和三個(gè)函數(shù):import()、unimport()、VERSION() 。函數(shù)VERSION() 返回包的版本號(hào),也就是包中$VERSION的值。Perl對(duì)版本號(hào)有格式上的要求:以字母v開頭和至少三個(gè)以點(diǎn)號(hào)(.)隔開整數(shù)。
package MyCode v1.2.1;
package Pinball::Wizard v1969.3.7 { ... }
#分開寫,老式的版本號(hào)寫法
package MyCode;
our $VERSION = 1.21;
每個(gè)包都從UNIVERSAL類中繼承了VERSION()函數(shù)。
my $version = Some::Plugin->VERSION;
#返回$VERSION的值
你也可以向函數(shù)傳遞(版本號(hào))參數(shù),如果模塊的版本號(hào)小于你傳遞的版本號(hào),函數(shù)會(huì)拋出異常:
# require at least 2.1
Some::Plugin->VERSION( 2.1 );
die "Your plugin $version is too old" unless $version > 2;
包和名字空間
在編譯期間和運(yùn)行時(shí)你可以在任何地方訪問或修改一個(gè)包的內(nèi)容,不過這可能會(huì)讓代碼非常難讀。
很多項(xiàng)目都會(huì)創(chuàng)建自己的頂級(jí)名字空間,這樣就減少全局變量沖突的可能性,同時(shí)也能更好的組織代碼,例如:
? StrangeMonkey 是項(xiàng)目名字
? StrangeMonkey::UI 用戶接口代碼
? StrangeMonkey::Persistence 數(shù)據(jù)處理代碼
? StrangeMonkey::Test 測(cè)試代碼
這只是一種約定,不是強(qiáng)制的,但是這種約定很通用。
引用
先看個(gè)例子:
sub reverse_greeting
{
my $name = reverse shift;
return "Hello, $name!";
}
my $name = 'Chuck';
say reverse_greeting( $name );
say $name;
我們期望通過一個(gè)函數(shù)來實(shí)現(xiàn)反轉(zhuǎn)功能。但是例子中的代碼卻是這樣工作的:將變量值傳遞給函數(shù),在函數(shù)內(nèi)部進(jìn)行反轉(zhuǎn)。但是完成后,出了函數(shù),變量值還是沒變(還是Chuck)。
再考慮一種情形:有一個(gè)值,你復(fù)制了很多份在不同的地方,現(xiàn)在這個(gè)值需要修改下,那么你想在每個(gè)地方都去改一下?有沒有一種機(jī)制可以實(shí)現(xiàn)一處修改,多出生效呢?可以,這就要用到引用了。
標(biāo)量引用
使用反斜杠創(chuàng)建引用。在標(biāo)量語境,創(chuàng)建單個(gè)的引用;在列表語境,創(chuàng)建一系列的引用。
對(duì)引用加記號(hào)($)就能訪問所引用的值了,這就是解引用。
my $name = 'Larry';
my $name_ref = \$name;
#通過引用訪問值
say $$name_ref;
將上面反轉(zhuǎn)的例子用引用來實(shí)現(xiàn):
sub reverse_in_place
{
my $name_ref = shift;
$$name_ref = reverse $$name_ref; #解引用的用法
}
my $name = 'Blabby';
reverse_in_place( \$name );
say $name;
參數(shù)@_就是參數(shù)變量的別名,所以還能這樣也能實(shí)現(xiàn)同樣的效果:
sub reverse_value_in_place
{
$_[0] = reverse $_[0];
}
my $name = 'allizocohC';
reverse_value_in_place( $name );
say $name;
#這樣的寫法很少見
調(diào)用函數(shù)傳遞引用可以降低內(nèi)存使用。
這個(gè)很容易理解,對(duì)比下傳遞一個(gè)很大的字符串作為參數(shù),和傳遞字符串的引用。
復(fù)雜的引用可能需要使用花括號(hào)來消除歧義,這才是完整的語法,只不過大部分時(shí)候都省略了:
sub reverse_in_place
{
my $name_ref = shift;
${ $name_ref } = reverse ${ $name_ref };
}
如果在使用時(shí)忘記了解引用,Perl會(huì)根據(jù)語境強(qiáng)制將轉(zhuǎn)換為字符串SCALAR(0x93339e8)或數(shù)字0x93339e8。(不過這并不一定是變量實(shí)際的內(nèi)存地址啊。
數(shù)組引用
數(shù)組引用有以下用途:
- 在函數(shù)中傳遞和返回?cái)?shù)組,而不是扁平的列表
- 創(chuàng)建多維數(shù)組結(jié)構(gòu)
- 避免不必要的數(shù)組復(fù)制
- 匿名數(shù)組
聲明一個(gè)數(shù)組引用:
my @cards = qw( K Q J 10 9 8 7 6 5 4 3 2 A );
my $cards_ref = \@cards;
任何通過引用$cards_ref進(jìn)行的修改將直接修改數(shù)組@cards。
你可以通過使用記號(hào)@來解引用,訪問整個(gè)數(shù)組。
my $card_count = @$cards_ref;
my @card_copy = @$cards_ref;
使用箭頭符號(hào)(->)訪問單個(gè)元素:
my $first_card = $cards_ref->[0];
my $last_card = $cards_ref->[-1];
也可以這樣訪問:
my $first_-card = $$cards_ref[0];
# 丑!
切片:
my @high_cards = @{ $cards_ref }[0 .. 2, -1];
#強(qiáng)烈推薦使用花括號(hào),提高可讀性
匿名數(shù)組,只能通過引用訪問
my $suits_ref = [qw( Monkeys Robots Dinos Cheese )];
區(qū)分以下2種情況:
#引用
my @meals = qw( soup sandwiches pizza );
my $sunday_ref = \@meals;
my $monday_ref = \@meals;
my @meals = qw( soup sandwiches pizza );
my $sunday_ref = [ @meals ];
my $monday_ref = [ @meals ];
#這2個(gè)引用并不能修改@meals數(shù)組,自己體會(huì)下。
哈希引用
創(chuàng)建一個(gè)哈希引用:
my %colors = (
blue => 'azul',
gold => 'dorado',
red => 'rojo',
yellow => 'amarillo',
purple => 'morado',
);
my $colors_ref = \%colors;
通過在引用前面使用記號(hào)%來訪問和使用整個(gè)哈希:
my @english_colors = keys %$colors_ref;
my @spanish_colors = values %$colors_ref;
使用箭頭操作符(->)解引用來訪問元素:
sub translate_to_spanish
{
my $color = shift;
return $colors_ref->{$color};
# or return $$colors_ref{$color};
}
切片:
my @colors = qw( red blue green );
my @colores = @{ $colors_ref }{@colors};
使用花括號(hào),創(chuàng)建匿名哈希:
my $food_ref = {
'birthday cake' => 'la torta de cumplea?os',
candy => 'dulces',
cupcake => 'bizcochito',
'ice cream' => 'helado',
};
函數(shù)引用
對(duì)函數(shù)名使用引用操作符和&符號(hào)來創(chuàng)建函數(shù)引用。
sub bake_cake { say 'Baking a wonderful cake!' };
my $cake_ref = \&bake_cake;
#使用&符號(hào)表示創(chuàng)建函數(shù)引用,若沒有&則會(huì)執(zhí)行函數(shù),對(duì)返回值創(chuàng)建引用。
使用關(guān)鍵字sub創(chuàng)建匿名函數(shù):
my $pie_ref = sub { say 'Making a delicious pie!' };
通過引用調(diào)用函數(shù):
$cake_ref->();
$pie_ref->();
文件句柄引用
在open和opendir操作符中使用詞法變量時(shí),這個(gè)變量就是文件句柄引用。這個(gè)句柄就是IO::File對(duì)象,可以直接調(diào)用對(duì)象方法:
use autodie 'open';
open my $out_fh, '>', 'output_file.txt';
$out_fh->say( 'Have some text!' );
老的代碼可能使用的是IO::Handle對(duì)象,更老的代碼可能使用的是符號(hào)引用:
local *FH;
open FH, "> $file" or die "Can't write '$file': $!";
my $fh = \*FH;
這些方式都仍然可以使用,但是我們建議最新的詞法變量方式。
引用計(jì)數(shù)
Perl使用引用計(jì)數(shù)的方式來管理內(nèi)存。
每一個(gè)Perl變量都有一個(gè)計(jì)數(shù)器。增加一個(gè)引用,Perl就將計(jì)數(shù)器加一,引用減少就減一。當(dāng)計(jì)數(shù)器為零時(shí),Perl認(rèn)為就可以安全的回收該變量了。
考慮以下代碼:
say 'file not open';
{
open my $fh, '>', 'inner_scope.txt';
$fh->say( 'file open here' );
}
say 'file closed here';
變量$fh的作用域就是在塊里面,當(dāng)超出范圍時(shí)變量失效,Perl將它的計(jì)數(shù)減一,變成零,Perl就回收對(duì)應(yīng)的內(nèi)存。
你無需明白所有的技術(shù)細(xì)節(jié)。你只要明白在使用引用時(shí)是如何影響Perl的內(nèi)存管理就夠了。
引用和函數(shù)
當(dāng)你向函數(shù)傳遞引用時(shí),要小心了:在函數(shù)中可能會(huì)修改原來的值,這可能不是你想要的。如果要避免這個(gè)情況,你應(yīng)該先將變量值復(fù)制到一個(gè)新變量:
my @new_array = @{ $array_ref };
my %new_hash = %{ $hash_ref };
#對(duì)于復(fù)雜數(shù)據(jù)結(jié)構(gòu)引用的復(fù)制,你可以考慮使用系統(tǒng)模塊Storable的dclone()函數(shù)。
嵌套數(shù)據(jù)結(jié)構(gòu)
有時(shí)候你可能需要嵌套的數(shù)據(jù)結(jié)構(gòu),比如你想創(chuàng)建一個(gè)多維數(shù)組,但下面這種方式可得不到你想要的:
my @counts = qw( eenie miney moe );
my @ducks = qw( huey dewey louie );
my @game = qw( duck duck goose );
my @famous_triplets = (
@counts, @ducks, @game
);
這會(huì)把每一個(gè)數(shù)據(jù)展開合并成一個(gè)列表,賦值進(jìn)一個(gè)數(shù)組,并不是什么多維數(shù)組。
解決辦法就是引用:
#使用有名字的數(shù)組引用
my @famous_triplets = (
\@counts, \@ducks, \@game
);
#匿名引用創(chuàng)建多維結(jié)構(gòu):
my @famous_triplets = (
[qw( eenie miney moe )],
[qw( huey dewey louie )],
[qw( duck duck goose )],
);
#匿名哈希
my %meals = (
breakfast => { entree => 'eggs',
side => 'hash browns' },
lunch => { entree => 'panini',
side => 'apple' },
dinner => { entree => 'steak',
side => 'avocado salad' },
);
最后一個(gè)元素項(xiàng)后面的逗號(hào)是可選的,加上它會(huì)方便以后添加元素。
使用引用訪問多維數(shù)據(jù)結(jié)構(gòu),箭頭是可選的:
#省略箭頭
my $nephew = $famous_triplets[1][2];
my $meal = $meals{breakfast}{side};
#某些情況有箭頭反而顯得多余
my $last_nephew = $famous_triplets[1]->[2];
my $meal_side = $meals{breakfast}->{side};
#通過引用調(diào)用函數(shù)建議加上箭頭:
$actions{generous}{buy_food}->( $nephew, $meal );
嵌套數(shù)據(jù)結(jié)構(gòu)的內(nèi)容如果是數(shù)組和哈希,通過括號(hào)增加可讀性:
my $nephew_count = @{ $famous_triplets[1] };
my $dinner_courses = keys %{ $meals{dinner} };
嵌套數(shù)據(jù)的切片:
my ($entree, $side) =
@{ $meals{breakfast} }{ qw( entree side ) };
更清楚的方式:
my $meal_ref = $meals{breakfast};
my ($entree, $side) = @$meal_ref{qw( entree side )};
my ($entree, $side) = @{ $_ }{qw( entree side )}
for $meals{breakfast};
perldoc perldsc 可以查看Perl中各種數(shù)據(jù)結(jié)構(gòu)的使用例子。
自動(dòng)激活
當(dāng)你寫下嵌套數(shù)據(jù)結(jié)構(gòu)的一部分時(shí),Perl會(huì)自動(dòng)創(chuàng)建中間必要的過程:
my @aoaoaoa;
$aoaoaoa[0][0][0][0] = 'nested deeply';
Perl會(huì)自動(dòng)創(chuàng)建這個(gè)四維數(shù)組,每層數(shù)組包含一個(gè)元素。
類似的,下面這句會(huì)自動(dòng)創(chuàng)建一個(gè)哈希的哈希:
my %hohoh;
$hohoh{Robot}{Santa} = 'mostly harmful';
這個(gè)行為就叫做自動(dòng)激活。這很方便,但是也有可能在不經(jīng)意間誤解了你的真實(shí)意圖(你自己寫錯(cuò)了)。CPAN上有個(gè)autovivification的編譯指令可以控制該特性啟用和范圍。
調(diào)試嵌套數(shù)據(jù)結(jié)構(gòu)
調(diào)試嵌套數(shù)據(jù)結(jié)構(gòu)是困難的,幸好有幾個(gè)好工具。
系統(tǒng)模塊Data::Dumper可以把數(shù)據(jù)結(jié)構(gòu)顯示出來:
use Data::Dumper;
my $complex_structure = {
numbers => [ 1 .. 3 ];
letters => [ 'a' .. 'c' ],
objects => {
breakfast => $continental,
lunch => $late_tea,
dinner => $banquet,
},
};
print Dumper( $complex_structure );
$VAR1 = {
'numbers' => [
1,
2,
3
],
'letters' => [
'a',
'b',
'c'
],
'meals' => {
'dinner' => bless({...}, 'Dinner'),
'lunch' => bless({...}, 'Lunch'),
'breakfast' => bless({...}, 'Breakfast'),
},
};
當(dāng)然還有其他模塊也能干這個(gè)事情:YAML::XS 和 JSON模塊。開發(fā)者可能更傾向于使用這2模塊,因?yàn)檫@2模塊不會(huì)產(chǎn)生Perl代碼,輸出結(jié)果更清晰。,Data::Dumper則顯示得非常詳細(xì)。
回路引用
當(dāng)存在回路引用(相互引用)的時(shí)候,引用記錄器將永遠(yuǎn)不可能為0,Perl也就永遠(yuǎn)無法回收其內(nèi)存。
my $alice = { mother => '', father => '' };
my $robin = { mother => '', father => '' };
my $cianne = { mother => $alice, father => $robin };
push @{ $alice->{children} }, $cianne;
push @{ $robin->{children} }, $cianne;
可以使用Scalar::Util's 的weaken()函數(shù)(弱引用),使用弱引用不會(huì)增加引用計(jì)數(shù)。
use Scalar::Util 'weaken';
my $alice = { mother => '', father => '' };
my $robin = { mother => '', father => '' };
my $cianne = { mother => $alice, father => $robin };
push @{ $alice->{children} }, $cianne;
push @{ $robin->{children} }, $cianne;
weaken( $cianne->{mother} );
weaken( $cianne->{father} );
當(dāng)然絕大部分正常的數(shù)據(jù)結(jié)構(gòu)都不會(huì)出現(xiàn)回路引用,也就不需要使用弱引用。
替代嵌套數(shù)據(jù)結(jié)構(gòu)
對(duì)Perl來說無所謂,再復(fù)雜的嵌套數(shù)據(jù)結(jié)果都能順利處理,但是人不行,嵌套超過2層或3層人就難以理解了。這時(shí)就可以考慮使用類和對(duì)象技術(shù)來讓代碼更加清晰。