美文网首页程序员Perl小推车
第三章 Perl语言(三)-流程控制、标量、数组

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

作者: 可以没名字吗 | 来源:发表于2016-02-17 18:06 被阅读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)
    

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/rafmkttx.html