第11章:引用
- 引用一律声明为标量类型(即$开头的命名变量),使用\运算符取引用
- 对引用变量的修改等同于对引用指向实际数据的修改
- 取变量引用:my $scalar_r = \$scalar;
- 取列表的引用:my $array_r = \@array;
- 取哈希的引用:my $hash_r = \%hash;
- 通过引用解决列表无法嵌套的问题:
my @array1 = (10, 20, 30, 40, 50); my @array2 = ( 1, 2, \@array1, 3, 4); |
- 因为\@array1本质上只是一个标量,所以列表不会被扁平化,依旧保留了嵌套层次
- 对匿名列表的引用:将列表的()替换为[]:my $array_r = [1, 2, 3, 4, 5];
- 对匿名哈希的引用:将哈希的()替换为{}:my $hash_r = { apple => "pomme", pear => "poire" };
- 对引用变量解引用通过{$var_r}实现
- 列表
my @array = (1, 2, 3, 4, 5); my $array_r = \@array; my @array2 = @{$array_r}; #拷贝了数组 |
-
- 哈希与列表类似
- 对于数组引用,可以将${$ref}简记为$ref->,例如可以将${$ref}[2]简记为$ref->[2],而将${${ref}[2]}[1]简记为$ref->[2]->[1],并进一步简记为$ref->[2][1]
- 使用undef销毁一个引用:undef $ref; perl对于引用指向的数据维护一个引用计数,当计数减到0时引用指向的数据被销毁,内存空间被释放
- 使用引用使得表示复杂的数据结构成为可能。这些数据结构包括矩阵(多维数组)、链表、树、图等。
- 一些思考:C/C++的引用主要为了传地址。与C/C++中的指针、引用不同的是,perl引用除了传递地址外,还是perl中将标量(scalar)、列表(list)、哈希(hash)进行一般化(或者说统一)表示的机制,使用引用后,可以将标量、列表、哈希均表示为标量(因为地址本质上是一个无符号整型数,这一点与C中的void*有些类似)。通过引用,就可以解决perl中无法存储带有嵌套层次的列表、无法表示复杂数据结构的问题。
第12章:模块
- 模块是一个perl源代码文件,与普通的.pl源代码文件相比,模块有如下两个不同点:
- 扩展名不是pl,而是pm(这一条并不是强制条件)
- 最后有一句1;(或是return true;或是任何返回true值的语句)强制要求
- do用于在perl代码中的任意位置嵌入一个.pl脚本或是.pm模块,语法是do “filename”;perl将会在@INC中的路径下寻找filename
- 如果在main.pl中执行do ‘inc.pl’;需要注意的是inc.pl中的代码不能访问main.pl中定义的lexical变量
- require用于在perl代码中的任意位置嵌入一个模块,语法是require “filename”;或require modulename;当使用require modulename;时,modulename形如module::submodule::subsubmodule,代表文件./module/submodule/subsubmodule.pm
- 与do不同,require只支持嵌入模块,因此require的文件必须以返回true语句结束
- 对于一个文件只嵌入一次,即使写了多条对同一文件的require语句
- use用于在perl代码中的任一位置嵌入一个模块,语法是use modulename;modulename同require中的modulename
- 与require不同,use在编译前执行,也就是说,即使use中的模块写在代码中的最后一句,也会第一个执行
- | do | require | use |
支持 | 源码 模块 | 模块 | 模块 |
语法 | do ‘filename’; | require ‘filename’; require modulename; | use modulename; |
处理次数 | 嵌入几次,处理几次 | 仅一次 | 仅一次 |
文件不存在 | 跳过,不报错 | 报错 | 报错 |
执行时间 | 运行时 | 运行时 | 编译时(最先处理) |
- @INC变量也是一个普通perl列表,可以更改,因此可以自行向其中添加路径(使用unshift或者push)
- 模块文件的开头应该声明package packagename;packagename形如package::subpackage::subsubpackage,对于package::subpackage::subsubpackage中的函数sub1,调用方法是package::subpackage::subsubpackage::sub1
- 注意package声明的是模块名,而require和use使用的模块名实际上是路径名,和模块名并不一样,比如说有一个模块为./m1/test1.pm,其中的package声明却是package m1::test2;该包内包含一个函数sub1,则在main.pl中应该use/require m1::test1;而在调用时则应该写m1::test2::sub1()
- 当然,为了清晰、易管理,模块名和模块文件名、路径应该保持一致
- 可以使用Exporter类简化包内的函数调用写法。没有使用Exporter时,必须写形如package::subpackage::subsubpackage::sub1的调用,过于啰嗦,而在写包时继承Exporter即可:
package Acme::Webserver::LoggerExporter; # Acme/Webserver/LoggerExporter.pm use strict; use warnings; # become an exporter and export the functions use Exporter; use base 'Exporter'; our @EXPORT = qw(open_log close_log write_log log_level); 则调用open_log时就可以将全写调用: Acme::Webserver::LoggerExporter::open_log() 改为简写调用: open_log() |
- 也可以在use模块时声明要导入的模块:
use Data::Dumper qw(Dumper); # 可以直接调用Dumper() |
几个常用的包
- Data::Dumper是将变量序列化为perl语法的字符串的包,序列化列表和哈希时非常方便
- File::Find是一个遍历文件夹,对其中每一个文件进行处理的函数,用法是File::Find::find(\&wanted, “/home/simon/”);
- 首个参数wanted是一个回调函数,对每个文件应用。第二个参数是执行文件夹
- 每次执行回调时当前目录被切换为当前文件所在的目录
- 当前目录的相对路径为$File::Find::dir
- $_为当前文件的文件名
- $File::Find::name为当前文件的全名(包括目录)
- Getopt::Std和Getopt::Long是两个处理命令行参数的包,可以将形如-al的简写命令行参数解析为a和l两个参数,也可以将-a arg1 -l arg2这样的命令行参数解析为哈希映射
- File::Spec是一个处理路径字符串的包,包括路径字符串简化、路径叠加、路径解析等
- Benchmark是一个性能测试包,可将某一代码块重复执行若干次,测得性能参数
- Win32是一个封装了一些Win32 API的包,包括Win32::Sound、Win32::TieRegistry等
第13章:面向对象的Perl
- perl中并没有真正的“类”,所谓的类,其实是一个包
- 要定义一个类,声明一个package即可:package Person;
- 类的构造函数固定取名为new,即sub new {...}
- 初始化类对象通过$obj = new Person();或者$obj = Person->new();
- 构造函数sub new要点:
- 参数表的第一个参数(@_[0])是类名,第二个开始为调用构造函数时传入的参数
- 通过传入哈希实现类似成员变量的功能
- 生成对象引用后,必须使用bless()函数对引用的类型进行转换
- 最后一句必须返回生成的对象引用
#类定义 package Person; sub new { #此时_@为(‘Person’, ‘name’, ‘Carl’, ‘gender’, ‘male’) $classname = shift; #获得类名,此时$classname为’Person’, _@为(‘name’, ‘Carl’, ‘gender’, ‘male’) my $self = {@_}; #将传入参数转化为哈希,$self为(‘name’=>’Carl’, ‘gender’=>’male’) bless $self, $classname #将引用$self转化为$classname类型 return $self; #返回的Person对象本身是一个哈希,含有所有成员变量 } #类使用 $person = Person->new(‘name’=>’Carl’, ‘gender’=>’male’); |
- Package内定义的变量为类变量,即静态成员变量(static member variable),不能直接访问,必须定义访问器(accessor, 即get/set函数)
- 成员函数要点:
- 名字以下划线_开头的成员函数为私有的
- 函数的第一个参数(即@_[0])为对象引用,第二个参数开始为函数参数
#类定义 package Person; sub new {...} #省略 sub _init {...} #私有函数 sub name { my $self = shift; #取调用对象引用 my $name = shift; #取第二个参数 $self->{name} = $name if defined $name; #如果传入名字,则设置名字为传入值 return $self->{name}; #返回名字值 } #类使用 $person = Person->new(‘name’=>’Carl’, ‘gender’=>’male’); $person->name(‘Caesar’); #将名字设置为’Caesar’ print $person->name(), “\n”; #打印名字,将打印’Caesar’ |
- 对象的销毁参照11章中引用指向数据的销毁方法
后记
Perl给我留下深刻印象的地方:
- 简捷易用的文本I/O、正则表达式使Perl成为文本处理的利器
- 提供众多UNIX API,加上脚本语言的灵活性,Perl适合进行UNIX系统管理
个人感觉Perl中的两个难点,也是Perl的败笔:
- 引用(Reference)
- 列表自动一维化的机制莫名其妙。Perl中标量、列表和哈希拥有各自不同的词法标识($、@、%,列表、哈希内容均使用(),列表取值使用[],哈希使用{}),将其引用化后解引用又有一套各自不同的词法,很容易弄晕
- 面向对象(OO)
-
- Perl中的OO机制有点半残,单单是构造函数中必写的几行:
my $classname = shift; my $self = {@_}; bless $self, $classname; return $self; |
-
- 以及函数中第一句必写的my $self = shift;就让人十分讨厌,重复性劳动。如果这真的是一门OO语言,这些工作应该由编译器完成。
- 根据Wikipedia上Perl页面的介绍,OO是Perl 5中加入的新特性,这说明Perl最早并没有被设计为一门OO程序设计语言,所以Perl 5中的OO特性可以看成是在过程式语言中进行的一种升级。比如每个函数第一句就必有的my $self = shift;就与C++中的this指针神似,只不过C++作为一种新语言革命得比较彻底,this指针是由编译器自动提供的,不必手工获取。与C++相比,Perl里的OO更像是用C语言实现的OO,说到这里,有空可以去看看《Object-oriented Programming with ANSI-C》,这本书讲了用C语言实现OO特性的各个技术细节,“通过这本书你可以明白C++, Java, Python 等面向对象语言中的类、继承、实例、连接、方法、对象、多态... 都是如何实现的. 能让你通过C来写出优美并可以重用的代码.”(以上文字来自豆瓣网友Border