程序员Perl小推车

第三章 Perl语言(三)-流程控制、标量、数组

2016-02-17  本文已影响375人  可以没名字吗

流程控制

最基础的流程就是从开始到结束,一行一行的执行:

say 'At start';
say 'In middle';
say 'At end';

条件分支

if指令会在条件表达式为真时执行相应的动作:

say 'Hello, Bob!' if $name eq 'Bob';

这个是后缀形式,在执行简单表达式时非常有用。还有一种是当条件表达式为真时执行块的形式:

if ($name eq 'Bob')
{
say 'Hello, Bob!';
found_bob();
}

此时条件表达式的****圆括号不可省略,且要有块的形式(那就是大括号也不能省)。****

条件表达式可以由多项组成:

if ($name eq 'Bob' && not greeted_bob())
{
say 'Hello, Bob!';
found_bob();
}

使用后缀形式,添加括号增加可读性:

greet_bob() if ($name eq 'Bob' && not greeted_bob());

unless指令的意思与if相反,就是当条件为假时执行对应动作,它的用法和if一样。

say "You're not Bob!" unless $name eq 'Bob';

unless (is_leap_year() and is_full_moon())
{
frolic();
gambol();
}

if和unless都支持else指令。执行条件与主句条件相反。

if ($name eq 'Bob')
{
say 'Hi, Bob!';
greet_user();
}
else
{
say "I don't know you.";
shun_user();
}



unless ($name eq 'Bob')
{
say "I don't know you.";
shun_user();
}
else
{
say 'Hi, Bob!';
greet_user();
}

unless与if表达的意思相反,可以相互转换,根据自己的喜好,挑个顺手的用就好。

如果你有很多的条件要逐一比对,你可以在if中使用elsif指令:

if ($name eq 'Bob')
{
say 'Hi, Bob!';
greet_user();
}
elsif ($name eq 'Jim')
{
say 'Hi, Jim!';
greet_user();
}
else
{
say "You're not my uncle.";
shun_user();
}

下面这个会报错,没有(else if)这种语法:

if ($name eq 'Rick')
{
say 'Hi, cousin!';
}
# warning; syntax error
else if ($name eq 'Kristen')
{
say 'Hi, cousin-in-law!';
}

三目操作符

三目操作符(? :)可以根据给定的条件选择执行表达式.条件表达式为真时执行表达式1,条件表达式为假时执行表达式2:
条件表达式 ? 表达式1 : 表达式2

my $time_suffix = after_noon($time)
? 'afternoon'
: 'morning';

这里有个例子展示了三目操作符不仅能用于值还能用于变量:
push @{ rand() > 0.5 ? @red_team : @blue_team }, Player->new;

****短路****
Perl在遇到复杂的条件表达式时具有短路行为,原则就是:当Perl能确定整个多项表达式的真假时就不会继续去计算每一个子项。

say 'Both true!' if ok( 1, 'subexpression one' )
&& ok( 1, 'subexpression two' );
done_testing();

ok 1 - subexpression one
ok 2 - subexpression two
Both true!

上例中2个子项都执行了,下面这个例子中只需要计算第一项就够了:

say 'Both true!' if ok( 0, 'subexpression one' )
&& ok( 1, 'subexpression two' );

not ok 1 - subexpression one

逻辑运算中有个特征:与运算中,有一项为假时就能确定整个值为假;或运算中,有一项为真就能确定整个值为真。

条件指令的语境

条件指令:if,unless,还有三元条件操作符提供的都是****布尔语境****。
比较操作符,如eq,==,ne,and, !=也都产生的是布尔值。
Perl没有单独的真值和单独的假值。任何数值为0的数字都是假,如0, 0.0, 0e0, 0x0。字符串'0'和空字符''也是假值。但是字符串'0.0', '0e0'不是假值。空列表和undef都是假值。空数组和空哈希在标量环境中返回数字0,所以在布尔语境中也是假值。数组中只要拥有一个元素(哪怕是undef),那么这个数组在布尔语境中也是真值。类似的,哈希中只要有一个元素,即使键和值是undef,也是真值。

循环

foreach (1 .. 10)
{
say "$_ * $_ = ", $_ * $_;
}

跟if和unless一样,也支持后缀:

say "$_ * $_ = ", $_ * $_ for 1 .. 10;

也可以显式使用自定义的变量:

for my $i (1 .. 10)
{
say "$i * $i = ", $i * $i;
}

