Perl进阶
前言¶
- Perl版本:v5.14
第1章 简介¶
1.1 获取帮助的方式¶
- Stack Overflow
- Perlmonks:在线Perl社区。需要翻墙
- perl学习站点
1.2 strict和warnings¶
-
所有代码都应该打开
strict
和warnings
,以规范编写的perl代码,如:
1.3 程序版本¶
- 告知程序版本可以避免后续使用新版本的Perl时,会因为新加入的特性导致程序无法正常工作。
- 方法:
use 5.014
use v5.14.2
use 5.14.2
1.4 书单¶
- 有助于编写可复用代码:《Perl Best Practices》- Damian Conway
第2章 使用模块¶
2.1 CPAN¶
- CPAN是Perl的杀手锏,有各种信息、模块和服务:
- CPAN主页:http://www.cpan.org
- CPAN搜索服务页面:http://search.cpan.org
- MetaCPAN:https://www.metacpan.org
- CPAN的下一代搜索页面
- CPAN Testers:http://cpantesters.org
- 作者上传至CPAN的每一个模块都会被自动测试,并将报告上传到此。
- CPANdeps:http://deps.cpantesters.org
- Tester的进一步补充,合并了依赖性信息。
- CPAN RT:http://rt.cpan.org
- 问题跟踪系统
2.2. 阅读模块手册¶
使用perldoc
来阅读模块文档,Unix则用man
也可以,如:
2.3 功能接口¶
-
加载模块:
- 这样会把模块的默认符号加载到当前脚本中。
- 注意:如果当前脚本中已经有该符号则会被覆盖。
-
选择需要导入的内容,仅导入
fileparse
和basename
:- 空列表特指不导入任何符号。
2.4 面向对象的接口¶
use File::Spec;
my $filespec = File::Spec->catfile( $homedir{homqyy}, 'web_docs', 'photos', 'USS_Minnow.gif');
2.5 核心模块内容¶
-
Module::CoreList
是一个数据结构和接口库,该模块把很多关于Perl 5版本的模块历史信息聚合在一起,并且提供一个可编程的方式以访问它们: -
也可以在bash中直接运行命令
corelist
:% corelist Module::Build
2.6 通过CPAN安装模块¶
-
自动安装:
- 使用perl自带的
cpan
工具安装:% cpan Perl::Critic
- 使用
cpanp
(CPAN Plus):% cpanp -i Perl::Tidy
- 使用
cpanm
(CPAN Minus):% cpanm DBI WWW::Mechanize
- 零配置,轻量级的CPAN客户端
- 使用perl自带的
-
手动安装:
- Makefile.PL:
- 下载perl模块包:
% wget <URL>
(该URL可以从CPAN站点中获取) - 解压perl模块包:
% tar -xzf <MODULE.tar.gz>
- 进入模块目录:
% cd <MODULE>
% perl Makefile.PL
- 可以用
INSTALL_BASE
参数来指定安装的路径:perl Makefile.PL INSTALL_BASE=/home/homqyy/
- 可以用
% make
% make test
% make install
- 下载perl模块包:
- Build.PL
- 下载perl模块包:
% wget <URL>
(该URL可以从CPAN站点中获取) - 解压perl模块包:
% tar -xzf <MODULE.tar.gz>
- 进入模块目录:
% cd <MODULE>
% perl Build.PL
- 可以用
--install_base
参数来指定安装路径:% perl Build.PL --install_base /home/homqyy/
- 可以用
% perl Build
% perl Build test
% perl Build install
- 下载perl模块包:
- Makefile.PL:
2.7 搜索路径¶
-
perl是通过
@INC
数组里的路径去搜索模块的,可以通过以下两种方式获取@INC
的值:% perl -V
% perl -le "print for @INC
-
程序中添加指定路径:
- 推荐使用
use lib
,它是在编译执行,行为等效于首个例子:将参数给的路径加入到@INC
数组的开头。 use constant
可以设置常量,也是在编译时运行。
- 推荐使用
-
上述的指定路径是硬编码,不具备可移植性,我们可以利用
FinBin
模块来实现更通用的添加路径:
2.8 在程序外部设置搜索路径¶
- 使用环境变量
PERL5LIB
:% export PER5LIB=/home/homqyy/lib:/usr/local/lib/perl5
- 设立
PERL5LIB
环境变量的目的是为了给非管理员用户也能够扩展Perl的安装路径,如果管理员想增加额外的安装目录,只需要重新编译并安装Perl即可。 - 也可以在程序运行的使用通过
-I
选项来扩展安装路径:% perl -I/home/homqyy/lib test.pl
2.9 local::lib¶
-
在没有管理员权限的时候,我们需要有个便携的安装路径以及自动找到路径的方法,这时候就可以使用
local::lib
。 -
安装
- 该模块还不是核心模块,需要用
cpan
下载:% cpan local::lib
- 该模块还不是核心模块,需要用
-
查看提供的环境变量:
% perl -Mlocal::lib
: -
使用其安装模块:
- 对于
cpan
:% cpan -I Set::Crossproduct
- 对于
cpanm
:% cpanm --local-lib HTML::Parser
- 对于
-
在脚本中自动将安装的路径加载到
@INC
中: -
自定义路径:
第3章 中级基础¶
- 常见列表操作符:
print
:打印sort
:正序排列reverse
:反序排列push
:添加元素
3.1 使用grep过滤列表¶
-
grep会测试列表中的每个值,如果测试结果为真(标量上下文),则将值返回:
my @input_numbers = (1, 2, 4, 8, 10, 15, 20, 51, 60); # 1. compare express my @bigger_then_10 = grep $_ > 10, @input_numbers; # filter number of greater than 10 my $bigger_than_10_num = grep $_ > 10, @input_numbers; # count number of greater than 10 # 2. pattern my @end_in_0 = grep /4$/, @input_numbers; # filter number of end in 4 # 3. function my @odd_digit_sum = grep digit_sum_is_odd($_), @input_numbers; sub digit_sum_is_odd { my $input = shift; my @digits = split //, $input; # Assume no nondigit characters my $sum; $sum += $_ for @digits; $sum % 2; } # 4. block of code my @odd_digit_sum = grep { my $input = $_; my @digits = split //, $input; # Assume no nondigit characters my $sum; $sum += $_ for @digits; $sum % 2; } @input_numbers; # 5. optimization my @odd_digit_sum = grep { my $sum; $sum += $_ for split //; $sum % 2; } @input_numbers;
- 这里的
$_
是列表中的每个元素值,而且是别名,即:如果修改了$_
值,则原值也将被修改。 - 代码快实际上是一个**匿名子例程**。
- 将**示例3**用**示例4**的代码块代替时,有两处需要变更:
- 不再使用入参,而是
$_
; - 不使用
return
- 不再使用入参,而是
- 这里的
3.2 使用map转换列表¶
-
功能是将列表中的元素转换成另一个(列表上下文)。与grep一样,支持**表达式**和**代码块**。
-
因为是列表上下文,因此一个元素可以产生多个结果:
-
列表转hash:当列表成对时,可以将其转成hash,列表会被按'Key-Value'解析:
-
有时候我们不关心键值,只关心是否有键存在,这时候可以如此:
-
-
可以返回一个空列表
()
来表示此次结果不记录到列表中,即删除列表中的某个元素my @result = map { my @digits = split //, $_; if ($digits[-1] == 4) { @digits; } else { # 不处理非4结尾的数据 (); } } @input_numbers;
- 因此,利用此特性我们可以用
map
来代替grep
- 因此,利用此特性我们可以用
3.3 使用eval捕获错误¶
-
使用
eval
来捕获错误,避免程序因为出错直接崩溃。如果捕获到错误,则$@
会有值,反之则为空。最常见的用法就是在eval
之后立刻判断$@
的值: -
eval
语句块后的分号是必须的,因为它是一个术语,语句块是真实的语句块,而不是像if
和while
。 eval
语句块中可以包含my
等任意语句。-
eval
语句块有类似子例程的返回值(最后一行表达式求值,或者之前通过return
返回的值)。如果块中代码运行失败,在标量上下文中返回undef
,在列表上下文中返回空列表()
: -
eval
语句块不能捕获最严重的错误:使perl自己中断的错误。 -
可以使用
Try::Tiny
来处理复杂的异常:
3.4 使用eval动态编译代码¶
-
这是
eval
的第二种形式,它的参数是**字符串表达式**而不是**代码块**:
3.5 使用do语句块¶
-
do
将一组语句汇聚成单个表达式,其执行结果为最后一个表达式的值。do
非常适合创建一个操作的作用域: -
do
还支持**字符串参数**的形式:do
语句查找文件并读取该文件,然后切换内容为eval
语句块的字符串形式,以执行它。- 因此
do
将忽视文件中的任何错误,程序将继续执行。
3.6 require¶
-
除了
use
也可以用require
来加载模块,实际use
等效于以下代码:- 但是
require
实际上是在运行程序时执行的,不过require
可以记录所加载的文件,避免重复加载同样的文件。
- 但是
第4章 引用简介¶
- 这里的引用,效果类似指针,但与指针不同的是这里指向的是整个数组,而不是首个元素。
4.1 在多个数组上完成相同的任务¶
-
示例:为了海上安全,每个登上Minnow的人需要有一套救生用具、一瓶防晒霜、一个水壶和一件雨衣:
sub check_required_items { my $who = shift; my %whos_items = map { $_, 1 } @_; # the rest are the person's items my @required = qw(preserver sunscreen water_bottle jacket); for my $item (@required) { unless ( $whos_items{$item} ) { # not found in list? print "$who is missing $item.\n"; } } } my @skipper = qw(blue_shirt hat jacket preserver sunscreen); my @professor = qw(sunscreen water_bottle slide_rule batteries radio); check_required_items('skipper', @skipper); check_required_items('professor', @professor);
-
上述示例在将数组的值传递给了方法
check_required_items
,如果值大量的话势必会造成大规模的复制数据,浪费空间并损耗性能。
4.2 Perl图形结构(PeGS)¶
- 该图形由**Joseph Hall**开发,用图形可以方便的解释Perl,图形内容不做记录。
4.3 数组引用¶
-
取引用
-
可以将引用的地址传递给函数,这样我们需要的话还能直接修改其值,如同C语言的指针一样
check_required_items("The Skipper", \@skipper); sub check_required_items { my $who = shift; my $items = shift; # 获取引用值 my %whose_items = map { $_, 1 } @$items; # 解引用 my @required = qw(preserver sunscreen water_bottle jacket); for my $item (@required) { unless ( $whose_items{ $item } ) # 没有在列表中发现该工具 { print "$who is missing $item.\n"; push @missing, $item; } } if (@missing) { print "Adding @missing to @$items for $who.\n"; push @$items, @missing; # 修改引用项的内容:向里面增加缺失的工具 } }
-
解引用
- 用括号来解引用,应
@
表明以数组的方式解引用:@{ $itmes }
- 如果引用项是简单的
$
+裸字,则可以省略掉中间的括号:@$items
- 用括号来解引用,应
4.4 嵌套的数据结构¶
-
假设我们有如下嵌套结构:
-
运用4.3知识,
@{ $all_with_names[0] }
进行解引用可以得到带有两个元素的数组@skipper_with_name
的引用。接着可以用如下方式获取名字和其携带的工具:
4.5 用箭头简化嵌套元素的引用¶
- 学过C语言的都知道
->
具有解引用的效果,而Perl也支持类似的方式:${ $all_with_names[0] }[0]
等效于$all_with_names[0]->[0]
${ $all_with_names[0] }[0][1]
等效于$all_with_names[0]->[0][1]
4.6 散列的引用¶
-
与数组一样,只是散列解引用时用的是
%
和{}
:# 创建一个散列结构 my %gilligan_info = { name => 'Gilligan', hat => 'White', shirt => 'Red', position => 'First Mate', }; my $hash_ref = \%gilligan_info; # 引用散列 # 获取名称 $name = ${ $hash_ref }{'name'}; # 带括号的形式 $name1 = $$hash_ref{'name'} # 不带括号的形式 $name2 = $hash_ref->{'name'} # `->`形式 # 重获散列结构 my @keys = keys %{ $hash_ref }
4.7 数组与散列的嵌套引用¶
-
结合4.5和4.6即可,比如:
-
可以如下使用各值:
- 选用一个我喜欢的方式:
$crew[0]->{'name'}
- 选用一个我喜欢的方式:
4.8 检查引用类型¶
-
使用
ref
来检查引用类型:use Carp qw(croak); sub show_hash { my $hash_ref = shift; my $ref_type = ref $hash_ref; croak "I expected a hash reference!" unless $ref_type eq 'HASH'; while (my ($key, $value) = each %$hash_ref ) { print "$key => $value\n"; # ...其他 } } # 测试:成功 &show_hash({'name' => 'successful'}); # 测试:失败 &show_hash(['failed']);
-
但是
ref
返回的是引用类型的字符串,如果有一个表现类似于散列引用的对象,程序将运行失败,因为字符串是不同的。,可以使用Scalar::Util
模块的reftype
代替: -
也可以用
eval
来检查:
第5章 引用和作用域¶
-
Perl通过“引用计数”的来实现“自动回收垃圾”的机制。即,一块数据仅当引用计数为0时被销毁,且被销毁的数据空间通常并不会返还给操作系统,而是保留给下一次需要空间的数据使用。
-
每创建一个数据的时候,引用计数值初始为1。
5.1 循环引用造成内存泄露¶
-
示例:
-
使用引用计数在循环引用的情况下无法正常处理,因为它的引用计数将永远不为0:如例子,
@data1
和@data2
结束生命周期后,两个列表的引用计数都还为1。 -
因此,我们必须谨防创建循环引用,或则在不得不这样做的时候,在变量超出作用于之前打断“环”:
{ my @data1 = qw(one won); my @data2 = qw(two too to); # 创建循环引用 push @data2, \@data1; # @data2引用@data1,'qw(one won)'的引用数变成2 push @data1, \@data2; # @data1引用@data2,'qw(two too to)'的引用数变成2 # 打破环 @data1 = (); # 解除对@data2的引用,'qw(one won)'引用数减1,剩下1 @data2 = (); # 解除对@data2的引用,'qw(two too to)'引用数减1,剩下1 } # 由于@data1和@data2超出作用于,因此引用计数从1减为0,回收数据空间
5.2 匿名数组和散列¶
-
匿名数组使用
[]
创建,匿名散列由{}
创建: -
由于匿名散列与代码块有冲突,因此我们可以在左括号前加入一个
+
来显示的告诉Perl这是一个匿名散列,在左括号后面加入一个;
来显示表示是一个代码块:
5.3 自动带入¶
-
如果没有给变量(或者访问数组或者散列中的单个元素)赋值,Perl将自动创建代码过程假定存在的引用类型。
-
示例:
第6章 操作复杂的数据结构¶
6.1 使用调试器¶
- 在运行程序时添加
-d
参数来启动调试模式,类似于C程序的gdb
:% perl -d ./test.pl
- 使用方法可以查阅手册
perldebug
,或则在通过-d
启动后输入h
来查看帮助。
6.2 使用 Data::Dumper 模块查看复杂数据¶
- 该模块提供了一个基本方法,将Perl的数据结构显示为Perl代码:
- 输入文件为:
- 结果如下所示:
$VAR1 = {
'thurston.howell.hut' => {
'lovey.howell.hut' => 1250
},
'professor.hut' => {
'lovey.howell.hut' => 1360,
'gilligan.crew.hut' => 1250
},
'ginger.girl.hut' => {
'maryann.girl.hut' => 199,
'professor.hut' => 1218
}
};
-
该模块目的是用于后续重建数据结构使用,因此输出结构就是一段完成的Perl代码
-
其他转储程序
-
Data::Dump
: -
Data::Printer
-
6.4 数据编组¶
- 数据编组:利用
Data::Dumper
可以将复杂的数据结构转化为字节流,这样可以供另一个程序使用。 -
因为
Data::Dumper
输出的符号将变成普通的**VAR**符号,这样会影响阅读,因此可以利用Dump
接口来实现符号的定义: -
更适合编组的模块
Storable
:原因是其生成的更短小并且易于处理的文件:- 要求:必须把所有数据放入到一个引用中
-
使用方法1:内存
- 使用方法2:文件
-
浅复制和深复制:平时使用的
my @d1 = @d2
是浅拷贝,而Storable
提供了深拷贝的方法:my @d1 = @{ dclone \@d2 }
-
YAML模块:通过该模块可以让被
Data::Dumper
编组后的数据可读性更强 - JSON模块:提供了将数据结构与JSON格式间相互转换的方法
第7章 对子例程的引用¶
7.1 引用子例程¶
-
与数组和散列引用一样,也是用
\
进行引用,比如: -
解引用也是有3种:
-
匿名子例程,格式为:
sub { ...body of subroutine };
,结果是创建了一个匿名函数的引用,比如: -
回调函数:通过传递一个函数的引用形成回调,比如:
7.2 闭包¶
-
在Perl术语中,闭包是一个包含已经超出作用域的词法变量的子例程:
-
返回多个子例程:
use File::Find; sub create_find_callbacks_that_sum_the_size { my $total_size = 0; return (sub { $total_size += -s if -f }, sub { return $total_size }); } my ($count_em, $get_results) = create_find_callbacks_that_sum_the_size(); find($count_em, '.'); my $total_size = &$get_results(); print "total size of . is $total_size\n";
-
也可以通过参数来初始化**闭包变量**
-
命名子例程也可以作为闭包函数,但是闭包变量的位置很重要:
- 首先,
my $countdown = 10
这句赋值语句有两层含义:- 在编译阶段,将
$countdown
翻译为词法变量,并且值都为undef
- 在运行阶段,给
$countdown
赋值为10
- 在编译阶段,将
-
因此,以下代码将不能正常工作:
-
解决方案:
-
将
$countdown
提前放置: -
将闭包变量和定义都放到BEGIN中(测试失败): -
使用
state
变量:在Perl v5.10开始引入了此功能,类似于C语言的静态变量(static声明的):
-
- 首先,
-
转储闭包:使用
Data::Dump::Streamer
模块可以将代码引用和闭包的内容展示出来
第8章 文件句柄引用¶
-
在 Perl v5.6 及后续版本,
open
支持打开匿名的临时文件:
8.1 typeglob¶
-
在旧版本上,使用符号表(是typeglob,书籍翻译成符号表有点不好理解,因为还有个symbol table)来传递文件句柄:
8.2 标量¶
- 从Perl v5.6开始,open能够用标量来存储句柄了,前提是该变量的值必须是
undef
- 建议在文件句柄部分加上大括号,以显示声明我们的意图
- 当标量超出作用域后Perl将自动关闭对应的文件句柄,可以不显示的关闭
- 如果想在老版本中使用标量,则可通过模块
IO::Scalar
来实现。 -
示例:
8.3 指向字符串¶
-
从Perl v5.6开始,能够以文件句柄的形式打开一个标量而不是文件:
-
可以利用此特性和钻石操作符结合来处理多行字符串:
8.4 IO::Handle¶
-
将文件句柄以对象的形式使用:
8.5 IO::File¶
-
使用该模块以一个更友好的方式来使用文件句柄:
8.6 IO::Tee¶
- 多路通道输出模块,该模块功能等效于shell工具:
tee
8.7 IO::Pipe¶
-
使用该模块来执行命令:
8.8 IO::Null¶
-
可以利用此模块进行调试输出的设置:
8.9 IO::Dir¶
- 用该模块去操作目录
9 正则表达式引用¶
- 预编译操作符:
qr//
- 如果用单引号
'
作为分隔符(qr''
),则Perl解释器就不会做任何双引号插入操作:qr'$var'
- 正则表达式选项:
- 可以用3种方式添加选项(
flags
):- 在匹配或替换操作符最后一个分隔符后面添加:
m/pattern/flags
或s/pattern/flags
- 在qr后面添加:
qr/pattern/flags
- 在模式本身中指定:
?flags:pattern
- 能够在模式中的flag前面追加一个-号表明要移除某个修饰符:
qr/abc(?x-i:G i l l i g a n)def/i
,使用了x
,移除了i
- 能够在模式中的flag前面追加一个-号表明要移除某个修饰符:
- 在匹配或替换操作符最后一个分隔符后面添加:
- 可以用3种方式添加选项(
-
正则表达式可以以变量的形式使用,通过
qr//
操作符来创造引用:use experimental qw(smartmatch); my $string = 'Gilligan test'; my $regex = qr/Gilligan/; print "variable of regex\n" if $string =~ m/$regex/; # 也可以直接绑定,不用使用 m// print "normal regex\n" if $string =~ $regex; print "smartmatch\n" if $string ~~ $regex; $string =~ s/$regex/Skipper/; print "after modify: $string\n";
use experimental qw(smartmatch); use feature say; my @patterns = ( qr/(?:Willie )?Gilligan/, qr/Mary Ann/, qr/Ginger/, qr/(?:The )?Professor/, qr/Skipper/, qr/Mrs?\. Howell/, ); my $name = 'Ginger'; # 手动遍历 foreach my $pattern (@patterns) { if ($name =~ $pattern) { say "Manual Match!"; last; } } # 智能匹配将遍历数组中的每个元素 say "Smart Match!" if $name ~~ @patterns;
-
当在一个更大的模式中引用正则表达式时,正则的引用其相当于一个原子(原理是
qr
操作的pattern会自动加上非捕获圆括号(?:)
:my $poor_people = qr/$r1|$r2/;
-
**RFC 1738**规定了URL格式,其翻译结果如下:
my $alpha = qr/[a-z]/; my $digit = qr/\d/; my $alphadigit = qr/(?i:$alpha|$digit)/; my $safe = qr/[\$_.+-]/; my $extra = qr/[!*'\(\),]/; my $national = qr/[{}|\\^~\[\]`]/; my $reserved = qr|[;/?:@&=]|; my $hex = qr/(?i:$digit|[A-F])/; my $escape = qr/%$hex$hex/; my $unreserved = qr/$alpha|$digit|$safe|$extra/; my $uchar = qr/$unreserved|$escape/; my $xchar = qr/$unreserved|$reserved|$escape/; my $ucharplus = qr/(?:$uchar|[;?&=])*/; my $digits = qr/(?:$digit){1,}/; my $hsegment = qr/ucharplus;/; my $hpath = qr|$hsegment(?:/$hsegment)*|; my $search = $ucharplus; my $scheme = qr|(?i:https?://)|; my $port = qr/$digits/; my $password = $ucharplus; my $user = $ucharplus; my $toplevel = qr/$alpha|$alpha(?:$alphadigit|-)*$alphadigit/; my $domainlabel = qr/$alphadigit|$alphadigit(?:$alphadigit|-)*$alphadigit/; my $hostname = qr/(?:$domainlabel\.)*$toplevel/; my $hostnumber = qr/$digits\.$digits\.$digits\.$digits/; my $host = qr/$hostname|$hostnumber/; my $hostport = qr/$host(?::$port)?/; my $login = qr/(?:$user(?::$password)\@)?/; my $urlpath = qr/(?:(?:$xchar)*)/;
-
使用
Regexp::Common
模块来直接获取某个pattern。 -
egexp::Assemble
模块帮助我们建立高效的择一匹配 -
List::Util
模块中的first
函数功能类似grep
,但是它只要成功命中一次就停止继续匹配。
第10章 使用的引用技巧¶
10.1 施瓦茨变换¶
-
一个高效的排序结构:
my @output_data = map { EXTRACTION }, # 提取 sort { COMPARISON } # 比较,如果是哈希形式的话,可以如此使用:{ $a->{xxx} cmp $b->{xxx} } map [ CONSTRUCTION ],# 构造,也支持哈希的形式:map { CONSTRUCTION }
- 因为map操作符和sort操作符是从右到左执行的,所以我们应该由下而上地读这个结构。
-
示例
10.2 递归定义的数据¶
在递归算法的不同分支上拥有多个基线条件是很常见的。没有基线条件的递归算法将是无限循环。
递归子例程有一个调用它本身的分支用于处理部分任务,以及一个不调用它本身的分支用于处理基线条件。
注意
类似Perl的动态语言无法自动将“尾递归”转为循环,因为再一次调用子例程之前,子例程定义可能改变。
use Data::Dumper;
sub data_for_path {
my $path = shift;
if (-f $path or -l $path) # files or symbolic links
{
return undef;
}
if (-d $path)
{
my %directory;
opendir PATH, $path or die "Cannot opendir $path: $!";
my @names = readdir PATH;
closedir PATH;
for my $name (@names)
{
next if $name eq '.' or $name eq '..';
$directory{$name} = data_for_path("$path/$name");
}
return \%directory;
}
warn "$path is neither a file nor a directory\n";
return undef;
}
print Dumper(&data_for_path('../'));
10.3 避免递归¶
-
迭代取代递归的模板:
sub iterative_solution { my ($start) = @_; my $data = {}; my @queue = ( [$start, $data] ); while ( my $next = shift @queue ) { # ... process current element ... # ... add new things to @queue ... } return $data; }
- 在模板中,
@queue
数组中的每项都携带希望处理的全部元素,其中每个元素都是匿名数组。此时start是需要处理的项,并且$data是用于存储结果的引用。
- 在模板中,
-
深度优先解决方案:FIFO
- 优势:能够在任意喜欢的层级很轻易地停留。
-
广度优先解决方案:LIFO
第11章 构建更大型的程序¶
11.1 基本概念¶
-
函数获取参数的方法:
my $arg = shift
:作者更喜欢这种(my $arg) = @_
:与lua风格相似
-
.pm
扩展名是“Perl模块”的意思
11.2 嵌入代码¶
-
用
eval
嵌入代码:eval $code_string; die $@ if $@;
- 类似
bash
下的resource
,eval
可以调用作用域下的任何词法变量
- 类似
-
用
do
嵌入代码:do 'Navigation.pm'; die $@ if $@;
- 导入的代码作用域在
do
自己里面,因此类似my
等语句并不会影响主程序。 - 不会搜索模块目录,因此需要提供绝对路径或相对路径。
- 导入的代码作用域在
-
用
require
嵌入代码:追踪文件,可以避免重复- 导入文件中的任何语法错误都将终止程序,所以不再需要很多
die $@ if $@
语句; - 文件中的最后一个求值表达式必须返回一个真值,因此
require
语句才能知道该文件正确导入- 由于这个特点,用于
require
的文件在末尾都需要加个神秘的1
- 由于这个特点,用于
- 导入文件中的任何语法错误都将终止程序,所以不再需要很多
11.3 命名空间¶
- 命名空间可以避免符号冲突。
- 对于
.pm
的文件,在文件开头增加命名空间:package Navigation;
。 - 命名规则与变量一样,包名应当以一个大写字母开头(来源于
perlmodlib
文档) - 包名也可以由
::
(双冒号)分隔多个名称:Name1::Name2
-
主程序的包名为
main
-
Package有作用域:
-
代码块:
-
Perl v5.12后支持包语句块:
-
-
无论当前包如何定义,有些名称或变量总在
main
包中:- 名称:
ARGV
,ARGVOUT
,ENV
,INC
,SIG
,STDERR
,STDIN
,STDOUT
- 标点符号变量:
$_
,$2
等
- 名称:
-
设置包版本的方法:
- 设置
$VERSION
的值:our $VERSION = '0.01'
- 在v5.12版本后可以用
package
指定:package Navigation 0.01
- 设置
第12章 创建你自己的发行版本¶
- 构建方法有两种:
- Makefil.PL:老的,基于
make
,使用ExtUtils::Maker
构建 - Build.PL:新的,存Perl工具,使用
Module::Build
构建
- Makefil.PL:老的,基于
12.1 构建工具¶
- h2xs
- Module::Starter
- 创建模板:Module::Starter::Plugin
- Dist::Zilla:这个模块不但可以自动创建发行版,而且在我们修改发行版中的文件后,它还知道如何更新发行包。
12.2 Build.PL¶
- 创建构建框架:
% module-starter --module=Animal --author="yourName" --email="yourEmail" --verbose --mb
- 创建构建脚本:
% perl Build.PL
- 开始构建:
% ./Build
- 执行测试:
% ./Build test
- 发行前检测一下内容是否有遗漏:
% ./Build disttest
- 发行版本:
% ./Build dist
12.3 Makefile.PL¶
- 创建构建框架:
% module-starter --module=Animal --author="homqyy" --email="youEmail" --verbose --builder="ExtUtils::Makemaker"
- 创建构建脚本:
% perl Makefile.PL
- 开始构建:
% make
- 执行测试:
% make test
- 发行前检测一下内容是否有遗漏:
% make disttest
- 发行版本:
% make dist
12.3 添加额外的模块¶
- 安装插件:
Module::Starter::AddModule
- 添加额外的模块:
module-starter --moudle=addon_module --dist=.
12.4 目录文件介绍¶
MANIFEST
:记录检查的结果META.*
文件描述了发行版本的信息,其中客户端最关系_require
相关字段。
12.5 文档¶
- pod语法:
perldoc pod
- 检测格式:
podchecker
- 查看或产生文档:
- 查看:
perldoc module.pm
- 产生文档:
pod2html
,pod2man
,pod2text
,pod2usage
- 查看:
12.5.1 段落¶
-
标题:
=head n
-
有序列表:
-
无序列表
-
文本:
12.5.2 文本格式¶
- 每一种含格式都以一个大写字母开始,并且用
<
和>
括住所需的内容- B<粗体文本>
- C<代码文本>
- E<实体文本>
- I<斜体文本>
- L<链接文本>
-
根据需要,可以增加
<
和>
的个数,只要成对就行:B<<< 粗体文本 >>> -
使用utf8:
第13章 对象简介¶
- 面向对象编程(OOP)
- 对于Perl来说,仅当程序超过1000行时(经验值),OOP的溢出才能显露出来
- OOP书籍:
- 《Object Oriented Perl》Damian Conway(Manning出版社)
13.1 调用方法¶
-
Class->method(@args)
- 这种调用方式,会隐式的添加类名作为首个参数,等效于
Class::method(Class, @args)
- 这种调用方式,会隐式的添加类名作为首个参数,等效于
-
Class::Method('Class', @args)
- 该调用方法与
Class->method(@args)
等效
- 该调用方法与
13.2 继承¶
-
示例1:
-
示例2(等效示例1):
-
使用类的方式调用函数时,Perl的调用过程为:
- 构建参数列表
- 先尝试查找
Class::method
- 在
@ISA
中按序找,比如:$ISA[0]::method
、$ISA[1]::method
、... - 调用找到的方法,并将1中保存的参数列表传入
- 首个参数是类名
-
@ISA
注意事项:@ISA
中查找都是递归的,深度优先,并且从左到右进行。
13.3 调用父类方法¶
-
直接调用(不提倡):
-
SUPER调用(提倡)
第14章 测试简介¶
- 测试模块:
- 基本模块:
Test::More
- 其他模块:
Test::*
- 基本模块:
-
测试文档:
Test::Turorial
-
声明测试数量
-
测试的艺术:
- 我们需要测试代码运行中断的情况,以及代码正常工作的情况。
- 需要测试边界和中间情况。
- 如果某种情况应当抛出异常,我们也要确保测试不会有不良的副作用:传递额外的参数或则多余的参数,或则没有传递足够的参数,搞混命名参数的大小写。
-
处理浮点数:
Test::Number::Delta
-
测试有两种模式,通过以下两个环境变量区分:
RELEASE_TESTING
:作者自行的测试,为发行前的准备AUTOMATED_TESTING
:自动测试,在用户侧进行的测试
-
模块编译检查:在
BEGIN
中使用use_ok()
-
由于开启了“污染”检查模式(
perl -T
),因此PERL5LIB
这个环境变量会被忽略,需要自行指定搜索路径:- 使用
-I
指定:perl -Iblib/lib -T t/00-load.t
- 使用
blib
模块搜索:perl -Mblib -T t/00-load.t
- 使用
-
用
TODO
标注那些期望测试失败的用例,类似于备忘,该用例失败后不会作为失败处理。其中,$TODO
作为测试的标签: -
测试Pod:当安装了
Test::Pos
和Test::Pos::Coverage
时,./Build test
会对Pod进行测试。 -
测量测试覆盖率:
- 安装模块
Devel::Cover
- 执行
% ./Build testcover
来进行覆盖率测量 - 输出报告:
% cover
- 安装模块
第15章 带数据的对象¶
第x章 环境变量汇总¶
- PERL5LIB:设置搜索路径
- Linux可用
:
分隔多个搜索路径 - Windows可用
;
分隔多个搜索路径
- Linux可用
第x章 模块汇总¶
-
Cwd > 提供了获取当前路径的方法
-
Data::Dumper > 数据编组:将Perl的数据结构转为Perl代码(字节流)
-
Data::Dump
-
Data::Printer
-
File::Basename > 处理路径
-
File::Spec > 类似于
File::Basename
,但是是面向对象的。 -
File::Find > 提供一个可移植的方式高效的遍历给定文件系统的层次结构
-
List::Unit > > - first: 与grep用法一样,只是匹配一次成功就返回
-
Math::BigInt > 能够处理超出Perl本身范围的数字
-
构建工具
- h2xs
- Module::Starter
- Module::Starter::Plugin > 创建模板
- Dist::Zilla > 这个模块不但可以自动创建发行版,而且在我们修改发行版中的文件后,它还知道如何更新发行包。
-
Module::Starter > 一个好用的构建发行版本的模块,支持插件
-
Regexp::Common > > - Abigail,Perl的一位正则表达式大事,将大部分复杂的模式放入一个模块中 > - 该模块使用了tie,详情可以查看perltie文档
-
Regexp::Assemble > 该模块帮助建立高效的择一匹配
-
Spreadsheet::WriteExcel > 创建并写入Excel数据
-
HTTP::SimpleLinkChecker > URL检测
-
Try::Tiny > 异常处理模块
-
Storable > 数据编组:将Perl的数据结构转为二进制流,并且提供了深拷贝
-
IO::Handle > > - Pler实际上使用该模块实现文件句柄操作,因此,文件句柄标量实际上是
IO::Handler
模块的对象。 > - 自 Perl v5.14 之后,不必显示加载 IO::Handler模块 -
IO::File > 该模块是
IO::Handle
模块用于操作文件的子集。属于标准发型版本。 -
IO::Scalar > 如果使用的Perl是古老的版本,会出现不支持标量引用文件句柄的情况,这时候可以用该模块来支持此功能
-
IO::Pipe > 该模块是
IO::Handle
模块的前端,只要提供一条命令,就自动处理fork
和exec
命令,有点类似于C语言的popen
-
IO::Null > 创建一个空文件句柄,等效于
/dev/null
-
IO::Interactive > 返回一个文件句柄,当对该句柄进行写操作的时候,如果调用的程序是daemon则不输出,反之则输出到屏幕
-
IO::Dir
- 自 v5.6起,该模块称为Perl标准发行版的一部分,其将对目录的操作打成包,便于使用。
第x章 问题汇总¶
local
与my
的区别: >local
在后续调用的子例程中可以使用,而my
则不行,其余的都一样: > >