子程序(subroutine)

  • perl中的子程序其实就是自定义函数。它使用sub关键字开头,表示声明一个子程序
  • 子程序名称有独立的名称空间,不会和其它名称冲突
  • Perl中的子程序中可以定义、引用、修改全局变量,这和几乎所有的语言都不同。当然,也可以定义局部变量
  • perl中使用&SUB_NAME()的方式调用SUB_NAME子程序,&有时候可以省略,括号有时候也可以省略。具体的规则见后面
sub mysub {
$n += 1;
print "\$n is: $n","\n";
}
&mysub;
print "\$n is: $n","\n";
  • perl的子程序最后一个执行的语句的结果或返回值就是子程序的返回值(是最后一个执行的语句,不是最后一行语句)。所以子程序总会有返回值,但返回值并不总是有用的返回值
sub mysub {
$n += 2;
print "hello world\n";
$n + 3;
}
$num = ⊂

这里的&sub有返回值,它的返回值是最后执行的语句$n+3,并将返回值赋值给$num,所以$num的值为5。

如果把上面的print作为最后一句:

sub mysub {
$n += 2;
$n + 3; # 这不是返回值
print "hello world\n"; # 这是返回值
}
$num = ⊂

上面的print才是子程序的返回值,这个返回值是什么?这是一个输出语句,它的返回值为1,表示成功打印。这个返回值显然没什么用。所以,子程序最后执行的语句一定要小心检查。

那上面的$n+3的结果是什么?它的上下文是void context,相加的结果会被丢弃。

子程序还可以返回列表。

($m,$n)=(3,5);
sub mysub {
$m..($n+1);
}
@arr=&mysub;
print @arr; # 输出3456
  • 如果想在子程序的中间退出子程序,就可以使用return子句来返回值。当然,如果这个返回语句是最后一行,就可以省略return
# 返回一个偶数
$n=20;
sub mysub {
if ($n % 2 == 0){
return $n;
}
$n-1; # 等价于 return $n-1;
}
$oushu=&mysub;
print $oushu,"\n";

子程序参数

Perl子程序除了可以直接操作全局变量,还可以传递参数。例如:

&mysub(arg1,arg2...);
&mysub arg1,arg2...;

至于什么时候可以省略括号,后面再解释。

  • 调用子程序时传递的参数会传入子程序,它们存储在一个特殊的数组变量@_
  • 既然@_是数组,就可以使用$_[index]的方式引用数组中的各元素,也就是子程序的各个参数
  • @_数组只在每个子程序执行期间有效,每个子程序的@_都互不影响。子程序执行完成,@_将复原为原来的值
# 返回最大值
@_=qw(perl python shell); # 测试:先定义数组@_
sub mysub {
if($_[0] > $_[1]){
$_[0];
}else{
$_[1];
}
}
$max = &mysub(10,20);
print $max,"\n";
print @_; # 子程序执行完,@_复原为qw(perl python shell)

如果上面的子程序给的参数不是两个,而是3个或者1个(&mysub(10,20,30);&mysub(10);)会如何?因为参数是存在数组中的,所以给的参数多了,子程序用不到它,所以忽略多出的参数,如果给的参数少了,那么缺少的那些参数被引用时,值将是undef。

所以,在逻辑上来说,参数多少不会影响子程序的错误,但结果可能会受到一些影响。但可以在子程序中判断子程序的参数个数:

if(@_ != 2){     # 如果参数个数不是2个,就退出
return 1;
}

这里的比较是标量上下文,@_返回的是参数个数。

调用子程序的几种方式分析

一般情况下,调用我们自定义的子程序时,都使用&符号,有时候还要带上括号传递参数。

&mysub1();
&mysub2;
&mysub3 arg1,arg2;
&mysub4(arg1,arg2);

但有时候,&符号和括号是可以省略的。主要的规则是:

  • 只有子程序定义语句在子程序调用语句前面,才可以省略括号
  • 当使用了括号,perl已经知道这就是一个子程序调用,这时可以省略&也可以不省略&
  • 不能省略&的情况比较少。基本上,只要子程序名称不和内置函数同名,或者有特殊需求时(如需要明确子程序的名称时,如defined(&mysub)),都可以省略&
  • 不能在使用了&、有参数传递的情况下,省略括号
  • 最安全、最保险的调用方式是:
    • 有参数时:&subname(arg1,arg2),即不省略&和括号
    • 无参数时:&subname
    • 使用&的调用方式是比较古老的行为,虽然安全。但直接使用括号调用也基本无差别,但却更现代,所以建议用func()的方式调用自定义的子程序
sub mysub{
print @_,"\n";
}
# 先定义了子程序
mysub; # 正常调用
mysub(); # 正常调用
mysub("hello","world3"); # 正常调用
mysub "hello","world4"; # 正常调用
&mysub; # 安全的调用
&mysub("hello","world6"); # 安全的调用
&mysub "hello","world7"; # 本调用错误,因为使用了&,且有参数