注意:改变变量的值,会修改原始值:

my @nums = 1 .. 10;
$_ **= 2 for @nums;
#@nums数值变了

所以当修改常量时就会报错:

$_++ and say for qw( Huex Dewex Louid );

****迭代和范围界定****
循环中可能会出现这样一个问题:变量在内部嵌套中是可见的,所以当使用默认变量$_,并且未对它做保护时就会出问题。此类问题非常隐蔽:

for (@values)    #默认使用$_
{
topic_mangler();
}
sub topic_mangler
{
s/foo/bar/;    # $_继续有效,修改$_就修改连原始数组!
}

所以:使用自定义的有名字的变量总是一个更好的选择。

****C风格循环****

my $i = 'pig';
for ($i = 0; $i <= 10; $i += 2)
{
say "$i * $i = ", $i * $i;
}

****while和until****
while,当条件为真时执行,直到条件为假。until与while相反,条件假时执行,直到条件为真。(可以互换,挑自己顺手的用)

while (1) { ... }    #无限循环


while (my $value = shift @values)
{
say $value;
}
此循环有个隐秘的问题:当元素值为假值时就会退出循环,而并不一定是耗尽了整个数组。
do
{
say 'What is your name?';
my $name = <>;
chomp $name;
say "Hello, $name!" if $name;
} until (eof);

循环至少执行一次。

****循环嵌套****
循环嵌套就是循环内有循环:

for my $suit (@suits) {
for my $values (@card_values) { ... }
}

****循环控制****
next-立即进入下一循环:

while (<$fh>)
{
next if /\A#/;
...
}

last-立即结束循环。

while (<$fh>)
{
next if /\A#/;
last if /\A__END__/;    #看到文件结尾标志立即结束    
...
}

redo-重新进入当前迭代(无需评估循环条件)。

while (<$fh>)
{
print;
redo;    #不能这样写,因为这样会不停重复循环    
...    #这后面的不会被执行到
}

循环嵌套时,可能不容易让人理清楚结构,这时可以使用标签:

LINE:
while (<$fh>)
{
chomp;
PREFIX:
for my $prefix (@prefixes)
{
next LINE unless $prefix;
say "$prefix: $_";
# next PREFIX is implicit here
}
}

****continue语句****
continue的使用非常少见,使用时一般和while, until, when, for循环一同使用。它将始终被执行之前的条件再次进行计算,就像C语言的一个for循环中的第三部分。

while ($i < 10 ) {
next unless $i % 2;
say $i;
}
continue {
say 'Continuing...';
$i++;
}

Continuing...
1
Continuing...
Continuing...
3
Continuing...
Continuing...
5
Continuing...
Continuing...
7
Continuing...
Continuing...
9
Continuing...


注意:如果因为last或者redo指令离开当前迭代,那么continue 块当次不会被执行。

****switch****
该功能是实验性的,暂不介绍。

****尾部调用****
当函数中最后一个表达式调用函数时就是尾部调用。被调用的函数返回值将成为主函数的返回值。

sub log_and_greet_person
{
my $name = shift;
log( "Greeting $name" );
return greet_person( $name );
}

标量

标量,一个单一的、离散的值。标量是Perl的基本数据类型。它可以是一个字符串,一个整型数字,浮点数字,一个文件句柄,或者是一个引用——但肯定是一个单一的值。标量变量可以是词法作用域,包作用域,也可以是全局作用域。标量变量总是使用美元($)记号。

标量和类型

my $value;
$value = 123.456;
$value = 77;
$value = "I am Chuck's big toe.";
$value = Store::IceCream->new;

不同语境下会发生类型转换,Perl中的数字和字符串可以根据语境自动转换,比如将数字视为字符串:

my $zip_code = 97123;    #数字
my $city_state_zip = 'Hillsboro, Oregon' . ' ' . $zip_code;    #字符串

也可以对字符串使用++操作符:

my $call_sign = 'KBMIU';
my $next_sign = ++$call_sign;    # 返回新值,KBMIV
my $curr_sign = $call_sign++;    # 返回旧值,然后再更新


my $new_sign = $call_sign + 1;   # 这个可与上面2个效果不一样,这个值是1

++操作符用在数字上的行为是加一;但用在字符串上的行为是:a到b,z到aa;ZZ9到AAA0, ZZ09 到ZZ10,这样增长。

引用类型也能进行转换,在字符串语境下计算引用时,引用会返回字符串;在数字语境下会返回数字:

my $authors = [qw( Pratchett Vinge Conway )];
my $stringy_ref = '' . $authors;    #字符串
my $numeric_ref = 0 + $authors;  #数字

数组

数组,Perl语言的内置数据结构,包含0个或多个标量值。你可以通过索引访问各个数组成员,并且可以随意添加和删除元素,数组变大和缩小都是自动处理的。@记号表示一个数组。
声明一个数组:

my @items;

数组元素

使用标量记号($)来访问一个数字元素:

# @cats表示一堆猫,现在要访问第一猫
my $first_cat = $cats[0];

# 标量语境返回元素个数
my $num_cats = @cats;

# 也是标量语境
say 'I have ' . @cats . ' cats!';
# addition
my $num_animals = @cats + @dogs + @fish;

# 布尔语境
say 'Yep, a cat owner!' if @cats;

#元素索引从0开始
my $first_index = 0;
my $last_index = @cats - 1;    # 或者 my $last_index = $#cats;
say "My first cat has an index of $first_index, "
. "and my last cat has an index of $last_index."


#还支持从最后面开始定位,-1是最后一个元素,-2是倒数第二个……,不过要注意数量要够
my $last_cat = $cats[-1];
my $second_to_last_cat = $cats[-2];

数组赋值:

my @cats;
$cats[3] = 'Jack';
$cats[2] = 'Tuxedo';
$cats[0] = 'Daisy';
$cats[1] = 'Petunia';
$cats[4] = 'Brad';
$cats[5] = 'Choco';

my @cats = ( 'Daisy', 'Petunia', 'Tuxedo', ... );

数组操作

  • push 往数组末尾追加元素,一个或多个
  • pop 从数组末尾弹出一个元素
my @meals;
# what is there to eat?
push @meals, qw( hamburgers pizza lasagna turnip );
# ... but your nephew hates vegetables
pop @meals;
  • shift 从数组开头弹出一个元素
  • unshift 在数组开头插入元素,一个或多个
# expand our culinary horizons
unshift @meals, qw( tofu spanakopita taquitos );
# rethink that whole soy idea
shift @meals;
  • splice 可以删除数组中某段元素,也可以往数组中覆盖或插入元素
my ($winner, $runnerup) = splice @finalists, 0, 2;
# or
my $winner = shift @finalists;
my $runnerup = shift @finalists;
  • each 迭代返回元素的索引和值
while (my ($position, $title) = each @bookshelf) {
say "#$position: $title";
}

数组切片

标量每次只能访问数组中的单个元素,而有个叫数组切片的结构可以让你访问数组中的一系列(0个或多个)元素。
我们已经知道了,表示多个元素需要用到记号@:

my @youngest_cats = @cats[-1, -2];
my @oldest_cats = @cats[0 .. 2];
my @selected_cats = @cats[ @indexes ];

@users[ @replace_indices ] = @replace_users;

数组切片的索引为列表语境:

# function called in list context
my @hungry_cats = @cats[ get_cat_indices() ];


# single-element array slice; list context
@cats[-1] = get_more_cats();
# single-element array access; scalar context
$cats[-1] = get_more_cats();

****数组和语境****
在列表语境中,数组展平为列表。

my @cats = qw( Daisy Petunia Tuxedo Brad Jack Choco );
my @dogs = qw( Rodney Lucky Rosie );
take_pets_to_vet( @cats, @dogs );
sub take_pets_to_vet
{
# BUGGY: do not use!
my (@cats, @dogs) = @_;
...
}

扁平化有时也会造成混淆:

# creates a single array, not an array of arrays
my @numbers = (1 .. 10, (11 .. 20, (21 .. 30)));

# parentheses do not create lists
my @numbers = ( 1 .. 10, 11 .. 20, 21 .. 30 );
# creates a single array, not an array of arrays
my @numbers = 1 .. 30;

****数组插值****
数组在内插到双引号包围的字符串中时,会返回这样一个字符串:由$"的值将每个元素连接起来。
$"的值默认是一个空格。

my @alphabet = 'a' .. 'z';
say "[@alphabet]";

[a b c d e f g h i j k l m
n o p q r s t u v w x y z]


自定义$" 以便输出特定形式的结果:
local $" = ')(';
say "(@sweet_treats)";
(pie)(cake)(doughnuts)(cookies)(cinnamon roll)
上一篇下一篇

猜你喜欢

热点阅读