学习Perl (3)
这几天学习Perl,我的感觉是越到后面的内容越难一些。
子例程
1)定义子例程
定义你自己的子例程时,将会用到关键字sub、子例程的名称(不含&符号)以及(花括号内)经缩排的构成子例程主体的程序代码块:(子例程的定义可以放在程序中的任何地方)
#!/usr/bin/perl -w
sub marine {
$n += 1;
print "Hello,sailor number $n!n";
}
2)调用子例程
任何表达式中只要使用了子例程的名称(加上&符号),就会调用子例程:&marine;
3)返回值
在Perl中,任何子例程都有返回值,子例程并没有分成有返回值和没有返回值两种。
#!/usr/bin/perl -w
sub sum_of_fred_and_barney {
print "Hey,you called the sum_of_fred_and_barney subroutine!n";
$fred + $barney;
}
$fred = 3;
$barney = 4;
$c = &sum_of_fred_and_barney;
print "$c is $c.n";
$d = 3 * &sum_of_fred_and_barney;
print "$d is $d.n";
sub larger_of_fred_and_barney {
if ($fred > $barney){
$fred;
} else {
$barney;
}
}
$fred = 3;
$barney = 4;
$c = &larger_of_fred_and_barney;
print "The larger is $c.n";
3)参数
要传递参数列表到子例程里,只要在子例程调用的后面加上被括住的列表表达式就行了:$n = &max(10,15); #本子例程的调用包含了两个参数
当然得有变量来存储这个列表,所以Perl会自动将参数列表存储到名为@_的特殊数组变量,在子例程执行期间内都有效。子例程可以访问这个数组,以判断参数的个数和这些参数的值。所以这表示子例程的第一个参数存储于$_[0],第二个参数存储于$_[1],依次类推。
但是请特别注意,这些变量和$_变量毫无关联,就像$dino[3]与$dino能彼此共存一样。
sub max {
if ($_[0] > $_[1]) {
$_[0];
} else {
$_[1];
}
}
4)子例程里的私有变量
在Perl里,所有变量都被默认为全局变量,也就是说,在程序里的任何地方都可以访问它们。但是你可以随时运行my操作符来创建称为词法变量的私有变量:
sub max {
my($m,$n) = @_;
if ($m > $n) { $m } else { $n }
}
5)长度可变的参数列表
更好的&max例程
#!/usr/bin/perl -w
$maximum = &max(3 , 5, 10, 4, 6);
sub max {
my($max_so_far) = shift @_;
foreach (@_) {
if ($_ > $max_so_far) {
$max_so_far = $_;
}
}
$max_so_far;
}
print "$maximum.n";
上面的代码使用了一般称为高水位标记的算法:大水过后,在最后一波浪消退时,高水位标记会表示所见过最高的水位。在本例中,$max_so_far记录了最高水位标记,亦最大数字。
6)关于词法(my)变量
事实上,词法变量可以使用在任何块内,而不仅限于子例程的块。
还需要注意的是,my操作符并不会更改变量赋值时的上下文:
my($num) = @_; #列表上下文,和($num) = @_;相同
my $num = @_; #标量上下文,和$num = @_;相同
注意,在my不加括号时,只会声明一个词法变量而已:
my $fred,$barney; #错,没声明$barney my ($fred,$barney); #两个都声明了
7)use strict编译命令
所谓编译命令,就是对编译器的指示,告诉它关于程序代码的一些信息。
要告诉Perl你愿意接受某些限制,请将use strict这个编译命令放在程序开头(或在任何想要强制使用这些规则的块或文件里): use strict; #强制使用一些良好的程序语言规则
这样,撇开其他种种限制不谈,Perl会要求你一定要用my来声明每个新变量。当然,此限制只适用于新创建的变量。根据大部分人的建议,比整个屏幕长的程序都应该加上use strict。
8)return操作符
在子例程里,return操作符会立即返回某个值:
#!/usr/bin/perl -w
use strict;
my @names = qw/ fred barney betty dino wilma pebbles bamm-bamm /;
my $result = &which_element_is("dino", @names);
sub which_element_is {
my($what,@list) = @_;
foreach (0..$#list) {
if ($what eq $list[$_]){
return $_;
}
}
-1;
}
写Perl程序时return最常见的用法:马上返回某个值,而不要执行子例程的其余部分。
9)省略&符号
不要将子例程的声明放到子例程的调用之后,不然编译器就无法知道division的意义何在。
真正的陷阱是,假如这个子例程与Perl内置函数同名,你就必须适用&符号来调用:
#!/usr/bin/perl -w
use strict ;
sub chomp {
print " Munch, munch!n";
}
&chomp; #&符号不可省略!
所以,真正的省略规则如下:除非你知道所有Perl内置函数的名称,否则请务必在调用函数时使用&符号!
10)返回非标量值
子例程不仅可以返回标量值,只要在列表上下文中调用它,它就可以返回列表值:
#!/usr/bin/perl -w
sub list_from_fred_to_barney {
if ($fred < $barney) {
$fred..$barney;
} else {
reverse $barney..$fred;
}
}
$fred = 11;
$barney = 6;
@c = &list_from_fred_to_barney;
print "@c.n";
习题:
1)写一支名为&total的子例程,用来返回一串数字的总和:
#!/usr/bin/perl -w
use strict;
sub total {
my $sum;
foreach (@_) {
$sum += $_;
}
$sum;
}
my @fred = qw{ 1 3 5 7 9 };
my $fred_total = &total(@fred);
print "The total of @fred is $fred_total.n";
print "Enter some numbers on separate lines:";
my $user_total = &total(
print "The total of those number is $user_total.n";
2)利用前一题的子例程,写一支程序来计算从1到1000的数值的总和:
#!/usr/bin/perl -w
use strict;
sub total {
my $sum;
foreach (@_) {
$sum += $_;
}
$sum;
}
my @fred = 1..1000 ;
my $fred_total = &total(@fred);
print "The total of @fred is $fred_total.n";
参考答案如下:
#!/usr/bin/perl -w
use strict;
sub total {
my $sum = 0;
foreach (@_) {
$sum += $_;
}
$sum;
}
print "The number from 1 to 1000 add up to:",&total(1..1000),".n";
3)写一支名为&above_average的子例程,用来传入一串数字并返回所有大于平均值的数字:
#!/usr/bin/perl -w
sub total {
my $sum = 0;
foreach (@_) {
$sum += $_;
}
$sum;
}
sub average {
if (@_ == 0) { return }
my $count = @_;
my $sum = &total(@_);
$sum/$count;
}
sub above_average {
my $average = &average(@_);
my @list;
foreach $element (@_) {
if ($element > $average) {
push @list, $element;
}
}
@list;
}
my @fred = &above_average(1..10);
print "@fred is @fredn";
print "(Should be 6 7 8 9 10)n";
my @barney = &above_average(100,1..10);
print "@barney is @barneyn";
print "(Should be just 100)n";
输入与输出
1)读取标准输入
在标量上下文中使用
#!/usr/bin/perl -w
while (defined($line =
print "I saw $line";
}
#!/usr/bin/perl -w
while (
print "I saw $_";
}
#!/usr/bin/perl -w
while (defined($_ =
print "I saw $_";
}
在列表上下文中执行整行输入操作符,它会返回一个列表,其中包含(剩下来的)所有输入内容,每个列表的元素代表一行输入内容:
#!/usr/bin/perl -w
foreach (
print "I saw $_";
}
while循环和foreach循环的区别:在while循环里,Perl会读取一行输入,把它存入某个变量并且执行循环的主体,接下来,它会回头去寻找其他的输入行;但是在foreach循环里,整行输入操作符会在列表上下文中执行(因为foreach需要逐项处理列表的内容),为此,在循环能够开始执行之前,它必须先将输入内容全部读进来。因此,最好的做好是尽量使用while循环的简写,让它每次处理一行。
2)从钻石操作符输入
如果想写出用法像cat、sed、awk、sort、grep、lpr等工具的Perl程序,钻石操作符”<>”将会是你的好帮手。程序的调用参数通常是命令行上跟在程序名称后面的几个“单词”:
$ ./my_program fred barney betty #命令行参数代表依次处理的数个文件的名称
若不提供任何调用参数,程序应该改写成处理标准输入流。但有个特例,如果某个参数是连字符(-),那也代表标准输入。让程序以这种方式运行的好处,就是你可以在运行时指定程序的输入来源。钻石操作符是整行输入操作符的特例,不过它并不是从键盘取得输入,而是从用户想要的输入来源取得:
#!/usr/bin/perl -w
while (defined($line = <>)) {
chomp($line);
print "It was $line that I saw!n";
}
简写:#!/usr/bin/perl -w
while (<>) {
chomp; #chomp的默认用法,不加参数时,chomp会直接作用在$_上。
print "It was $_ that I saw!n";
}
3)调用参数
严格来说,钻石操作符其实不会真的去检查调用参数,它靠的是@ARGV数组。这个数组是由Perl解释器事先建立的特殊数组,其内容就是调用参数所组成的列表。钻石操作符如何决定该使用哪些文件名?方法如下:它会查找@ARGV,如果它找到的是空列表,就会改用标准输入流;否则,就会使用@ARGV里的文件列表。
4)输出至标准输出
#!/usr/bin/perl -w
$name = "Larry Wall" ;
print "Hello there,$name,did you know that 3+4 is",3+4,"?n";
输出数组和替换数组是两回事:
print @array; #输出列表中的项目,各个项目之间没有空格
print “@array”; #输出一个字符串(内容是数组替换的结果),项目间以空格隔开
一般来说,如果数组里的元素包含换行符号,那么只要直接将它们输出来就可以了;通常在使用引号的场合,字符串后面都应该加上n。
print <>; #’cat’的源代码 print sort <>; #’sort’的源代码
print的调用看起来像函数调用,函数调用:print (2+3);
print的返回值不是“真”就是“假”,代表print是否执行成功。除非发生了I/O错误,否则一定会执行成功:$result = print(“Hello world!n”);
#!/usr/bin/perl -w
$result = print("Hello world!n");
print "The result is : $result.n";
如果print(或其他函数名称)后面接着一个左括号,请务必在函数的所有参数之后也有相应的右括号。
省略括号的问题: print (2+3)*4 #错 print ((2+3)*4); #正确
5)用printf格式化输出结果
printf操作符的参数包括“格式字符串”及“所要输出的数据列表”,后面的列表里的项目的个数应该要和转换的数目一样多,否则就会无法正常运行。
printf可用的转换格式很多,如%g、%s、%f、%d等:
#!/usr/bin/perl -w
printf "in %d days!n",17.85; #输出结果为in 17 days! 无条件舍去小数点后的数字
6)文件句柄
文件句柄就是Perl程序里的某个名称,代表Perl进程与外界之间的输入/输出联系。它是“联系”的名称,不一定是文件名。建议全部使用大写字母来命名文件句柄。Perl保留了6个具有特殊用途的文件句柄名称:STDIN、STDOUT、STDERR、DATA、ARGV、ARGVOUT。
标准的STDERR通常是用户的屏幕,但可以将STDERR以如下的shell命令送到某个文件:
打开文件句柄
当你需要其他普通的文件句柄时,请使用open操作符告诉Perl,要求操作系统为你的程序和外界开启一座桥梁:open CONFIG, “,dino”; open LOG, “>>logfile”;open BEDROCK,”>fred”;
不良文件句柄
如果你尝试从不良文件句柄(即没有完全打开的文件句柄)来读取数据,将会立刻读到文件结尾。如果试图将数据写入不良文件句柄,这些数据将无声无息地被丢弃掉。可以避免:一开始就用-w选项,或者warnings编译命令来启用警告信息。
关闭文件句柄
当不再需要时,可以使用close操作符来关闭文件句柄:close BEDROCK;
7)用die来处理严重错误
die函数会输出你指定的信息(到此类信息该去的标准错误流里),并且让你的程序在非零的状态下立即终止。通常,对于程序的结束状态来说,零代表成功,非零代表失败。
Perl特殊变量“$!”,程序错误信息。
if ( !open LOG,">>logfile") {
die "Cannot create logfile:$!";
}
一个经验法则就是,用来指示用法错误的信息里可以加上结尾的换行符,但若想在调试过程中追踪相关的错误,就不要加上换行的结尾符。请一定检查open的返回值,因为之后的程序代码必须在文件打开成功时才能顺利进行。
用warn送出警告信息
warn函数不会停止程序的运行。
8)使用文件句柄
当文件句柄以读取模式打开后,你可以轻易的从它读取一行数据,就像从STDIN读取标准输入流一样。
以写入或添加模式打开的文件句柄,可以在print或printf函数中使用。使用时,请直接放在关键字之后,参数列表之前:print LOG “Captain’s log,stardate 3.14159n”;
改变默认的输出文件句柄
重新打开标准的文件句柄
习题
1)写一支功能跟cat类似的程序,但是将各行反序输出:
#!/usr/bin/perl -w
print "Please enter some lines,then press Ctrl+D!n";
print reverse<>;
参考答案如下:
#!/usr/bin/perl -w
print reverse<>;
2)写一支程序,要求用户分行键入各个字符串,然后以20个字符宽、向右对齐的方式输出每个字符串:
#!/usr/bin/perl -w
print "Please enter some lines,each line for one,then press Ctrl+D:n";
chomp(my @lines =
print "1234567890" x 7,"12345n";
foreach(@lines) {
printf "%20sn",$_;
}