考试结束有1周了,这7天感觉过得飞快。说实话玩得不少,不过断断续续看完了一本Perl教材,也算比较充实。
Perl给我的感觉是:灵活实用,但正因为太灵活,感觉程序规模上去后,代码很容易变得ugly。
它的语法上有很多琐碎的规则,过了一遍书上内容后,感觉忘了不少,所以在此简单总结一下。
一、概述
1.1 定义
Perl = Practical extraction and report language
1.2 Perl的核心特征
-
- 隐式变量
- Perl中有的函数带有可选参数,当该忽略该参数时,隐式变量会起作用。
-
- 函数与操作符可互换
- Perl中函数与操作符没有明显区别,形式上可以互相转换。
-
- 数就是一种数
- Perl中的数只有一种类型:double,字面值数字也会隐式地转换成double值。
-
- 变量是隐式声明的
- 变量在使用前不需要先声明。
-
- 字符串和数
- 字符串(或数字)作为参数传入函数时,会根据上下文隐式地转换成数字(或字符串)。
-
- Scala和list上下文
- Scala类型(或list类型)作为参数传入函数时,会根据上下文隐式地转换成list类型(或scala类型)。
-
- 一个功能可以有多种实现
- 针对同一个问题,Perl可能提供了从简单到复杂的不同实现方式,初学者往往更喜欢使用简单但可能繁琐的方式。
-
- 没有不必要的限制
- Perl语言设计为取消所有可能的限制。例如字符串和数组的长度是没有限制的,并且根据需要隐式地增长。当然实际机器的内存限制了可以达到的最大长度。
二、Scala类型,表达式和简单的输入输出
2.1 Scala字面值
2.1.1 数值字面值
默认情况下,Perl中绝大多数的数值数据都是以double(双精度浮点数)形式保存的。(代码中还是可以使用整数字面值的,只是最终保存时以double形式保存)
2.1.2 字符串字面值
2.2 Scala变量
所有Scala变量都以$
开头,后面接上一个包含字母、数字和下划线的字符串,区分大小写,规则类似于C等语言对变量名的限定。
Scala变量并没有具体的类型声明,任何一个scala变量都可以存储数值、字符串或者内存位置的引用(相当于C的指针)。当一个Scala变量在代码中首次出现时(在没有显式定义的情况下),编译器会隐式地定义它,并且所有隐式定义的变量都是当前Perl程序的全局变量。我们也可以显式地定义一个变量,这将在后面涉及。
Perl中自带了许多隐式定义的变量,其中许多为scala变量。隐式定义的变量常常作为特定操作符的默认操作数,其中最常见的一个是$_
。
如果一个Scala变量没有被显式赋值,其值为undef
,在参与数值运算时会隐式转换为0,所以使用未赋值的变量也不会导致运行时错误。
2.3 Scala操作符
2.3.1 算术操作符
Perl支持的算术运算符与C很相似,它们都提供了基本运算符 +
-
*
/
%
,二进制运算符 &
|
~
^
>>
<<
,以及自增++
自减--
运算符
不同点在于:
-
%
和二进制运算符会将操作数隐式转换成整数后再进行计算(转换是截取整数部分,而不是四舍五入)
- Perl提供了重复操作符
x
(小写字母x),用于重复字符串,后面会涉及。它的右操作数是数字,该数会被隐式转换成整数
- Perl还提供了指数运算符
**
,作用同C里的pow函数
- 包括
**
在内的其他操作符都会将其所有操作数隐式转换成double类型再进行计算
2.3.2 算符优先级
与C类似
2.3.3 字符串操作符
2.3.4 字符串函数
函数名 |
参数 |
操作 |
chop |
string |
删除并返回最后一个字符 |
chomp |
string |
删除尾部的记录分隔符(如果有的话),默认的记录分隔符是换行,且适用不同系统环境 |
length |
string |
返回字符串长度 |
lc |
string |
字符全部转换为小写 |
uc |
string |
字符全部转换为大写 |
ord |
string |
返回第一个字符的ASCII数值 |
hex |
string |
将十六进制字符串转换为对应数值 |
oct |
string |
将八进制字符串转换为对应数值 |
index |
两个string |
返回第二个字符串在第一个字符串中的位置 |
rindex |
两个string |
类似index,但返回从右边开始的位置 |
substr |
一个string和两个数值 |
提取并返回字符串中从第一个数的位置开始到第二个数位置的子串 |
join |
一个字符和一个list |
将list中的字符串连接在一起,形成一个长串,并用给定的字符来分隔每个子串 |
2.3.5 混合模式表达式
如果一个表达式中,二元操作符两边的操作数是不同的类型,这个表达式为混合模式表达式。该表达式中,操作符定义了所需的参数类型,如果类型不一致,会发生隐式的类型转换。其中,数值转换成字符串相当于把数值打印到字符串中;而字符串转数值则会去掉头部空白符和尾部非数字字符,然后尝试将剩下的部分转换为对应数字,如果转换失败,则返回数字0。
2.3.6 赋值语句
类似C中的定义,并且Perl中也支持复合赋值符,所有二元数值、字符串和布尔运算符(后面会涉及)都支持转换为对应的复合赋值符,例如+=
-=
*=
/=
…
2.4 简单键盘输入和屏幕输出
Perl中输入输出都和一种特殊变量:文件句柄有关。标准输入(即键盘)和标准输出(即屏幕)都是文件,Perl中提供了对应的文件句柄STDIN
和STDOUT
。我们可以通过行输入操作符<>
来一行一行地从某个文件句柄中读入内容,并且可以通过print
/printf
函数输出字符串到屏幕上。因为Perl中函数和操作符可互换,所以print
/printf
也可以以操作符的形式进行调用。另外,Perl也支持sprintf
将格式化文本输出到字符串中。
2.5 运行Perl程序
三、控制语句
3.1 控制表达式
3.1.1 简单控制表达式
简单控制表达式就是一个算术表达式或者一个字符串表达式。简单控制表达式的值根据以下规则进行解释:
- 如果它是字符串表达式,那么其值为true,除非它是空字符串或者字符串”0”
- 如果它是算术表达式,那么其值为true,除非它的值为0
那么,未定义的变量一定为false,因为未定义的数字会被解释为0,未定义的字符串会被解释为空字符串。
注意,”0.0”看起来像0,但是由于它不是”0”,所以它的值为true。
3.1.2 关系表达式
用于比较两个字符串或者数值的大小关系,包含了一个关系操作符(如下表)。它的值如果为ture,返回数字1;如果为false,则返回空字符串。关系操作符在运算时一定会隐式地转换参数类型。
比较类型 |
数值比较 |
字符串比较 |
相等 |
== |
eq |
不等 |
!= |
ne |
小于 |
< |
lt |
大于 |
> |
gt |
小于或等于 |
<= |
le |
大于或等于 |
>= |
ge |
示例如下:
与C类似,关系操作符不具有连接性,所以不能使用$a < $b < $c
这样的表达式。
3.1.3 复合表达式
由scala变量、scala字面值、关系表达式和布尔操作符组成。布尔操作符类似C里的定义,包括&&
||
和 !
,具有短路效应。另外Perl提供了一组类似的操作符and
or
not
,唯一的区别在于更好的可读性和更低的算符优先级,并且事实上,它们的优先级比其他任何Perl操作符的优先级还要低。
注意所有二元布尔操作符都可以与等号连接,形成复合赋值操作符。例如:
3.2 选择语句
3.3 迭代语句
3.4 区块跳转
与C类似,Perl中提供了如下区块跳转的操作符,便于对循环结构进行更灵活的控制:
操作符 |
相当于C的关键字 |
作用 |
next |
continue |
停止当前迭代,并跳转到控制表达式,如果表达式仍为true,继续下次迭代 |
last |
break |
跳出当前循环体,并开始执行循环体下面的第一条语句 |
redo |
无 |
停止当前迭代,并跳转到循环体的第一个语句,注意不会再次运算控制表达式,谨慎使用 |
这些操作符支持加上一个标签名作为唯一参数,这会在该标签对应的循环结构上执行相应的跳转。
3.5 语句修饰符
条件操作符和循环操作符还可用于构造语句修饰符。语句修饰符可添加到单个语句的末尾,以对其行为进行控制。
注意无论是采用普通的循环结构,还是使用这样的语句修饰符来构造循环,它们都是前测循环,意味着逻辑上都是先检测条件,再执行循环体。
要实现后测循环,也就是先执行循环体再检测条件,需要使用do块,并在它的后面接上包含循环操作符的语句修饰符。
3.6 停止执行
3.7 更多输入相关
结合行输入操作符和控制表达式,我们可以连续读入多行数据。由于Perl的许多内置函数支持默认参数,当省略参数时,会使用隐式参数执行操作,所以下面的代码显得十分简洁。另外,当<>
操作符读到EOF时,会返回空字符串,作为控制表达式的话,即为false。
3.8 调试器
Perl调试器的用法类似于GDB,以下列出主要的几个指令:(斜体为指令参数)
指令 |
解释 |
行为 |
n |
next |
执行下一行语句,如果包含函数,执行该函数并返回 |
s |
step |
执行单行语句,如果包括函数,则进入该函数的首行 |
b k
|
breakpoint |
在k行设置断点 |
b subx
|
breakpoint |
在函数subx的首行设置断点 |
c |
continue |
继续执行,直到下一个断点 |
p expr
|
print |
计算表达式并显示其结果 |
q |
quit |
终结该程序 |
四、数组
4.1 数组简介
Perl的数组十分灵活,其长度可以根据需要自动扩展,没有类型限制,也就是每个数组单元都可以保存数值、字符串和引用。
4.2 list字面值
list是Scala值的有序列。
List字面值 |
描述 |
(5) |
一个包含单个数值的list |
() |
一个空list |
(“apples”, “pcs”) |
一个包含两个字符串的list |
(“me”, 100, “you”, 50) |
一个包含两个字符串和两个数值的list |
($sum, “Sum”) |
一个带有变量的list |
(2 * $total, “!” x 20) |
一个带有表达式的list |
qw(bob carol ted arf) |
一个隐式quote的list |
范围操作符(省略号..
)可用于在list字面值中指定一个范围内的scala字面值,支持数值或字符串。例如:
List字面值 |
等价list |
(0 .. 6) |
(0, 1, 2, 3, 4, 5, 6) |
(1.5 .. 7) |
(1.5, 2.5, 3.5, 4.5, 5.5, 6.5) |
(5 .. 3) |
() |
(‘a’ .. ‘z’) |
(‘a’, ‘b’, ‘c’ … ‘z’) |
(‘aa’ .. ‘zz’) |
(‘aa’, ‘ab’, ‘ac’, … ‘zz’) |
注意上面第二个例子,等价list中每个数值都是前面的值加1,最后一个值要求不超过最大值7,所以是6.5。
另外,后两个例子中的等价list只是对取值的解释,不是Perl的语句。
4.3 数组
数组是一个值为list的变量,名称命名规则类似Scala变量,但必须以@
开头。除去首字符同名的Scala变量和数组变量没有任何关系,例如$list
与@list
无关。数组变量和Scala变量一样,不需要声明,其长度会随需要自动增长。未初始化(赋值)的数组有默认的初始值:空数组,也就是()
。注意list字面值中仅能包含scala值(或表达式、变量),不可以嵌套数组等其他类型的值,scala值包括数值、字符串和引用。
Perl中没有多维数组,但是可以通过保存了一系列引用的数组来模拟多维数组,因为引用也是一种scala值,这将在后面涉及。
4.4 引用数组元素
不出意料,Perl中引用数组中的元素也是使用[]
,只需在数组名后添加[下标]
,这里方括号中的下标是scala值或者可转换成scala值的表达式,下标从0开始。
出乎意料的是,Perl中引用数组元素时,变量名需要改成$
开头,就好像这个整体是一个scala变量。
4.5 切片
切片是对数组中元素子集的引用。
4.6 Scalar和list上下文
Perl中操作数的上下文影响了其在表达式中的值。上下文分两种,scala和list。赋值语句的左值类型决定了该操作的上下文,例如赋值语句左侧是scala,右侧是数组,则数组的长度会被赋给左值。一些函数和操作符需要其参数为scala,一些需要list类型。我们可以强制转换list类型为scala类型,但不能强制转换任何类型为list类型,因为如果某个函数需要list参数的话,其参数已经被强制转换为了list类型。当list字面值赋值给scala时,会将其中的最后一个元素的值赋给scala变量。还有的操作符在两种上下文中都可以使用。
4.7 foreach语句
4.8 List操作符
Perl提供了很多操作数组和list的操作符,由于数组的值就是list,所以我们一般把它们叫做list操作符。提醒下,操作符和函数是等价的。
split [/Pattern/[, Expression[, Limit]]]
类似于javascript的split
函数。其中/Pattern/
为正则表达式,Expression
为目标表达式,Limit
是产生的最大元素数量
4.9 命令行参数
@ARGV
是Perl中保存所有命令行参数的隐式变量,因此可以用如下方式访问所有命令行参数:
五、Hash和引用
Hash有时被称为关联数组,或者哈希表。Perl是唯一的一个拥有内置Hash支持的且被广泛使用的语言。Hash结合引用类型的scala变量可以构造出复杂的数据结构,例如数组的数组或者hash的数组。
5.1 Hash结构
Hash和数组很像,但两者最主要的区别是:
-
Hash结构中数据元素由字符串值索引,这些字符串也被保存在Hash中,并被称为键(key)。
-
Hash结构中的数据元素并没有经过排序。
Hash中的每个元素是一对scalar值,其中前者是键,后者为值。Hash变量以%
开头,和数组类似,使用Hash变量不需要提前声明,并且它会根据需要自动增长,也可以在任何时候将其压缩。我们可以通过键来访问Hash中对应的值。
5.2 Hash操作符
Perl有预定义的hash:%ENV
,保存了操作系统的环境变量。
5.3 引用
下面讨论第三种scala类型——引用。它与其他语言中的指针类似。许多应用程序需要使用复杂的数据结构。这些结构可以通过常指针,即Perl中的引用来定义和操作。引用的值是地址,这些地址可以是其他命名变量的地址或者匿名变量(字面值)的地址,或者甚至是我们将在后面介绍的子程序的地址。没有名字的匿名变量只可以通过引用来使用。
反斜杠\
可用于将命名变量或scala字面值的地址赋给一个变量,保存该地址的变量即为一个硬引用。解引用(dereference)只需将值变量的名字中字母或数字部分替换成对应引用变量的名字,而对数组或hash解引用还可以使用->
操作符。另外,Perl中也可以使用变量名称的字符串来作为一种引用,即软引用,其解引用方式与硬引用相同。具体实例如下:
使用软引用十分危险,因为可能由于拼错引用的变量的名称而导致严重的后果,所以可以使用use strict 'refs';
来指示编译器禁用这种符号引用。
5.4 嵌套数据结构
有了引用变量,我们可以构造比数组和hash更加复杂的数据结构。
六、函数
Perl中,函数又叫子程序。
6.1 子程序基础
Perl中的子程序没有显式地指定参数数量、参数类型和返回值类型。子程序可以在其他子程序的函数体以外的任何地方定义。
6.2 无参数的函数
6.3 变量的作用域和生命周期
Perl中的变量是隐式地属于全局变量,但我们可以通过my
和local
来定义局部变量,并且可以通过use strict 'vars'
来通知编译器禁用全局变量。
6.4 参数
6.5 非直接的函数调用
我们可以通过\
和&
来获得函数的地址,从而保存到引用变量中,然后可以通过&
解引用来调用该函数。当然,我们也可以采用软引用,直接使用函数名称字符串来解引用。
6.6 Perl预定义函数
具体请查表,略
6.7 再谈sort函数
sort函数可以接受两个参数,其中第一个参数为排序操作时的比较函数,第二个参数则是需要排序的数组。该比较函数可以采用函数名、函数区块或者一个值为函数名称或函数地址的scala变量。该函数中包含了隐式变量$a
和$b
,对应了每次比较的两个元素,该函数需要返回0表示两者顺序无关,或大于0表示两者需要对调位置,或小于0表示两者已经排好序。Perl提供了两个默认的比较函数:<=>
来比较数值,cmp
来比较字符串。通过自定义比较函数,我们可以完成自定义的排序操作。另外,这里$a
和$b
即使在use strict 'vars'
时仍可使用,因为它们是特殊的局部变量,不受此命令限制。
参考书籍:《A Little Book on Perl》, Robert W. Sebesta, Prentice Hall.