第4章:子程序
- 返回值:在子程序执行过程中,它会不断进行运算,而最后一次运算的结果(不管是什么)都回被自动当成子程序的返回值。
- 参数:Perl回自动将参数列表化名为特殊的数组变量
@_
,改变量在子程序执行期间有效。将数组作为参数传入,会自动打开作为参数列表。
子程序中的私有变量
sub max {
my($m, $n);
($m, $n) = @_;
# my($m, $n) = @_;
if ($m > $n ) { $m } else { $n }
}
# 求最大值
sub max {
my($max_so_far) = shift @_;
foreach (@_) {
if ($_ > $max_so_far) {
$max_so_far = $_;
}
# $max_so_far = $_ if $_ > $max_so_far; //简
}
$max_so_far;
}
词法(my)变量
- 词法变量可使用于任何语句块内
- my操作符不会更改变量赋值时的上下文:
my($num) = @_; # 列表上下文,和 ($num) = @_; 相同 my $num = @_; # 标量上下文,和 $num = @_; 相同
- my操作符不加括号时,只能用来声明单个词法变量:
my $fred, $barney; # 没声明$barney my($fred, $barney); # 两个都声明
return 操作符
my @name = qw/fred barney betty dino wilma pebbles bamm-bamm/;
my $result = &which_element_is("dino", @name);
print($result);
sub which_element_is {
my($what, @array) = @_;
foreach (0..$#array) {
if ($what eq $array[$_]) {
return $_;
}
}
-1;
}
非标量返回值
列表上下文调用子函数,可以返回列表值。
sub list_from_fred_to_barney {
if ($fred < $barney) {
$fred..$barney;
} else {
reverse $barney..$fred;
}
}
$fred = 1;;
$barney = 6;
@c = &list_from_fred_to_barney;
print(@c);
持久性私有变量
use 5.010;
// 第一次调用子程序时,Perl声明并初始化变量$n
// 接下来的调用中,这个表达式将被Perl忽略
// 每次子程序返回时,Perl都会将变量$n的当前值表里下来,以备下次调用时再用
sub marine {
state $n = 0;
$n += 1;
print "Hello, sailor number $n!\n";
}
&marine;
&marine;
&marine;
&marine;
use 5.010;
running_sum(5,6);
running_sum(1..3);
running_sum(4);
sub running_sum {
state $sum = 0;
state @numbers;
foreach my $number (@_) {
push @numbers, $number;
$sum += $number;
}
say "The sum of (@numbers) is $sum";
}
习题4
#4.1 求和
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 in separate lines: ";
my $user_total = total(<STDIN>);
print "The total of those numbers is $user_total.\n";
sub total {
my $sum = 0;
foreach my $num (@_) {
$sum += $num;
}
$sum;
}
#4.3 求平均值
my @fred = above_average(1..10);
print "\@fred is @fred\n";
my @b = above_average(100, 1..10);
print "\@b is @b\n";
my @empty = above_average();
print "\@empty is @empty\n";
sub total {
my $sum = 0;
foreach my $num (@_) {
$sum += $num;
}
$sum;
}
sub average {
if (@_ == 0) {
return
}
&total(@_) / @_;
}
sub above_average {
my $average = &average(@_);
my @list;
foreach my $element (@_) {
if ($element > $average) {
push @list, $element;
}
}
@list;
}
#4.4 持续性私有标量
use 5.010;
greet("Fred");
greet("Barney");
sub greet {
state $last_person;
my $name = shift;
print "Hi $name! ";
if (defined $last_person) {
print "$last_person is also here!\n";
} else {
print "You are the fires one here!\n";
}
$last_person = $name;
}
#4.5 持续性私有数组
use 5.010;
greet("Fred");
greet("Barney");
greet("Wilma");
greet("Betty");
sub greet {
state @names;
my $name = shift;
print "Hi $name! ";
if (@names) {
print "I've seen: @names\n";
} else {
print "You are the first one here!\n";
}
push @names, $name;
}
输入和输出
读取标准输入
行输入操作符()和Perl的默认变量($_)之间没有什么关联,只是在这个简写里,输入的内容会恰好存储在默认变量中而已。
while (defined($line = <STDIN>)) {
print "I saw $line";
}
# 简写
while (<STDIN>) {
print "I saw $_";
}
# 类似
while (defined($_ = <STDIN>)) {
print "I saw $_";
}
# 如果在列表上下文中调用输入操作符,它会返回一个列表
# 与while读取的区别在于:它是直接读取剩余全部内容
foreach (<STDIN>) {
print "I saw $_";
}
钻石操作符 (<>)
# 钻石操作符 <>
# 读取多个文件参数作为输入
# 可以理解为把多个文件合并为一个文件作为输入
# ./my_program file1 file2 file3
while (<>) {
print "$_";
}
while (defined($line = <>)) {
chomp($line);
print "It was $line that I saw!\n";
}
# 简写
while (<>) {
chomp;
print "It was $_ that I saw!\n";
}
调用参数
钻石操作符其实不会去检查命令行参数,它的参数其实不过是来自@ARGV
数组。这个数组是由Perl解释器事先建立的特殊数组,其内容就是由命令行参数组成的列表。
钻石操作符会查看数组@ARGV
,然后决定改用哪些文件名,如果它找到的是空列表,就会改用标准输入流;否则,就会使用@ARGV
里的文件列表。
文件句柄
文件句柄(filehandle)就是程序里代表Perl进程(process)与外界之间的I/O联系的名称。
特殊文件句柄名 | 描述 |
---|---|
STDIN | 标准输入 |
STDOUT | 标准输出 |
STDERR | 标准错误输出 |
DATA | |
ARGV | |
ARGVOUT |
打开文件句柄
open CONFIG, 'dino';
# 同上
open CONFIG, '<dino';
open CONFIG, '>dino';
open LOG, '>>logfile';
# 5.6版本之后, 三个参数形式写法
my $filename = '';
open CONFIG, '<', 'dino';
# 以指定编码打开
open CONFIG, '<:encoding(UTF-8)', 'dino';
# 以指定编码写入
open LOG, '>:encoding(UTF-8)', $filename;
# 简写
open LOG, '>:utf8', $filename;
打印所有perl能理解和处理的字符编码清单:
perl -MEncode -le "print for Encode->encodings(':all')"
以二进制方式读写文件句柄
binmode STDOUT; # 不要转换行符
binmode STDERR; # 不要转换行符
# Perl5.6, 可以在第二个参数指定层
# 输出 Unicode 到 STDOUT
binmode STDOUT, ':encoding(UTF-8)';
# 如果传到 STDIN 的是 UTF-8 编码的字符
binmode STDIN, ':encoding(UTF-8)';
用die处理致命错误
die函数会哦i输出你指定的信息到专为这类信息准备的标准错误流中,并且让你的程序立刻终止返回不为零的退出码。
warn 函数跟die函数一样,只不过它不会终止程序运行。
# 变量 $! 保存了系统错误信息
# 若不是系统服务请求的错误, 则没用
if ( ! open LOG, '>>', 'logfile' ) {
die "Cannot create logfile: $!";
}
# die 会自动将 Perl 程序名和行号附加在错误信息后面
if (@ARGV < 2) {
die "Not enough arguments";
}
# 通过添加换行符, 不显示行号和文件名
if (@ARGV < 2) {
die "Not enough arguments\n";
}
自动检测致命错误
从Perl 5.10 开始,autodie
编译指定已经成为标准库的一部分。如果Perl内置函数的幕后操作需要调用操作系统接口的话,那么中途出现的错误并不是编程人员所能控制的,一旦发现这部分系统调用出错,autodie便会自动帮你调用die。
use autodie;
open LOG, '>>', 'logfile';
标准变量中的文件句柄
# 使用标准变量保存文件句柄
my $rocks_fh;
open $rocks_fh, '<', 'rocks.txt'
or die "Could not open rocks.txt:$!";
# 一步到位
open my $rocks_fh2, '<', 'rocks.txt'
or die "Could not open rocks.txt:$!";
# 使用方式同裸字
while (<$rocks_fh>) {
chomp;
}
open my $rocks_fh, '>>', 'rocks.txt'
or die "Count not open rocks.txt: $!";
foreach my $rock (qw( slate lava granite )) {
say $rocks_fh $rock
}
# Perl能够自动判断,如果跟在print后面的第一个参数之后没有逗号,就说明它是一个文件句柄
print $rocks_fh "limestone\n";
close $rocks_fh;
习题5
#5.2
print "Enter some lines, then press Ctrl-D:\n";
chomp(my @lines = <STDIN>);
print "1234567890" x 7, "12345\n";
foreach (@lines) {
printf "%20s\n", $_;
}
# 不使用循环
my $format = "%20s\n" x @lines;
printf $format, @lines;
#5.3
print "请输入字宽:";
chomp(my $wide = <STDIN>);
print "Enter some lines, then press Ctrl-D:\n";
chomp(my @lines = <STDIN>);
print "1234567890" x (($wide+9)/10), "\n";
foreach (@lines) {
printf "%${wide}s\n", $_;
}
习题6
#6.1
my %last_name = qw{
fred flintstone
barney rubble
wilma flintstone
};
print "Please enter a first name: ";
chomp(my $name = <STDIN>);
if (exists $last_name{$name}) {
print "That's $name $last_name{$name}\n";
} else {
print "The name $name is not exists.\n";
}
#6.2 计算单词出现次数
chomp(my @lines = <STDIN>);
my %words = ();
foreach (@lines) {
$words{$_} ++;
}
foreach my $work (sort keys %words) {
printf "%s:%d\n", $work, $words{$work};
}
#6.3 对齐打印环境变量
my $longest = 0;
foreach my $key (keys %ENV) {
my $key_length = length($key);
$longest = $key_length if $key_length > $longest;
}
foreach my $key (sort keys %ENV) {
printf "%-${longest}s %s\n", $key, $ENV{$key};
}
第7章 漫游正则表达式王国
Unicode 属性
Unicode 字符能够理解自身含义,它们不只是简单的字节序列。每个字符除了字节组合之外,还附带着属性信息。若要匹配某项属性,只需要把属性名放入\p{PROPERTY}
里面。
# 匹配空格
if (/\p{Space}/) {
print "The string has some whitespace.\n";
}
# 匹配数字
if (/\p{Digit}/) {
print "The string has a digit.\n";
}
# 匹配十六进制
if (/\p{Hex}\p{Hex}/) {
print "The string has a pair of hex digits.\n";
}
# 匹配不包含特定属性的字符,使用大写的 P
# 只要不是空白符都能匹配
if (/\P{Space}/) {
print "The string has one or more non-whitespace characters.\n";
}
模式分组
可以使用反向引用(back reference)来引用圆括号中的模式所匹配的文字,这个行为我们成为捕获组(capture group)。
# 匹配连续出现的两个通用的字符
$_ = "abba";
if (/(.)\1/) {
# 匹配 bb
}
$_ = "yabba dabba doo";
if (/y(....) d\1/) {}
#回文模式
if (/y(.)(.)\2\1/) {
# 匹配 abba
}
# 分组顺序:左括号的顺序,包括嵌套括号
if (/y((.)(.)\3\2) d\1/) {}
# \g{N}
use 5.010;
$_ = "aa11bb";
if (/(.)\g{1}11/) {}
# 相对反向引用
if (/(.)\g{-1}11/) {}
择一匹配
# 匹配任何含有3个单词中一个的字符串
/fred|barney|betty/;
# 匹配fred和barney之间出现一次以上空格、制表符或者两者混合的字符串
/fred( |\t)+barney/;
# 中间的分隔符一致
/fred( +|\t+)barney/;
字符集简写
简写 | 描述 |
---|---|
\d | 0-9,以及其他有数学意义的字符 |
\s | 任意空白字符 |
\h | 水平空白符 |
\v | 垂直空白符 |
\R | 任意一种断行符(5.10) |
\w | [a-zA-Z0-9_],即匹配标识符(变量名) |
第八章 用正则表达式进行匹配
用 m// 进行匹配
m//
(pattern match operator,模式匹配操作符)
模式匹配修饰符(modifier)
它们是一组追加在模式表达式末尾定界符后面的字母,用来改变默认的匹配行为。
修饰符 | 描述 |
---|---|
/i | 大小写无关 |
/s | 匹配任意字符,点号(.)默认无法匹配换行符 |
/x | 允许在模式里加入空白符,使它更易阅读、理解 |
选择一种字符解释方式
修饰符 | 描述 |
---|---|
/a | ASCII方式 |
/u | Unicode方式,\w可以匹配中文 |
/l | 遵从本地化语言的设定 |
锚位
默认情况下,如果给定模式不匹配字符串的开头,就会顺移到下一个字符继续尝试。而通过给定锚位,我们可以让模式仅在字符串指定的位置匹配。
锚位 | 描述 |
---|---|
\A | 匹配字符串的绝对开头,即便是多行文本 |
\z | 匹配字符串的绝对末尾 |
\Z | 匹配行末锚位 |
^ | 脱字符,行首锚位,beginning-of-line |
$ | 行末锚位,end-of-line |
\b | 单词锚位,整词匹配,不受引号影响 |
绑定操作符 =~
默认情况下匹配模式的操作对象是$_
,绑定操作符(binding operator,=~)告诉Perl,拿右边的模式来匹配左边的字符串,而不是$_。
捕获变量
圆括号出现的地方一般都会触发正则表达式引擎捕获匹配到的字符串。
自动捕获变量
有三个免费的捕获变量,就算不加捕获圆括号也能使用
捕获变量 | 描述 |
---|---|
$& | 字符串实际匹配模式的部分 |
$` | 匹配区段之前的内容 |
$’ | 匹配区段之后的内容 |
第九章 用正则表达式处理文本
用s/// 进行替换
$_ = "green scaly dinosaur";
print "$_\n";
s/(\w+) (\w+)/$2, $1/;
print "$_\n";
s/^/huge, /;
print "$_\n";
s/,.*een//;
print "$_\n";
s/green/red/;
print "$_\n";
s/\w+$/($`!)$&/;
print "$_\n";
s/\s+(!\W+)/$1 /;
print "$_\n";
s/huge/gigantic/;
print "$_\n";
大小写转换
可用于任何双引号内的字符串
转义符 | 描述 |
---|---|
\U | 将其后的所有字符转为大写 |
\L | 将其后的所有字符转成小写 |
\E | 关闭大小写转换功能 |
\u | 只影响紧跟其后的第一个字符 |
\l | 同上 |
split操作符
根据给定模式拆分字符串,并返回字段列表。
my @fields = split /separator/, $string;
split 会保留开头处的空字段,却会舍弃结尾处的空字段。
join 函数
join函数不会使用模式,它的功能与split相反。
列表上下文中的 m//
在列表上下文中使用模式匹配操作符(m//)时,如果匹配成功,则返回的是所有捕获变量的列表;如果匹配失败,则返回空列表。
$_ = "Hello there, neighbor!";
my($first, $second, $third) = /(\S+) (\S+) (\S+)/;
print "$second is my $third\n";
一次更新多个文件
$^I
默认值是 undef,如果将其赋值成某个字符串,钻石操作符<>
就会具有更多的魔力。
<>
会自动帮你打开许多文件,如果没有指定文件,它就会从标准输入读进数据。如果$^I
中是个字符串,改字符串就会变成本分文件的扩展名。