上面是先定义子程序,再调用子程序的。下面是先调用子程序,再定义子程序的。

mysub;                      # 本调用无括号,不报错,当做内置函数执行,但无此内置函数,所以忽略
mysub(); # 有括号,不报错
mysub("hello","world3"); # 有括号,不报错
mysub "hello","world4"; # 无括号,本调用错误
&mysub "hello","world7"; # 本调用错误
&mysub; # 安全的调用
&mysub("hello","world6"); # 安全的调用 sub mysub{
print @_,"HELLO","\n";
}

如果子程序名称和内置函数同名,则不安全的调用方式总会优先调用内置函数。

子程序中的私有变量:my关键字

my关键字可以声明局部变量、局部数组。它可以用在任何类型的语句块内,而不限于sub子程序中。

sub mysub {
my $a=0; # 一次只能声明一个局部目标
my $b; # 声明变量,初始undef
my @arr; # 声明空数组
my($m,$n); # 一次可以声明多个局部目标
($m,$n)=@_; # 将函数参数赋值给$m,$n
my($x,$y) = @_; # 一步操作
} foreach my $var (@arr) { # 将控制变量定义为局部变量
my($sq) = $_ * $_; # 在foreach语句块内定义局部变量
}

在my定义局部变量的时候,需要注意列表上下文和标量上下文:

@_=qw(perl shell python);
my $num = @_; # 标量上下文
my (@num) = @_; # 列表上下文
print $num,"\n"; # 返回3
print @num,"\n"; # 返回perlshellpython

在Perl中,除了my可以修饰作用域,还有local和our也可以修饰作用域,它们之间的区别参见:Perl的our、my、local的区别

state关键字

my关键字是让变量、数组、哈希私有化,state关键字则是让私有变量、数组、哈希持久化。注意两个关键字:私有,持久化。

使用state关键字声明、初始化的变量对外不可见,但对其所在子程序是持久的:每次调用子程序后的变量值都保存着,下次再调用子程序会继承这个值。

这个特性是perl 5.10版才引入的,所以必须加上use 5.010;语句才能使用该功能。

use 5.010;
$n=22;
sub mysub {
state $n += 1;
print "Hello,$n\n";
}
&mysub; # 输出Hello,1
print $n,"\n"; # 输出22
&mysub; # 输出Hello,2
print $n,"\n"; # 输出22

当然,如果在子程序中每次都用state将变量强制初始化,那么这个变量持久与否就无所谓了,这时用my关键字的效果是一样的。

use 5.010;
sub mysub {
state $n=0;
print "hello,$n","\n";
}
&mysub;
&mysub;
&mysub;

state除了可以初始化变量,还可以初始化数组和hash。但初始化数组和hash的时候有限制:不能在列表上下文初始化。

use 5.010;
sub mysub {
state $n; # 初始化为undef
state @arr1; # 初始化为空列表()
state @arr2 = qw(perl shell); # 错误,不能在列表上下文初始化
}

perl作用域初探

因为perl中支持先定义子程序再调用,也支持先调用再定义的方式。不同的调用方式有可能会有区别。

例如:

#!/usr/bin/env perl -w
use strict; # do_stuff(1); # (1).在定义的前面
{
my $last = 1;
sub do_stuff {
my $arg = shift;
print $arg + $last;
}
}
do_stuff(1); # (2).在定义的后面

上面在不同的位置调用子程序do_stuff(1),但只有第二种方式是正确的,第一种方式是错误的。

原因在于my定义的变量是词法作用域变量,先不用管词法作用域是什么。只需要知道my定义的变量是在程序编译期间定义的好的,但是赋值操作是在程序执行期间进行的。而子程序sub的名称do_stuff是无法加my关键字的,所以perl中所有的子程序都是全局范围可调用的。子程序的调用是程序执行期间的

所以上面的程序中,整个过程是先在编译期间定义好词法变量$last(但未赋值初始化),子程序do_stuff。然后开始从程序头部往下执行:

当执行到(1)的位置处也就是调用子程序,这个子程序中引用了变量$last,但$last至今未赋值,所以会报变量未初始化的错误。

如果没有(1),那么从上往下执行将首先执行到my $last = 1,这表示为已在编译期间定义好的变量赋值。然后再继续执行到(2)调用子程序,但子程序引用的$last已经赋值初始化,所以一切正常。

在perl中的子程序是在编译期间定义好的,还是执行期间临时去定义的,目前我个人还不是太确定,按照perl的作用域规则,它应该是在执行期间临时去定义的。但无论如何,它先定义还是后定义,都不影响对变量作用域的判断。

Perl的子程序的更多相关文章

  1. Perl的子程序(二)

    在Perl中可以自己创建子程序(Subroutine): 关键字sub,子程序名以及用花括号封闭起来的代码块. sub  marine { ... } 子程序名与标量的命名空间是不同的两个部分. 子程 ...

  2. Perl系列文章

    0.Perl书籍推荐 Perl书籍下载 密码:kkqx 下面是一些我学习Perl过程中读过完整的或部分章节的觉得好的书. 入门级别1:<Perl语言入门>即小骆驼 入门级别2:<In ...

  3. perl学习(4) 子程序

    子程序,类比c语言中的函数,在形式上个人认为最大的区别:没有形参 1.1.定义子程序 1.2.调用 #! /usr/bin/perl sub marine { $n += 1 ; print &quo ...

  4. Perl子程序引用和匿名子程序

    子程序也有引用,也有匿名子程序.假设你已经具备了数组.hash的引用知识,所以这里简单介绍一下. $ref_sub = \&mysub; # 子程序引用,&符号必须不能少 &{ ...

  5. Perl 引用:引用就是指针,Perl 引用是一个标量类型可以指向变量、数组、哈希表(也叫关联数组)甚至子程序。

    Perl 引用引用就是指针,Perl 引用是一个标量类型可以指向变量.数组.哈希表(也叫关联数组)甚至子程序,可以应用在程序的任何地方. 1.创建引用1.使用斜线\定义变量的时候,在变量名前面加个\, ...

  6. Perl 子程序(函数)

    1.Perl 子程序(函数)Perl 子程序也就是用户定义的函数.Perl 子程序即执行一个特殊任务的一段分离的代码,它可以使减少重复代码且使程序易读. Perl 子程序可以出现在程序的任何地方,语法 ...

  7. Perl 学习笔记-子程序

    1.定义子程序 使用sub关键字定义 ;   子程序名和标识符同要求, 但是有的特殊的可以用 &符号;  子程序是全局的, 不需要再使用前声明;  重名函数后者覆盖前者. sub roger{ ...

  8. perl学习之子程序

    一.定义子程序即执行一个特殊任务的一段分离的代码,它可以使减少重复代码且使程序易读.PERL中,子程序可以出现在程序的任何地方.定义方法为:sub subroutine{statements;}二.调 ...

  9. perl语言入门总结-第4章-子程序

    子程序定义和返回值 sub sum{ print "调用了子程序\n"; $a + $b; #后一行为返回值 } ; ; $s =∑ #34 调用子程序 子程序中的参数,参数固定( ...

随机推荐

  1. swarm集群日常部分操作

    docker swarm: 1)查看集群使用docker info 或 docker node ls 查看集群中的相关信息 2)swarm集群中node的availability状态可以为 activ ...

  2. Il laser che è chiaramente visibile

    Prima di quel tempo ho ottenuto questo potente puntatore laser 500mW, non so davvero come questo dis ...

  3. [转] XEN, KVM, Libvirt and IPTables

    http://cooker.techsnail.com/index.php/XEN,_KVM,_Libvirt_and_IPTables XEN, KVM, Libvirt and IPTables ...

  4. Icehouse 创建Instance代码分析

    1. nova-api接收到request 在/etc/nova/api-paste.ini中,是这样配置nova v2的 [app:osapi_compute_app_v2] paste.app_f ...

  5. java基础知识-笔记整理

    1.查看已安装jdk文件路径 CMD输入java -verbose.   2.java学习提升路线 java学习视屏地址: http://www.icoolxue.com/album/show/38 ...

  6. 18年最有"钱"途的专业就是它(文末有福利)

    根据社会调查机构麦可思发布的<2018年中国大学生就业报告>中得知,从就业率.薪资和就业满意度等多角度综合考量,信息安全专业为首推绿牌专业. 不管你是计算机相关专业的学生,还是已经工作的I ...

  7. Java集合排序(面试必考点之一)

    集合是Java面试必考知识点,而集合的排序也是非常重要的,工作中经常用到,那么这个知识点也是必须要掌握的,下面是我曾经面试时被面试官问的问题: 根据API可知,Java集合的工具类Collection ...

  8. JDK 在 Linux 上安装的详细过程

    1.下载JDK Linux上一般会安装Open JDK,如果有系统预装了OpenJDK的话需要先卸载掉OpenJDK, 卸载过程可以参考这里:https://www.cnblogs.com/sxdcg ...

  9. 第70节:Java中xml和tomcat

    第70节:Java中xml和tomcat 前言: 哭着也要看完,字数: jdbc crud - statement dao java.sql.Driver The interface that eve ...

  10. Java之JDK7的新语法探索

    Java之JDK7的新语法探索 前言 感谢! 承蒙关照~ 字面量: 各种精致的表达方式: 八进制以0开头,十六进制0X开头,二进制以0B开头. 二进制运算时,应该写成这样才直观: &15 -& ...