智能匹配操作符
Perl 5.10 版本中的智能匹配操作符(~~)会根据需要选择恰当的方式比较两端的操作数。它只用于判断操作数是否相同,在比较大小时就不能用了,老老实实的用传统的比较操作符吧。智能匹配操作符~~和前面讲的绑定操作符=~很像,不过相对来说~~操作符更强悍一些。他甚至可以代替绑定操作符。如下:
先前用绑定操作符
print "I found Fred in the name!n" if $name =~ /Fred/;
现在可以使用智能匹配操作符代替,可以完成同样的任务:
use 5.010;
print "I found Fred in the name!n" if $name ~~ /Fred/;
只能匹配操作符看到左侧是个标量,右侧是个正则表达式,于是它自己推断出应该执行模式匹配操作。这只是一个简单的例子,在处理更复杂的情况时,它就该大显身手了,如,你想在哈希%names中查找任何匹配Fred的键,如果找到就打印输出一条信息。这时你无法使用exists判断,因为它需要给定确切的键,当然你可以用foreach来遍历每个键,尝试用正则表达式匹配,跳过不匹配的,直到发现要找的键,最后用last退出:
1
2
3
4
5
6
7
|
my
$flag
=
0
;
foreach
my
$key
(
keys
%names
)
{
next
unless
$key
=~
/Fred/
;
$flag
=
$key
;
last
;
}
print
"I found a key matching 'Fred'. It was %flagn"
if
$flag
;
|
这样写很费事,但兼容性是最好的,Perl 5 版本都支持,但相对于智能匹配操作符来说就显得很麻烦了,下面使用智能匹配操作符来搞定它:
use
5.010
;
say
"I found a key matching 'Fred'"
if
%names
~~
/Fred/
;
--这里智能匹配操作符发现了左侧是一个哈希,右侧是一个正则表达式,因此它聪明的遍历%names的所有键,用给定的正则表达式逐个测试,如果找到就立即停止,并返回真。在这里的匹配和标量的匹配不太一样,它集数种不同的操作于一身,用了一个操作符就解决了各种各样的问题。
两个数组的比较(只考虑长度相同的数组)
如果按照常规的方法可以按数组索引依次遍历,取出相同位置的两个元素比较。如果比较下来两者相抵,则令计数器$equal自增1,循环结束后如果$equal和数组@names1的长度一致则说明两个数组完全相同:
1
2
3
4
5
6
7
|
my
$equal
=
0
;
foreach
my
$index
(
0
..
$#names1
)
{
last
unless
$names1
[
$index
]
eq
$names2
[
$index
]
;
$equal
++;
}
print
"The arrays have the same elements!n"
if
$equal
==
@names1
;
|
下面是使用智能匹配操作符来处理
use
5.010
;
say
"The arrays have the same elements!"
if
@names1
~~
@names2
;
--看到了吧,仍然是这么简单,就一句话,几乎算不上是程序。
使用智能匹配是,对两边操作数的顺序没有要求,倒过来写也可以。就像代数中的“交换律”
1
2
3
|
use
5.010
;
say
"I found a name matching 'Fred'"
if
$name
~~
/Fred/
;
say
"I found a name matching 'Fred'"
if
/Fred/
~~
$name
;
|
智能匹配操作处理方式
下表是智能匹配操作符对不同操作数的处理方式
例子 |
匹配方式 |
%a ~~ %b |
哈希的键是否一致 |
%a ~~ @b |
至少 %a 中的一个键在列表@b中 |
%a ~~ /Fred/ |
至少一个键匹配给定的模式 |
%a ~~ 'Fred' |
哈希中某一指定键$a{Fred}是否存在 $a{Fred} |
@a ~~ @b |
数组是否相同 |
@a ~~ /Fred/ |
有一个元素匹配给定的模式 |
@a ~~ 123 |
至少有一个元素转化为数字后是123 |
@a ~~ 'Fred' |
至少有一个元素转化为字符串后是'Fred' |
$name ~~ undef |
$name确实尚未定义 |
$name ~~ /Fred/ |
模式匹配 |
123 ~~ '123.0' |
数字和字符串是否相等 |
'Fred' ~~ 'Fred' |
字符串是否相同 |
123 ~~ 456 |
数字是否相等 |
当使用只能匹配操作符时,Perl会按此表自上而下查看使用的操作数。先找到哪一种匹配就选择对应的操作。
注意:当两个标量以字符串的形式存储像123,123.12这些数字的时候,使用智能匹配操作符进行比对时会默认将这些字符串转换为数字,然后在进行比对。
given语句
Perl中的given-when控制结构能够根据given的参数,执行某个条件对应的语句块,其与C语言中的switch语句类似。只不过更具有Perl的色彩。看看下面的例子,其从命令行中取出第一个参数,$ARGV[0],然后依次走一遍when条件判断,看是否找到了Fred。每个when语句对应不同的处理方式,判断的条件从最宽松的开始测试:
1
2
3
4
5
6
7
|
use
5.010
;
given
(
$ARGV
[
0
]
)
{
when
(
/fred/i
)
{ say
'Name has fred in it'
}
when
(
/^Fred/
)
{ say
'Name starts with Fred'
}
when
(
'Fred'
)
{ say
'Name is Fred'
}
default
{ say
"I don't see a Fred"
}
}
|
--given会将参数化为$_,每个when条件都尝试用智能匹配对$_进行测试,实际上可以写成如下形式,这就清除多了:
1
2
3
4
5
6
7
|
use
5.010
;
given
(
$ARGV
[
0
]
)
{
when
(
$_
~~
/fred/i
)
{ say
'Name has fred in it'
}
when
(
$_
~~
/^Fred/
)
{ say
'Name starts with Fred'
}
when
(
$_
~~
'Fred'
)
{ say
'Name is Fred'
}
default
{ say
"I don't see a Fred"
}
}
|
--如果$_不能满足任何when条件,perl就会执行default语句块。下面为运行结果:
$ perl5.10.0 switch.pl Fred
Name has fred in it
$ perl5.10.0 switch.pl Frederick
Name has fred in it
$ perl5.10.0 switch.pl Barney
I don't see a Fred
$ perl5.10.0 switch.pl Alfred
Name has fred in it
看到这里你也许会说,Perl中的if-elsif-else语句不是一样可以完成这个例子吗,为什么还要用given-when语句呢,它还有存在的必要吗?实际上他们两个语句最大的不同在于given-when可以在满足某个条件的基础上,继续测试其他的条件,但if-elsif-else一旦满足了某个条件,就只能执行对应的那个语句块。实际上前面的例子可以写成如下的方式:
1
2
3
4
5
6
7
|
use
5.010
;
given
(
$ARGV
[
0
]
)
{
when
(
$_
~~
/fred/i
)
{ say
'Name has fred in it'
; break
}
when
(
$_
~~
/^Fred/
)
{ say
'Name starts with Fred'
; break
}
when
(
$_
~~
'Fred'
)
{ say
'Name is Fred'
; break
}
default
{ say
"I don't see a Fred"
; break
}
}
|
--按照这种写法,因为第一条测试语句如果传来的参数匹配/fred/i,后面的所有语句就没有机会执行了,这时候就会直接跳出控制结构。如果在when语句块的末尾使用continue,Perl就会尝试执行后续的when语句了,这也是if-elsif-else语句块力不能及的地方。当另一个when的条件满足时,会执行对应语句块。在每个when语句块的末尾写上continue,就意味着所有的条件判断都会执行:
1
2
3
4
5
6
7
|
use
5.010
;
given
(
$ARGV
[
0
]
)
{
when
(
$_
~~
/fred/i
)
{ say
'Name has fred in it'
;
continue
}
when
(
$_
~~
/^Fred/
)
{ say
'Name starts with Fred'
;
continue
}
when
(
$_
~~
'Fred'
)
{ say
'Name is Fred'
;
continue
}
# 注意!
default
{ say
"I don't see a Fred"
}
}
|
--实际上这样写是有问题的,这里的default总是会运行
$ perl5.10.0 switch.pl Alfred
Name has fred in it
I don't see a Fred
--default块相当于一个测试条件永远为真的when语句。如果在default之前的when语句使用了continue,Per就会继续执行default语句。因此可以说default就是一个特殊的when:
1
2
3
4
5
6
7
|
use
5.010
;
given
(
$ARGV
[
0
]
)
{
when
(
$_
~~
/fred/i
)
{ say
'Name has fred in it'
;
continue
}
when
(
$_
~~
/^Fred/
)
{ say
'Name starts with Fred'
;
continue
}
when
(
$_
~~
'Fred'
)
{ say
'Name is Fred'
;
continue
}
# 注意!
when
(
1
==
1
)
{ say
"I don't see a Fred"
}
# 相当于default语句块
}
|
要解决这个问题,只要拿掉最后一个when的continue就可以了,改写成如下形式:
1
2
3
4
5
6
7
|
use
5.010
;
given
(
$ARGV
[
0
]
)
{
when
(
$_
~~
/fred/i
)
{ say
'Name has fred in it'
;
continue
}
when
(
$_
~~
/^Fred/
)
{ say
'Name starts with Fred'
;
continue
}
when
(
$_
~~
'Fred'
)
{ say
'Name is Fred'
; break
}
# 现在就对了!
when
(
1
==
1
)
{ say
"I don't see a Fred"
}
# 这里的when(1==1)可改写成default
}
|
多个项目的when匹配
有些时候需要遍历很多元素,但given只能一次接受一个参数,当然可以将given语句放到foreach里面循环测试。比如要遍历@names,依次将各元素赋值到$name,然后再用given:
1
2
3
4
5
6
|
use
5.010
;
foreach
my
$name
(
@names
)
{
given
(
$name
)
{
...
}
}
|
如果使用given-when语句,想必大家首先想到的就是上面的方法,实际上要遍历多个元素时就就不必使用given了,使用foreach的简写形式,让它给当前正在遍历的元素起个化名$_。此外若要用智能匹配,当前元素就只能是$_。
use
5.010
;
foreach
(
@names
)
{
# 不要使用命名变量!
when
(
/fred/i
)
{ say
'Name has fred in it'
;
continue
}
when
(
/^Fred/
)
{ say
'Name starts with Fred'
;
continue
}
when
(
'Fred'
)
{ say
'Name is Fred'
;
}
default
{ say
"I don't see a Fred"
}
}
一般在遍历的时候,总希望可以看到当前的工作状态。可以在foreach语句块中写上其他的语句,比如say:
1
2
3
4
5
6
7
8
9
|
use
5.010
;
foreach
(
@names
)
{
# 不要使用命名变量!
say
"nProcessing $_"
;
when
(
/fred/i
)
{ say
'Name has fred in it'
;
continue
}
when
(
/^Fred/
)
{ say
'Name starts with Fred'
;
continue
}
when
(
'Fred'
)
{ say
'Name is Fred'
;
}
say
"Moving on to default..."
;
default
{ say
"I don't see a Fred"
}
}
|
本章练习
1、用given-when结构写一个程序,根据输入的数字,如果能被3整除,就打印“Fizz”,如果能被5整除就打印“Bin”,如果能被7整除就打印“Sausag”。比如输入数字15,程序打印“Fizz”和“Bin”
1
2
3
4
5
6
7
8
9
10
11
|
#!/usr/binperl -w
use strict
;
use
5.010
;
given
(
$ARGV
[
0
]
)
{
when
(
not
$_
%
3
)
{say
'Fizz'
;continue
}
when
(
not
$_
%
5
)
{say
'Bin'
;continue
}
when
(
not
$_
%
7
)
{say
'sausage'
}
}
|
# perl ex_13_1.pl 3
Fizz
# perl ex_13_1.pl 15
Fizz
Bin
# perl ex_13_1.pl 105
Fizz
Bin
sausage
2、使用foreach-when写个程序,要求从命令行遍历某个目录下的文件,并报告每个文件的可读、可写和可执行属性状态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
#!/usr/bin/perl -w
use strict
;
use
5.010
;
my
$SourcePath
=
$ARGV
[
0
]
;
opendir MYDIR
,
$SourcePath
or
die
"can't open $SourcePath :$!"
;
chdir
$SourcePath
;
foreach
(
readdir MYDIR
)
{
say
"e[32m==============================e[0m"
;
when
(
-x
$_
)
{say
"$SourcePath/$_ e[1;32mexecutable!e[0m"
;continue
}
when
(
-r
$_
)
{say
"$SourcePath/$_ e[1;35mreadable !e[0m"
;continue
}
when
(
-w
$_
)
{say
"$SourcePath/$_ e[1;33mwritable !e[0m"
}
}
closedir MYDIR
|
--我这里从命令行读取用户输入的目录,如果目录不存在,报错退出,否则切换当前路径到该目录下,依次遍历目录中的文件,使用when语句判断文件可读写情况。如果是.和..则直接略过,在测试文件属性时用到了前面介绍的一个技巧,即_也就是下划线,它代表了上一个测试的文件。
运行结果:
