| 关键词: nbsp 文件 数组 数名 变量 pointer line 哈希 语法 函数 |
第十一章 文件系统 本章所讲的函数多数使用了UNIX操作系统的特性,在非UNIX系统中,一些函数可能没有定义或有不同的工作方式,使用时请查看Perl联机文档。一、文件输入/输出函数 本节讲述从文件中读取信息和向文件写入信息的内置库函数。1、基本I/O函数 一些I/O函数在前面的章节中已有讲述,如 open:允许程序访问文件 close:终止文件访问 print:文件写入字符串 write:向文件写入格式化信息 printf:格式化字符串并输出到文件 这里简单回顾一下,再讲一些前面未提到的函数。1)open函数 open函数将文件变量与某文件联系起来,提供访问文件的接口,例如:open(MYVAR, "/u/file"); 如果文件打开成功,则返回非零值,否则返回零。缺省地,open打开文件用以读取其内容,若想打开文件以写入内容,则在文件名前加个大于号:open(MYVAR, ">/u/file"); 向已有的文件末尾添加内容用两个大于号:open(MYVAR, ">>/u/file"); 若想打开文件作为数据导向的命令,则在命令前加上管道符(|):open(MAIL, "|mail dave");2)用open重定向输入 可以把打开的文件句柄用作向程序输入数据的命令,方法是在命令后加管道符(|),如: open(CAT, "cat file*|"); 对open的调用运行命令cat file* ,此命令创建一个临时文件,这个文件的内容是所有以file打头的文件的内容连接而成,此文件看作输入文件,可用文件变量CAT访问,如: $input = ; 下面的例子使用命令w的输出来列出当前登录的所有用户名。 1 : #!/usr/local/bin/perl2 :3 : open (WOUT, "w|");4 : $time = <WOUT>;5 : $time =~ s/^ *//;6 : $time =~ s/ .*//;7 : ; # skip headings line8 : @users = ;9 : close (WOUT);10: foreach $user (@users) {11: $user =~ s/ .*//;12: }13: print ("Current time: $time");14: print ("Users logged on:\n");15: $prevuser = "";16: foreach $user (sort @users) {17: if ($user ne $prevuser) {18: print ("\t$user");19: $prevuser = $user;20: }21: } 结果输出如下: Current time: 4:25pmUsers logged on: dave kilroy root zarquon w命令列出当前时间、系统负载和登录的用户,以及每个用户的作业时间和当前运行的命令,如: 4:25pm up 1 day, 6:37, 6 users, load average: 0.79, 0.36, 0.28 User tty login@ idle JCPU PCPU what dave ttyp0 2:26pm 27 3 w kilroy ttyp1 9:01am 2:27 1:04 11 -csh kilroy ttyp2 9:02am 43 1:46 27 rn root ttyp3 4:22pm 2 -csh zarquon ttyp4 1:26pm 4 43 16 cc myprog.c kilroy ttyp5 9:03am 2:14 48 /usr/games/hack 上例中从w命令的输出中取出所需的信息:当前时间和登录的用户名。第3行运行w命令,此处对open的调用指定w的输出用作程序的输入,用文件变量WOUT来访问该输入。第4行读取第一行信息,即: 4:25pm up 1 day, 6:37, 6 users, load average: 0.79, 0.36, 0.28 接下来的两行从这行中抽取出时间。首先,第5行删除起始的空格,然后第6行删去除时间和结尾换行符之间的所有字符,存入变量$time。 第7行从WOUT读取第二行,这行中无有用信息,故不作处理。第8行把剩下的行赋给数组@users,然后第9行关闭WOUT,终止运行w命令的进程。 @users中的每个元素都是一行用户信息,因为本程序只需要每行的第一个单词,即用户名,故10~12行去掉除换行符外的其它字符,这一循环结束后,@users中只剩下用户名的列表。 第13行输出存贮在$time中的时间,注意这时print不需要加上换行符,因为$time中有。16~21行对@users中的用户名排序并输出。因为同一个用户可以多次登录,所以用$preuser存贮输出的最后一个用户名,下次输出数组元素$user时,如果其与$preser相等,则不输出。3)文件重定向 许多UNIX shell可以把标准输出文件(STDOUT)和标准错误文件(STDERR)都重定向到同一个文件,例如在Bourne Shell(sh)中,命令 $ foo > file1 2>&1 运行命令foo并把输出到标准输出文件和标准错误文件的内容存贮到文件file1中。下面是用Perl实现这一功能的例子: 1: #!/usr/local/bin/perl2: 3: open (STDOUT, ">file1") || die ("open STDOUT failed");4: open (STDERR, ">&STDOUT") || die ("open STDERR failed");5: print STDOUT ("line 1\n");6: print STDERR ("line 2\n");7: close (STDOUT);8: close (STDERR); 运行后,文件file1中的内容为: line 2 line 1 可以看到,这两行并未按我们想象的顺序存贮,为什么呢?我们来分析一下这段程序。 第3行重定向标准输出文件,方法是打开文件file1将它与文件变量STDOUT关联,这也关闭了标准输出文件。第4行重定向标准错误文件,参数>&STDOUT告诉Perl解释器使用已打开并与STDOUT关联的文件,即文件变量STDERR指向与STDOUT相同的文件。第5、6行分别向STDOUT和STDERR写入数据,因为这两个文件变量指向同一个文件,故两行字符串均写到文件file1中,但顺序却是错误的,怎么回事呢? 问题在于UNIX对输出的处理上。当使用print(或其它函数)写入STDOUT等文件时,UNIX操作系统真正所做的是把数据拷贝到一片特殊的内存即缓冲区中,接下来的输出操作继续写入缓冲区直到写满,当缓冲区满了,就把全部数据实际输出。象这样先写入缓冲区再把整个缓冲区的内容输出比每次都实际输出所花费的时间要少得多,因为一般来说,I/O比内存操作慢得多。 程序结束时,任何非空的缓冲区都被输出,然而,系统为STDOUT和STDERR分别维护一片缓冲区,并且先输出STDERR的内容,因此存贮在STDERR的缓冲区中的内容line 2出现在存贮在STDOUT的缓冲区中的内容line 1之前。 为了解决这个问题,可以告诉Perl解释器不对文件使用缓冲,方法为: 1、用select函数选择文件 2、把值1赋给系统变量$| 系统变量$|指定文件是否进行缓冲而不管其是否应该使用缓冲。如果$|为非零值则不使用缓冲。$|与系统变量$~和$^协同工作,当未调用select函数时,$|影响当前缺省文件。下例保证了输出的次序: 1 : #!/usr/local/bin/perl2 : 3 : open (STDOUT, ">file1") || die ("open STDOUT failed");4 : open (STDERR, ">&STDOUT") || die ("open STDERR failed");5 : $| = 1;6 : select (STDERR);7 : $| = 1;8 : print STDOUT ("line 1\n");9 : print STDERR ("line 2\n");10: close (STDOUT);11: close (STDERR); 程序运行后,文件file1中内容为: line 1 line 2 第5行将$|赋成1,告诉Perl解释器当前缺省文件不进行缓冲,因为未调用select,当前的缺省文件为重定向到文件file1的STDOUT。第6行将当前缺省文件设为STDERR,第7行又设置$|为1,关掉了重定向到file1的标准错误文件的缓冲。由于STDOUT和STDERR的缓冲均被关掉,向其的输出立刻被写到文件中,因此line 1出现在第一行。4)指定读写权限 打开一个既可读又可写的文件方法是在文件名前加上"+>",如下: open (READWRITE, "+>file1"); 此语句打开既可读又可写的文件file1,即可以重写其中的内容。文件读写操作最好与库函数seek和tell一起使用,这样可以跳到文件任何一点。 注:也可用前缀"+<"指定可读写权限。5)close函数 用于关闭打开的文件。当用close关闭管道,即重定向的命令时,程序等待重定向的命令结束,如: open (MYPIPE, "cat file*|"); close (MYPIPE); 当关闭此文件变量时,程序暂停运行,直到命令cat file*运行完毕。6)print, printf和write函数 print是这三个函数中最简单的,它向指定的文件输出,如果未指定,则输出到当前缺省文件中,如: print ("Hello, there!\n"); print OUTFILE ("Hello, there!\n"); 第一句输出到当前缺省文件中,若未调用select,则为STDOUT。第二句输出到由文件变量OUTFILE指定的文件中。 printf函数先格式化字符串再输出到指定文件或当前缺省文件中,如: printf OUTFILE (“You owe me %8.2f", $owing); 此语句取出变量$owing的值并替换掉串中的%8.2f,%8.2f是域格式的例子,把$owing的值看作浮点数。 write函数使用输出格式把信息输出到文件中,如: select (OUTFILE); $~ = "MYFORMAT"; write; 关于printf和write,详见《第x章 格式化输出》。7)select函数 select函数将通过参数传递的文件变量指定为新的当前缺省文件,如: select (MYFILE); 这样,MYFILE就成了当前缺省文件,当对print、write和printf的调用未指定文件时,就输出到MYFILE中。8)eof函数 eof函数查看最后一次读文件操作是否为文件最后一个记录,如果是,则返回非零值,如果文件还有内容,返回零。 一般情况下,对eof的调用不加括号,因为eof和eof()是等效的,但与<>操作符一起使用时,eof和eof()就不同了。现在我们来创建两个文件,分别叫做file1和file2。file1的内容为: This is a line from the first file. Here is the last line of the first file. file2的内容为: This is a line from the second and last file. Here is the last line of the last file. 下面就来看一下eof和eof()的区别,第一个程序为: 1: #!/usr/local/bin/perl2: 3: while ($line = <>) {4: print ($line);5: if (eof) {6: print ("-- end of current file --\n");7: }8: } 运行结果如下: $ program file1 file2This is a line from the first file.Here is the last line of the first file.-- end of current file --This is a line from the second and last file.Here is the last line of the last file.-- end of current file --$ 下面把eof改为eof(),第二个程序为: 1: #!/usr/local/bin/perl2: 3: while ($line = <>) {4: print ($line);5: if (eof()) {6: print ("-- end of output --\n");7: }8: } 运行结果如下: $ program file1 file2This is a line from the first file.Here is the last line of the first file.This is a line from the second and last file.Here is the last line of the last file.-- end of output --$ 这时,只有所有文件都读过了,eof()才返回真,如果只是多个文件中前几个的末尾,返回值为假,因为还有要读取的输入。9)间接文件变量 对于上述各函数open, close, print, printf, write, select和eof,都可以用简单变量来代替文件变量,这时,简单变量中所存贮的字符串就被看作文件变量名,下面就是这样一个例子,此例很简单,就不解释了。需要指出的是,函数open, close, write, select和eof还允许用表达式来替代文件变量,表达式的值必须是字符串,被用作文件变量名。 1: #!/usr/local/bin/perl2: 3: &open_file("INFILE", "", "file1");4: &open_file("OUTFILE", ">", "file2");5: while ($line = &read_from_file("INFILE")) {6: &print_to_file("OUTFILE", $line);7: }8: 9: sub open_file {10: local ($filevar, $filemode, $filename) = @_;11: 12: open ($filevar, $filemode . $filename) ||13: die ("Can't open $filename");14: }15: sub read_from_file {16: local ($filevar) = @_;17: 18: <$filevar>;19: }20: sub print_to_file {21: local ($filevar, $line) = @_;22: 23: print $filevar ($line);24: } 2、跳过和重读数据 函数名 seek 调用语法 seek (filevar, distance, relative_to); 解说 在文件中向前/后移动,有三个参数:1、filevar,文件变量2、distance,移动的字节数,正数向前移动,负数往回移动3、reletive_to,值可为0、1或2。为0时,从文件头开始移动,为1时,相对于当前位置(将要读的下一行)移动,为2时,相对于文件末尾移动。运行成功返回真(非零值),失败则返回零,常与tell函数合用。 函数名 tell 调用语法 tell (filevar); 解说 返回从文件头到当前位置的距离。注意:1、seek和tell不能用于指向管道的文件变量。2、seek和tell中文件变量参数可使用表达式。 3、系统读写函数 函数名 read 调用语法 read (filevar, result, length, skipval); 解说 read函数设计得与UNIX的fread函数等效,可以读取任意长度的字符(字节)存入一个简单变量。其参数有四个:1、filevar:文件变量2、result:存贮结果的简单变量(或数组元素)3、length:读取的字节数4、skipval:可选项,指定读文件之前跳过的字节数。返回值为实际读取的字节数,如果已到了文件末尾,则返回零,如果出错,则返回空串。 函数名 sysread 调用语法 sysread (filevar, result, length, skipval); 解说 更快的读取数据,与UNIX函数read等效,参数与read相同。 函数名 syswrite 调用语法 syswrite (filevar, data, length, skipval); 解说 更快的写入数据,与UNIX函数write等效,参数:1、filevar:将要写入的文件2、data:存贮要写入数据的变量3、length:要写入的字节数4、skipval写操作之前跳过的字节数。 4、用getc读取字符 函数名 getc 调用语法 $char = getc (infile); 解说 从文件中读取单个字符。 5、用binmode读取二进制文件 函数名 binmode 调用语法 binmode (filevar); 解说 当你的系统(如类DOS系统)对文本文件和二进制文件有所区别时使用。必须在打开文件后、读取文件前使用。 二、目录处理函数 函数名 mkdir 调用语法 mkdir (dirname, permissions); 解说 创建新目录,参数为:1、dirname:将要创建的目录名,可以为字符串或表达式2、permissions:8进制数,指定目录的访问权限,其值和意义见下表,权限的组合方法为将相应的值相加。 值 权限 4000 运行时设置用户ID 2000 运行时设置组ID 1000 粘贴位 0400 拥有者读权限 0200 拥有者写权限 0100 拥有者执行权限 0040 组读权限 0020 组写权限 0010 组执行权限 0004 所有人读权限 0002 所有人写权限 0001 所有人执行权限 函数名 chdir 调用语法 chdir (dirname); 解说 改变当前工作目录。参数dirname可以为字符串,也可以为表达式。 函数名 opendir 调用语法 opendir (dirvar, dirname); 解说 打开目录,与下面几个函数合用,可查看某目录中文件列表。参数为:1、dirvar:目录变量,与文件变量类似2、dirname:目录名,可为字符串或表达式成功返回真值,失败返回假。注:程序中可用同名的目录变量和文件变量,根据环境确定取成分。 函数名 closedir 调用语法 closedir (mydir); 解说 关闭打开的目录。 函数名 readdir 调用语法 readdir (mydir); 解说 赋给简单变量时,每次赋予一个文件或子目录名,对数组则赋予全部文件和子目录名。 函数名 telldir 调用语法 location = telldir (mydir); 解说 象在文件中前后移动一样,telldir和下面的seekdir用于在目录列表中前后移动。 函数名 seekdir 调用语法 seekdir(mydir, location); 解说 location必须为telldir返回的值。 函数名 rewinddir 调用语法 rewinddir (mydir); 解说 将读取目录的位置重置回开头,从而可以重读目录列表。 函数名 rmdir 调用语法 rmdir (dirname); 解说 删除空目录。成功则返回真(非零值),失败返回假(零值)。 三、文件属性函数1、文件重定位函数 函数名 rename 调用语法 rename (oldname, newname); 解说 改变文件名或移动到另一个目录中,参数可为字符串或表达式。 函数名 unlink 调用语法 num = unlink (filelist); 解说 删除文件。参数为文件名列表,返回值为实际删除的文件数目。此函数之所以叫unlink而不叫delete是因为它实际所做的是删除文件的链接。 2、链接和符号链接函数 函数名 link 调用语法 link (newlink, file); 解说 创建现有文件的链接--硬链接,file是被链接的文件,newlink是被创建的链接。成功返回真,失败返回假。当删除这两个链接中的一个时,还可以用另一个来访问该文件。 函数名 symlink 调用语法 symlink (newlink, file); 解说 创建现有文件的符号链接,即指向文件名,而不是指向文件本身。参数和返回值同上。当原文件被删除(如:被unlinke函数删除),则被创建链接不可用,除非再创建一个与原被链接的文件同名的文件。 函数名 readlink 调用语法 filename = readlink (linkname); 解说 如果linkname为符号链接文件,返回其实际指向的文件。否则返回空串。 3、文件许可权函数 函数名 chmod 调用语法 chmod (permissions, filelist); 解说 改变文件的访问权限。参数为:1、permissions为将要设置的权限,其含义见上述mkdir中权限表2、filelist为欲改变权限的文件列表 函数名 chown 调用语法 chown (userid, groupid, filelist); 解说 改变文件的属主,有三个参数:1、userid:新属主的(数字)ID号2、groupid:新的组(数字)ID号,-1为保留原组3、filelist:欲改变属主的文件列表 函数名 umask 调用语法 oldmaskval = umask (maskval); 解说 设置文件访问权限掩码,返回值为当前掩码。 4、其它属性函数 函数名 truncate 调用语法 truncate (filename, length); 解说 将文件的长度减少到length字节。如果文件长度已经小于length,则不做任何事。其中filename可以为文件名,也可以为文件变量 函数名 stat 调用语法 stat (file); 解说 获取文件状态。参数file可为文件名也可为文件变量。返回列表元素依次为: 文件所在设备 内部参考号(inode) 访问权限 硬链接数 属主的(数字)ID 所属组的(数字)ID 设备类型(如果file是设备的话) 文件大小(字节数) 最后访问时间 最后修改时间最后改变状态时间 I/O操作最佳块大小 分配给该文件的块数 函数名 lstat 调用语法 lstat (file); 解说 与stat类似,区别是将file看作是符号链接。 函数名 time 调用语法 currtime = time(); 解说 返回从1970年1月1日起累计秒数。 函数名 gmtime 调用语法 timelist = gmtime (timeval); 解说 将由time, stat 或 -A 和 -M 文件测试操作符返回的时间转换成格林威治时间。返回列表元素依次为: 秒 分钟 小时,0~23 日期 月份,0~11(一月~十二月) 年份 星期,0~6(周日~周六) 一年中的日期,0~364 是否夏令时的标志详见UNIX的gmtime帮助。 函数名 localtime 调用语法 timelist = localtime (timeval); 解说 与gmtime类似,区别为将时间值转换为本地时间。 函数名 utime 调用语法 utime (acctime, modtime, filelist); 解说 改变文件的最后访问时间和最后更改时间。例如:$acctime = -A "file1";$modtime = -M "file1";@filelist = ("file2", "file3");utime ($acctime, $modtime, @filelist); 函数名 fileno 调用语法 filedesc = fileno (filevar); 解说 返回文件的内部UNIX文件描述。参数filevar为文件变量。 函数名 fcntlflock 调用语法 fcntl (filevar, fcntlrtn, value);flock (filevar, flockop); 解说 详见同名UNIX函数帮助。 四、使用DBM文件 Perl中可用关联数组来访问DBM文件,所用函数为dbmopen和dbmclose,在Perl5中,已用tie和untie代替。 函数名 dbmopen 调用语法 dbmopen (array, dbmfilename, permissions); 解说 将关联数组与DBM文件相关联。参数为:1、array:所用关联数组2、dbmfilename:将打开的DBM文件名3、访问权限,详见mkdir 函数名 dbmclose 调用语法 dbmclose (array); 解说 关闭DBM文件,拆除关联数组与之的关系。 第十二章 Perl5中的引用/指针 一、引用简介 引用就是指针,可以指向变量、数组、哈希表(也叫关联数组)甚至子程序。Pascal或C程序员应该对引用(即指针)的概念很熟悉,引用就是某值的地址,对其的使用则取决于程序员和语言的规定。在Perl中,可以把引用称为指针,二者是通用的,无差别的。引用在创建复杂数据方面十分有用。 Perl5中的两种引用类型为硬引用和符号引用。符号引用含有变量的名字,它对运行时创建变量名并定位很有用,基本上,符号引用就象文件名或UNIX系统中的软链接。而硬引用则象文件系统中的硬链接。 Perl4只允许符号引用,给使用造成一些困难。例如,只允许通过名字对包的符号名哈希表(名为_main{})建立索引。Perl5则允许数据的硬引用,方便多了。 硬引用跟踪引用的计数,当其数为零时,Perl自动将被引用的项目释放,如果该项目是对象,则析构释放到内存池中。Perl本身就是个面向对象的语言,因为Perl中的任何东西都是对象,包和模块使得对象更易于使用。 简单变量的硬引用很简单,对于非简单变量的引用,你必须显式地解除引用并告诉其应如何做,详见《第 章Perl中的面向对象编程》。二、使用引用 本章中,简单变量指像$pointer这样的变量,$pointer仅含一个数据项,其可以为数字、字符串或地址。 任何简单变量均可保存硬引用。因为数组和哈希表含有多个简单变量,所以可以建立多种组合而成的复杂的数据结构,如数组的数组、哈希表的数组、子程序的哈希表等等。只要你理解其实只是在用简单变量在工作,就应该可以正确的在最复杂的结构中正确地解除引用。 首先来看一些基本要点。 如果$pointer的值为一个数组的指针,则通过形式@$pointer来访问数组中的元素。形式@$pointer的意义为“取出$pointer中的地址值当作数组使用”。类似的,%$pointer为指向哈希表中第一个元素的引用。 有多种构建引用的方法,几乎可以对任何数据建立引用,如数组、简单变量、子程序、文件句柄,以及--C程序员会感兴趣的--引用。Perl使你有能力写出把自己都搞糊涂的极其复杂的代码。:) 下面看看Perl中创建和使用引用的方法。三、使用反斜线(\)操作符 反斜线操作符与C语言中传递地址的操作符&功能类似。一般是用\创建变量又一个新的引用。下面为创建简单变量的引用的例子: $variavle = 22; $pointer = \$variable; $ice = "jello"; $iceprt = \$ice; 引用$pointer指向存有$variable值的位置,引用$iceptr指向"jello"。即使最初的引用$variable销毁了,仍然可以通过$pointer访问该值,这是一个硬引用,所以必须同时销毁$pointer和$variable以便该空间释放到内存池中。 在上面的例子中,引用变量$pointer存的是$variable的地址,而不是值本身,要获得值,形式为两个$符号,如下: #!/usr/bin/perl$value = 10;$pointer = \$value;printf "\n Pointer Address $pointer of $value \n";printf "\n What Pointer *($pointer) points to $$pointer\n"; 结果输出如下: Pointer Address SCALAR(0x806c520) of 10What Pointer *(SCALAR(0x806c520)) points to 10 每次运行,输出结果中的地址会有所改变,但可以看到$pointer给出地址,而$$pointer给出$variable的值。 看一下地址的显示,SCALAR后面一串十六进制,SCALAR说明该地址指向简单变量(即标量),后面的数字是实际存贮值的地址。 注意:指针就是地址,通过指针可以访问该地址处存贮的数据。如果指针指向了无效的地址,就会得到不正确的数据。通常情况下,Perl会返回NULL值,但不该依赖于此,一定要在程序中把所有的指针正确地初始化,指向有效的数据项。四、引用和数组 关于Perl语言应该记住的最重要的一点可能是:Perl中的数组和哈希表始终是一维的。因此,数组和哈希表只保存标量值,不直接存贮数组或其它的复杂数据结构。数组的成员要么是数(或字符串)要么是引用。 对数组和哈希表可以象对简单变量一样使用反斜线操作符,数组的引用如下: 1 #!/usr/bin/perl2 #3 # Using Array references4 #5 $pointer = \@ARGV;6 printf "\n Pointer Address of ARGV = $pointer\n";7 $i = scalar(@$pointer);8 printf "\n Number of arguments : $i \n";9 $i = 0;10 foreach (@$pointer) {11 printf "$i : $$pointer[$i++]; \n";12 } 运行结果如下: $ test 1 2 3 4 Pointer Address of ARGV = ARRAY(0x806c378)Number of arguments : 40 : 1;1 : 2;2 : 3;3 : 4; 第5行将引用$pointer指向数组@ARGV,第6行输出ARGV的地址。$pointer返回数组第一个元素的地址,这与C语言中的数组指针是类似的。第7行调用函数scalar()获得数组的元素个数,该参数亦可为@ARGV,但用指针则必须用@$pointer的形式指定其类型为数组,$pointer给出地址,@符号说明传递的地址为数组的第一个元素的地址。第10行与第7行类似,第11行用形式$$pointer[$i]列出所有元素。 对关联数组使用反斜线操作符的方法是一样的--把所有关联数组名换成引用$poniter。注意数组和简单变量(标量)的引用显示时均带有类型--ARRAY和SCALAR,哈希表(关联数组)和函数也一样,分别为HASH和CODE。下面是哈希表的引用的例子。 #!/usr/bin/perl1 #2 # Using Associative Array references3 #4 %month = (5 '01', 'Jan',6 '02', 'Feb',7 '03', 'Mar',8 '04', 'Apr',9 '05', 'May',10 '06', 'Jun',11 '07', 'Jul',12 '08', 'Aug',13 '09', 'Sep',14 '10', 'Oct',15 '11', 'Nov',16 '12', 'Dec',17 );1819 $pointer = \%month;2021 printf "\n Address of hash = $pointer\n ";22 23 #24 # The following lines would be used to print out the25 # contents of the associative array if %month was used.26 #27 # foreach $i (sort keys %month) {28 # printf "\n $i $$pointer{$i} ";29 # }3031 #32 # The reference to the associative array via $pointer33 #34 foreach $i (sort keys %$pointer) {35 printf "$i is $$pointer{$i} \n";36 } 结果输出如下: $ mthAddress of hash = HASH(0x806c52c)01 is Jan02 is Feb03 is Mar04 is Apr05 is May06 is Jun07 is Jul08 is Aug09 is Sep10 is Oct11 is Nov12 is Dec 与数组类似,通过引用访问哈希表的元素形式为$$pointer{$index},当然,$index是哈希表的键值,而不仅是数字。还有几种访问形式,此外,构建哈希表还可以用=>操作符,可读性更好些。下面再看一个例子: 1 #!/usr/bin/perl2 #3 # Using Array references4 #5 %weekday = (6 '01' => 'Mon',7 '02' => 'Tue',8 '03' => 'Wed',9 '04' => 'Thu',10 '05' => 'Fri',11 '06' => 'Sat',12 '07' => 'Sun',13 );14 $pointer = \%weekday;15 $i = '05';16 printf "\n ================== start test ================= \n";17 #18 # These next two lines should show an output19 #20 printf '$$pointer{$i} is ';21 printf "$$pointer{$i} \n";22 printf '${$pointer}{$i} is ';23 printf "${$pointer}{$i} \n";24 printf '$pointer->{$i} is ';2526 printf "$pointer->{$i}\n";27 #28 # These next two lines should not show anything 29 #30 printf '${$pointer{$i}} is ';31 printf "${$pointer{$i}} \n";32 printf '${$pointer->{$i}} is ';33 printf "${$pointer->{$i}}";34 printf "\n ================== end of test ================= \n";35 结果输出如下: ================== start test =================$$pointer{$i} is Fri${$pointer}{$i} is Fri$pointer->{$i} is Fri${$pointer{$i}} is${$pointer->{$i}} is================== end of test ================= 可以看到,前三种形式的输出显示了预期的结果,而后两种则没有。当你不清楚是否正确时,就输出结果看看。在Perl中,有不明确的代码就用print语句输出来实验一下,这能使你清楚Perl是怎样解释你的代码的。五、多维数组 语句@array = list;可以创建数组的引用,中括号可以创建匿名数组的引用。下面语句为用于画图的三维数组的例子: $line = ['solid' , 'black' , ['1','2','3'] , ['4','5','6']]; 此语句建立了一个含四个元素的三维数组,变量$line指向该数组。前两个元素是标量,存贮线条的类型和颜色,后两个元素是匿名数组的引用,存贮线条的起点和终点。访问其元素语法如下: $arrayReference->[$index] single-dimensional array $arrayReference->[$index1][$index2] two-dimensional array $arrayReference->[$index1][$index2][$index3] three-dimensional array 可以创建在你的智力、设计经验和计算机的内存允许的情况下极尽复杂的结构,但最好对可能读到或管理你的代码的人友好一些--尽量使代码简单些。另一方面,如果你想向别人炫耀你的编程能力,Perl给你足够的机会和能力编写连自己都难免糊涂的代码。:) 建议:当你想使用多于三维的数组时,最好考虑使用其它数据结构来简化代码。 下面为创建和使用二维数组的例子: 1 #!/usr/bin/perl2 #3 # Using Multi-dimensional Array references4 #5 $line = ['solid', 'black', ['1','2','3'] , ['4', '5', '6']];6 print "\$line->[0] = $line->[0] \n";7 print "\$line->[1] = $line->[1] \n";8 print "\$line->[2][0] = $line->[2][0] \n";9 print "\$line->[2][1] = $line->[2][1] \n";10 print "\$line->[2][2] = $line->[2][2] \n";11 print "\$line->[3][0] = $line->[3][0] \n";12 print "\$line->[3][1] = $line->[3][1] \n";13 print "\$line->[3][2] = $line->[3][2] \n";14 print "\n"; # The obligatory output beautifier. 结果输出如下: $line->[0] = solid$line->[1] = black$line->[2][0] = 1$line->[2][1] = 2$line->[2][2] = 3$line->[3][0] = 4$line->[3][1] = 5$line->[3][2] = 6 那么三维数组又如何呢?下面是上例略为改动的版本。 1 #!/usr/bin/perl2 #3 # Using Multi-dimensional Array references again4 #5 $line = ['solid', 'black', ['1','2','3', ['4', '5', '6']]];6 print "\$line->[0] = $line->[0] \n";7 print "\$line->[1] = $line->[1] \n";8 print "\$line->[2][0] = $line->[2][0] \n";9 print "\$line->[2][1] = $line->[2][1] \n";10 print "\$line->[2][2] = $line->[2][2] \n";11 print "\$line->[2][3][0] = $line->[2][3][0] \n";12 print "\$line->[2][3][1] = $line->[2][3][1] \n";13 print "\$line->[2][3][2] = $line->[2][3][2] \n";14 print "\n"; 结果输出如下: $line->[0] = solid $line->[1] = black $line->[2][0] = 1 $line->[2][1] = 2 $line->[2][2] = 3 $line->[2][3][0] = 4 $line->[2][3][1] = 5 $line->[2][3][2] = 6 访问第三层元素的方式形如$line->[2][3][0],类似于C语言中的Array_pointer[2][3][0]。本例中,下标均为数字,当然亦可用变量代替。用这种方法可以把数组和哈希表结合起来构成复杂的结构,如下: 1 #!/usr/bin/perl2 #3 # Using Multi-dimensional Array and Hash references4 #5 %cube = (6 '0', ['0', '0', '0'],7 '1', ['0', '0', '1'],8 '2', ['0', '1', '0'],9 '3', ['0', '1', '1'],10 '4', ['1', '0', '0'],11 '5', ['1', '0', '1'],12 '6', ['1', '1', '0'],13 '7', ['1', '1', '1']14 );15 $pointer = \%cube;16 print "\n Da Cube \n";17 foreach $i (sort keys %$pointer) {18 $list = $$pointer{$i};19 $x = $list->[0];20 $y = $list->[1];21 $z = $list->[2];22 printf " Point $i = $x,$y,$z \n";23 } 结果输出如下: Da Cube Point 0 = 0,0,0 Point 1 = 0,0,1 Point 2 = 0,1,0 Point 3 = 0,1,1 Point 4 = 1,0,0 Point 5 = 1,0,1 Point 6 = 1,1,0 Point 7 = 1,1,1 这是一个定义立方体的例子。%cube中保存的是点号和坐标,坐标是个含三个数字的数组。变量$list获取坐标数组的引用:$list = $$ pointer{$i}; 然后访问各坐标值:$x = $list->[0]; ... 也可用如下方法给$x、$y和$z赋值:($x,$y,$z) = @$list; 使用哈希表和数组时,用$和用->是类似的,对数组而言下面两个语句等效: $$names[0] = "kamran"; $names->[0] = "kamran"; 对哈希表而言下面两个语句等效: $$lastnames{"kamran"} = "Husain"; $lastnames->{"kamran"} = "Husain"; Perl中的数组可以在运行中创建和扩展。当数组的引用第一次在等式左边出现时,该数组自动被创建,简单变量和多维数组也是一样。如下句,如果数组contours不存在,则被创建: $contours[$x][$y][$z] = &xlate($mouseX, $mouseY);六、子程序的引用 perl中子程序的引用与C中函数的指针类似,构造方法如下: $pointer_to_sub = sub {... declaration of sub ...}; 通过所构造的引用调用子程序的方法为: &$pointer_to_sub(parameters); 子程序模板 子程序的返回值不仅限于数据,还可以返回子程序的引用。返回的子程序在调用处执行,但却是在最初被创建的调用处被设置,这是由Perl对Closure处理的方式决定的。Closure意即如果你定义了一个函数,它就以最初定义的内容运行。(Closure详见OOP的参考书)下面的例子中,设置了多个错误信息显示子程序,这样的子程序定义方法可用于创建模板。 #!/usr/bin/perlsub errorMsg { my $lvl = shift; # # define the subroutine to run when called. # return sub { my $msg = shift; # Define the error type now. print "Err Level $lvl:$msg\n"; }; # print later. }$severe = errorMsg("Severe");$fatal = errorMsg("Fatal");$annoy = errorMsg("Annoying");&$severe("Divide by zero");&$fatal("Did you forget to use a semi-colon?");&$annoy("Uninitialized variable in use"); 结果输出如下: Err Level Severe:Divide by zeroErr Level Fatal:Did you forget to use a semi-colon?Err Level Annoying:Uninitialized variable in use 上例中,子程序errorMsg使用了局域变量$lvl,用于返回给调用者。当errorMsg被调用时,$lvl的值设置到返回的子程序内容中,虽然是用的my函数。三次调用设置了三个不同的$lvl变量值。当errorMsg返回时,$lvl的值保存到每次被声明时所产生的子程序代码中。最后三句对产生的子程序引用进行调用时$msg的值被替换,但$lvl的值仍是相应子程序代码创建时的值。 很混淆是吗?是的,所以这样的代码在Perl程序中很少见。七、数组与子程序 数组利于管理相关数据,本节讨论如何向子程序传递多个数组。前面我们讲过用@_传递子程序的参数,但是@_是一个单维数组,不管你传递的参数是多少个数组,都按序存贮在@_中,故用形如my(@a,@b)=@_; 的语句来获取参数值时,全部值都赋给了@a,而@b为空。那么怎么把一个以上的数组传递给子程序呢?方法是用引用。见下例: #!/usr/bin/perl@names = (mickey, goofy, daffy );@phones = (5551234, 5554321, 666 );$i = 0;sub listem { my ($a,$b) = @_; foreach (@$a) { print "a[$i] = " . @$a[$i] . " " . "\tb[$i] = " . @$b[$i] ."\n"; $i++; }}&listem(\@names, \@phones); 结果输出如下: a[0] = mickey b[0] = 5551234 a[1] = goofy b[1] = 5554321 a[2] = daffy b[2] = 666 注意: 1、当想传递给子程序的参数是多于一个的数组时一定要使用引用。2、一定不要在子程序中使用形如 (@variable)=@_; 的语句处理参数,除非你想把所有参数集中到一个长的数组中。八、文件句柄的引用 有时,必须将同一信息输出到不同的文件,例如,某程序可能在一个实例中输出到屏幕,另一个输出到打印机,再一个输出到记录文件,甚至同时输出到这三个文件。相比较于每种处理写一个单独的语句,可以有更好的实现方式如下: spitOut(\*STDIN); spitOut(\*LPHANDLE); spitOut(\*LOGHANDLE); 其中子程序spitOut的代码如下: sub spitOut { my $fh = shift; print $fh "Gee Wilbur, I like this lettuce\n";} 注意其中文件句柄引用的语法为\*FILEHANDLE。 |
|
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系
[邮箱地址] 删除
|