《Beginning Perl》读书笔记4:11~13章

  • 引用一律声明为标量类型(即$开头的命名变量),使用\运算符取引用
    • 对引用变量的修改等同于对引用指向实际数据的修改
    • 取变量引用: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中的模块写在代码中的最后一句,也会第一个执行
-dorequireuse
支持源码 模块模块模块
语法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