在线咨询
eetop公众号 创芯大讲堂 创芯人才网
切换到宽版

EETOP 创芯网论坛 (原名:电子顶级开发网)

手机号码,快捷登录

手机号码,快捷登录

找回密码

  登录   注册  

快捷导航
搜帖子
查看: 8282|回复: 11

[讨论] perl入门

[复制链接]
发表于 2011-11-3 14:09:11 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?注册

x
Perl 免费提供许多数据结构,这些数据结构在其他编程语言里是需要你自己制作的。比如那些计算机科学的新芽们都需要学习的堆栈和队列在 Perl 里都只是数组。在你 push 和 pop(或者 shift 和 unshift)一个数组的时候,它就是一个堆栈;在你 push 和 shift(或者 unshift 和 pop)一个数组的时候,它就是一个队列。
下面是如何把一个两维数组放在一起的方法:


   # 给一个数组赋予一个数组引用列表。
   @Aoa = (
      ["fred", "barney" ],
      ["george", "jane", "elroy" ],
      ["homer", "marge", "bart" ],
   );

   print $AoA[2][1];      # 打印 "marge"
整个列表都封装在圆括弧里,而不是花括弧里,因为你是在给一个列表赋值而不是给引用赋值。如果你想要一个指向数组的引用,那么你要使用方括弧:


# 创建一个指向一个数组的数组的引用。
$ref_to_AoA = [
[ "fred", "barney", "pebbles", "bamm bamm", "dino", ],
[ "homer", "bart", "marge", "maggie", ],
[ "george", "jane", "elroy", "judy", ],
];

print $ref_to_AoA->[2][3];      # 打印 "judy"
请记住在每一对相邻的花括弧或方括弧之间有一个隐含的 ->。因此下面两行:


   $AoA[2][3]
   $ref_to_AoA->[2][3]

等效于下面两行:

   $AoA[2]->[3]
   $ref_to_AoA->[2][3]

大的列表赋值是创建大的固定数据结构的好方法,但是如果你想运行时计算每个元素,或者是一块一块地制作这些结构的时候该怎么办呢?

让我们从一个文件里读入一个数据结构。我们将假设它是一个简单平面文本文件,它的每一行是结构的一个行,并且每行包含由空白分隔的元素。下面是处理的方法:(注:在这里和其他章节一样,我们忽略那些通常你要放进去的 my 声明。在这个例子里,你通常写 my @tmp = split。)


while (<>) {
        @tmp = split;         # 把元素分裂成一个数组
        push @AoA, [ @tmp ];      # 向 @AoA 中增加一个匿名数组引用
}当然,你不必命名那个临时的数组,因此你还可以说:


while(<>){
        push @AoA, [ split ];
}如果你想要一个指向一个数组的数组的引用,你可以这么做:


while (<>){
        push @ref_to_AoA, [ split ];
}这些例子都向数组的数组增加新的行。那么如何增加新的列呢?如果你只是对付两维数组,通常更简单的方法是使用简单的赋值:(注:和前面的临时赋值一样,我们在这里已经简化了;在这一章的循环在实际的代码中应该写做 my $x。)


for $x (0..9){               # 对每一行...
        for $y (0..9) {            # 对每一列...
                $AoA[$x][$y] = func($x, $y);   # ...设置调用
        }
}

for $x (0..9) {               # 对每一行...
        $ref_to_AoA->[$x][3] = func2($x);      # ...设置第四行
}至于你给元素赋值的顺序如何则没有什么关系,而且 @AoA 的脚标元素是否存在也没有什么关系;Perl 会很开心地为你创建它们,并且把中间的元素根据需要设置为未定义值。(如果有必要,Perl 甚至会为你在 $ref_to_AoA 中创建最初的引用。)如果你只是想附加一行,你就得做得更奇妙一些:


   # 向一个已经存在的行中附加一个新列
   push @{ $AoA[0] }, "wilma", "betty";

请注意下面这些无法运转:


   push $AoA[0], "wilma", "betty";         # 错误!

