第三章 Perl語言(五)-包、引用、復(fù)雜數(shù)據(jù)

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ù)來讓代碼更加清晰。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,901評(píng)論 11 349
  • 第5章 引用類型(返回首頁) 本章內(nèi)容 使用對(duì)象 創(chuàng)建并操作數(shù)組 理解基本的JavaScript類型 使用基本類型...
    大學(xué)一百閱讀 3,692評(píng)論 0 4
  • 從匹配中返回值 Match 對(duì)象 成功的匹配總是返回一個(gè) Match 對(duì)象, 這個(gè)對(duì)象通常也被放進(jìn) $/ 中, (...
    焉知非魚閱讀 1,943評(píng)論 0 1
  • 你是要養(yǎng)個(gè)小娃娃的嗎 身懷六甲是為了給小生命占山為王呀 孩子,是生命輪回中的徘徊吧 移一棵心 在泥土里發(fā)芽,破繭而...
    江城妖怪閱讀 334評(píng)論 1 3
  • 今天或許值得紀(jì)念,因?yàn)槲彝蝗辉谧咴隈R路上的某個(gè)時(shí)刻,發(fā)現(xiàn)了自己討人厭的原因。原來我,一直都太想證明自己。這大概是我...
    白謊話閱讀 331評(píng)論 0 0

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