上面的东西甚至连编译都过不了,因为给 push 的参数必须是一个真正的数组,而不只是一个指向一个数组的引用。因此,第一个参数绝对必须以 @ 字符开头。而跟在 @ 后面的东西则可以忽略一些。

如果你想追踪脚标,你可以这么做:


   for $i (0..$#AoA) {
      print "row $i is: @{$AoA[$i]}\n";
   }
取得数组长度和深度

# 如果键字是标识符,我们通常省略引号
%HoA = (
flintstones    => [ "fred", "barney" ],
jetsons   => [ "george", "jane", "elroy" ],
simpsons   => [ "homer", "marge", "bart" ],
);要向散列增加另外一个数组,你可以简单地说:


   $HoA{teletubbies} = [ "tinky winky", "dipsy", "laa-laa", "po" ];


$AoA[$i] = [ @array ];      # 最安全,有时候最快
$AoA[$i] = \@array;         # 快速但危险,取决于数组的自有性
@{ $AoA[$i] } = @array;      # 有点危险
   
下面是填充一个数组的散列的技巧。从下面格式的文件中读取出来:


   flintsotnes:    fred barney wilma dino
   jetsons:   george jane elroy
   simpsons:   homer marge bart
你可以用下列两个循环之一:


while( <> ) {
        next unless s/^(.*?):\S*//;
        $HoA{$1} = [ split ];
}

while ( $line = <> ) {
        ($who, $rest) = split /:\S*/, $line, 2;
        @fields = spilt ' ', $rest;
        $HoA{$who} = [ @fields ];
}


你可以打印所有这些家族,方法是遍历该散列的所有键字:


for $family ( keys %HoA ){
        print "$family: @{ $HoA{$family} }\n";
}我们稍微多做一些努力,你就可以一样追加数组索引:


for $family ( keys %HoA ) {
        print "$family: ";
        for $i ( 0 .. $#{ $HoA{$family} }) {
                print " $i = $HoA{$family}[$i]";
        }
        print "\n";
}或者通过以数组拥有的元素个数对它们排序:


for $family ( sort { @{$HoA{$b}} <=> @{$HoA{$a}} } keys %HoA ){
        print "$family: @{ $HoA{$family}}\n";
}或者甚至可以是以元素的个数对数组排序然后以元素的 ASCII 码顺序进行排序(准确地说是 utf8 的顺序):


# 打印以成员个数和名字排序的所有内容
for $family ( sort { @{$HoA{$b}} <=> @{$HoA{$a}}} keys %HoA) {
        print "$family: ", join(", ", sort @{$HoA{$family}}), "\n";
}

要把某个键字/数值对变成大写,对该元素应用一个替换:


   $HoH{jetsons}{'his boy'} =~ s/(\w)/\u$1/;

在使用 Perl 书写一个复杂的应用或者网络服务的时候,你可能需要给你的用户制作一大堆命令供他们使用。这样的程序可能有象下面这样的代码来检查用户的选择,然后采取相应的动作:


if    ($cmd =~ /^exit$/i)     { exit }
elsif ($cmd =~ /^help$/i)     { show_help() }
elsif ($cmd =~ /^watch$/i)    { $watch = 1 }
elsif ($cmd =~ /^mail$/i)     { mail_msg($msg) }
elsif ($cmd =~ /^edit$/i)     { $edited++; editmsg($msg); }
elsif ($cmd =~ /^delete$/i)   { confirm_kill() }
else {
    warn "Unknown command: `$cmd'; Try `help' next time\n";
}
你还可以在你的数据结构里保存指向函数的引用,就象你可以存储指向数组或者散列的引用一样:



%HoF = (                           # Compose a hash of functions
    exit    =>  sub { exit },
    help    =>  \&show_help,
    watch   =>  sub { $watch = 1 },
    mail    =>  sub { mail_msg($msg) },
    edit    =>  sub { $edited++; editmsg($msg); },
    delete  =>  \&confirm_kill,
);

if   ($HoF{lc $cmd}) { $HoF{lc $cmd}->() }   # Call function
else { warn "Unknown command: `$cmd'; Try `help' next time\n" }

$rec = {
        TEXT       => $string,
        SEQUENCE    => [ @old_values ],
        LOOKUP    => { %some_table },
        THATCODE   => sub { $_[0] ** $_[1] },
        HANDLE   => \*STDOUT,
};

如果你想保存你的数据结构以便以后用于其他程序,那么你有很多方法可以用。最简单的方法就是使用 Perl 的 Data:umper 模块,它把一个(可能是自参考的)数据结构变成一个字串,你可以把这个字串保存在程序外部,以后用 eval 或者 do 重新组成:


   use Data:umper;
   $Data:umper:urity = 1;      # 因为 %TV 是自参考的
   open (FILE, "> tvinfo.perldata")    or die "can't open tvinfo: $!";
   print FILE Data::Dumper->Dump([\%TV], ['*TV']);
   close FILE         or die "can't close tvinfo: $!";

其他的程序(或者同一个程序)可以稍后从文件里把它读回来:


   open (FILE, "< tvinfo.perldata")   or die "can't open tvinfo: $!";
   undef $/;            # 一次把整个文件读取进来
   eval ;            # 重新创建 %TV
   die "can't recreate tv data from tvinfo.perldata: $@" if $@;
   close FILE         or die "can't close tvinfo: $!";
   print $TV{simpsons}{members}[2]{age};

或者简单的是:


   do "tvinfo.perldata"      or die "can't recreate tvinfo: $! $@";
   print $TV{simpsons}{members}[2]{age};

还有许多其他的解决方法可以用,它们的存储格式的范围从打包的二进制(非常快)到 XML(互换性非常好)。检查一下靠近你的 CPAN 镜象!

老的包分隔符还是一个单引号,因此在老的 Perl 程序里你会看到象 $main'sail 和 $somepack'horse 这样的变量。不过,双冒号是现在的优选的分隔符,部分原因是因为它更具有可读性,另一部分原因是它更容易被 emacs 的宏读取。而且这样表示也令 C++ 程序员觉得明白自己在做什么——相比之下,用单引号的时候就能让 Ada 的程序员知道自己在做什么。因为出于向下兼容的考虑,Perl 仍然支持老风格的语法,所以如果你试图使用象 "This is $owner's house" 这样的字串,那么你实际上就是在访问 $owner::s;也就是说,在包 owner 里的 $s 变量,这可能并不是你想要的。你可以用花括弧来消除歧义,就象 "This is ${owner}'s house"。

在符号表的散列里,每一对键字/数值对都把一个变量名字和它的数值匹配起来。键字是符号标识符,而数值则是对应的类型团。因此如果你使用 *NAME 表示法,那么你实际上只在访问散列里的一个数值,该数值保存当前包的符号表。实际上,下面的东西有(几乎)一样的效果:


   *sym = *main::variable;
   *sym = $main::{"variable"};

第一种形式更高效是因为 main 符号表是在编译时被访问的。而且它还会在该名字的类型团不存在的时候创建一个新的,但是第二种则不会。

因为包是散列,因此你可以找出该包的键字然后获取所有包中的变量。因此该散列的数值都是类型团,你可以用好几种方法解引用。比如:


foreach $symname (sort keys %main: {
        local *sym = $main::{$symname};
        print "\$$symname is defined\n" if defined $sym;
        print "\@$symname is nonnull\n" if   @sym;
        print "\%$symname is nonnull\n" if   %sym;
}

perl里面是没有class构建函数的,如果要使用可以把构建函数通过bless实现。
package Critter;
   sub spawn { bless {}; }

或者略微更明确地拼写:


package Critter;
sub spawn {
        my $self = {};      # 指向一个空的匿名散列
        bless $self, "Critter";   # 把那个散列作成一个 Critter 对象
        return $self;      # 返回新生成的 Critter
}有了那个定义,下面就是我们如何创建一个 Critter 对象了:


   $pet = Critter->spawn;



sub new {
        my $invocant = shift;
        my $class = ref($invocant) || $invocant;
        my $self = {
                color => "bay",
                legs => 4,
                owner => undef,
                @_,         # 覆盖以前的属性
        };
        
        return bless $self, $class;
}

$ed    = Horse->new;                          # 四腿湾马
$stallion = Horse->new(color => "black"); # 四腿黑马当把这个 Horse 构造器当作实例方法使用的时候,它忽略它的调用者现有的属性。你可以设计第二个构造器,把它当作实例方法来调用,如果你设计得合理,那你就可以使用来自调用对象的数值作为新生成的对象的缺省值:


$steed = Horse->new(color => "dun");
$foal = $steed->clone(owner => "EquuGen Guild, Ltd.");

sub clone {
        my $model = shift;
        my $self = $model->new(%$model, @_);
        return $self;         # 前面被 ->new 赐福过了
}
类继承的例子
   package Horse;
   our @ISA = "Critter";

所有问题中最大的失误就是忘记 use warnings,它可以标识非常多的错误。第二大的失误是忘记在合适的时候使用 use strict。当你的程序开始变大的时候(肯定会),这两个用法可以节约你好几个小时痛苦的调试。另外一个错误是忘记参考联机 FAQ。假设你想知道 Perl 是否有一个 round 函数,你可以试着找以下 FAQ 列表:

%perlfaq round

除了这些“元错误”之外,还有好些编程陷阱。有些陷阱几乎是每个人都掉进去过,而有些陷阱是只有那些来自不同文化的人才能掉进去,因为他们有不同的做事方法。我们将在随后各节中展开这些内容。

误把 = 当作 eq 或者误把 当作 ne。== 和 = 是测试数字的。其他两个操作符是测试字串的。字串“123”和“123.00”作为数字时是相等的,而作为字串时是不等的。同样,任何非数字字串在数字上都等于零。除非你是在处理数字,否则你几乎总是要用字串比较的。

在正则表达式之间不保存 $1,$2 等等。请注意每个新的 m/atch/ 或者s/ubsti/tution/ 将设置(或者清零,或者破坏)你的 $1,$2 ... 变量,以及 $`,$&,和 $'。一个保存它们的方法是在列表环境里计算匹配,象:
           my ($one, $two) = /(\w+) (\w+)/;

记住许多操作符在列表环境里和在标量环境里的行为是不同的。比如:

      ($x) = (4,5,6);      # 列表环境;$x 设置为 4
      $x = (4,5,6);      # 标量环境;$x 设置为 6

      @a = (4,5,6);
      $x = @a;      # 标量环境;$x 设置为 3(数组列表)


还要记住只有在文件读取是 while 循环中的唯一的条件的时候,尖角操作符读取的数据才存储在 $_:

      while() {}      # 数据赋予 $_。
      ;         # 读取数据并丢弃之!

如果你需要 =~ 的时候不要使用 =;这两个构造区别相当大:

       $x = /foo/;      # 在 $_ 中搜索“foo”,把结果放在 $x
      $x =~ /foo/;      # 在 $x 中搜索“foo”,抛弃结果

可以的话应该用 my 定义局部变量。使用 local 只是给全局变量一个临时值,这样也让你必须面对不可预见的动态范围的副作用。
Perl 里没有分支语句。(不过我们很容易现场制作一个;参阅第四章里的“光块”和“分支结构”。)

在 Perl 里,变量以 $,@,或者 % 开头。

注释以 # 开头而不是 /*。

你无法获取任何东西的地址,尽管 Perl 里类似的操作是反斜杠,它创建一个引用。

ARGV 必须大写。$ARGV[0] 是 C 的 argv[1],而 C 的 argv[0] 是 $0。

象 link,unlink,和 rename 这样的系统调用成功时返回真,而不是 0。

%SIG 里的信号句柄处理信号名,而不是数字。
用 next if 尽早排除普通的分支。对于简单的正则表达式,优化器喜欢这样做。但它只是对避免不必要的工作有帮助。你通常可以在 split 或者 chop 之前抛弃注释行和空行:

      while (<> ) {
         next if /^#/;
         next if /^$/;
         chop;
         @piggies = split(/, /);
         ...
      }

避免在长的字串上使用 syubstr,特别是在该字串包含 UTF-8 的时候。在字串的开头用 substr 没有问题,并且对于某些任务来说,你可以让 substr 总是处理字串的开头:一边使用四个参数的 substr,一边“吃掉”字串,把你吃掉的部分用 " " 代替:

      while ($buffer) {
         process(substr($buffer, 0,. 10, " "));
      }

使用 pack 和 unpack 代替多个 substr 调用。

用 $foo = $a || $b || $c。这样要比下面这样快得多(也短得多):
      if ($a) {
         $foo = $a;
      }
      elsif ($b) {
         $foo = $b;
      }
      elsif ($c) {
         $foo = $c;
      }
类似,用下面的方法设置缺省值:

      $pi ||= 3;

      如果你是删除字符,那么 tr/abc//d 比 s/[abc]//g 快。

带逗号分隔符的 print 可能比连接字串快。比如:

      print $fullname{$name} . " has a new home directory " .
         $home{$name} . "\n";
在把数值传递给底层的打印过程之前必须先粘合两个散列和两个定长字串,而:


      print $fullname{$name}, " has a new home directory ",
         $home{$name}, "\n";
发表于 2011-11-4 09:29:55 | 显示全部楼层
这个太经典了!!!!

数据结构是perl比较强的一个,就是LOL , AOA , AOL , LOA  ,

加上引用 还是厉害啊
发表于 2011-11-4 09:31:24 | 显示全部楼层
use Data:umper ;
use Storable;

这2个 module在调试 数据结构的时候特别有用

几乎是必用的
发表于 2011-11-4 09:31:25 | 显示全部楼层
use Data:umper ;
use Storable;

这2个 module在调试 数据结构的时候特别有用

几乎是必用的
发表于 2011-11-4 09:31:43 | 显示全部楼层
use Data: :  Dumper ;
发表于 2011-11-7 15:24:25 | 显示全部楼层
都是强淫!
发表于 2011-11-12 23:09:38 | 显示全部楼层
这个太经典了!!!!

数据结构是perl比较强的一个,就是LOL , AOA , AOL , LOA  ,

加上引用 还是厉害啊
发表于 2011-11-14 23:19:26 | 显示全部楼层
那是,reference 和datastructure 是perl的强项,

tcl这方面比较弱,写起来累死人
发表于 2011-11-15 23:18:23 | 显示全部楼层
上文中写道@AoA和@ref_to_AoA有什么区别呢?不都是指向数组的引用?(下面是对LZ的拷贝)谢谢

在这个例子里,你通常写 my @tmp = split。)
while (<>) {
        @tmp = split;         # 把元素分裂成一个数组
        push @AoA, [ @tmp ];      # 向 @AoA 中增加一个匿名数组引用
}当然,你不必命名那个临时的数组,因此你还可以说:
while(<>){
        push @AoA, [ split ];
}
如果你想要一个指向一个数组的数组的引用,你可以这么做:
while (<>){
        push @ref_to_AoA, [ split ];
}这些例子都向数组的数组增加新的行。
发表于 2011-11-16 23:33:49 | 显示全部楼层
这个是有具体语境的

不过从上面看, @AOA和@ref_to_AOA 确实是一样的

里面都是 0xABCFSFS,  0xAFSAFSAF , 等指针元素, 是引用
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

站长推荐 上一条 /2 下一条

小黑屋| 关于我们| 联系我们| 在线咨询| 隐私声明| EETOP 创芯网
( 京ICP备:10050787号 京公网安备:11010502037710 )

GMT+8, 2024-4-28 23:23 , Processed in 0.036497 second(s), 9 queries , Gzip On, Redis On.

eetop公众号 创芯大讲堂 创芯人才网
快速回复 返回顶部 返回列表