子程序的返回值细节

Perl子程序总是有返回值,且因为存在上下文的原因,子程序的返回值和其他语言有些不同,有必要去了解一下相关的细节。

Perl中设置返回值的方式有两种:使用return和无return。其中:

  • return的参数(即要指定的返回值)是一个列表上下文
  • 无return时将以最后被执行的语句计算结果作为返回值

无参数的return

如果不指定return的参数,它也有指定返回值,此时return自身将根据调用子程序的上下文来决定返回何值:

  • 在列表上下文,无参数的return返回空列表
  • 在标量上下文,无参数的return返回未定义值(可当作undef或0或空字符串使用)
  • 在空上下文,无参数的return不返回任何数据,只用于终止子程序
sub roll{
  print 1+int(rand(6));
  return;
}

无return的子程序返回值

在Perl中,即使不使用return指定返回值,子程序也会有返回值,此时子程序的返回值为最后一条被执行语句的计算结果。

下面是两个等价的子程序定义方式:

sub roll{
  1+int(rand(6));
}

sub roll(){
  return 1+int(rand(6));
}

这里要注意区分最后一条被执行语句和最后一条语句,最后一条被执行的语句不一定是子程序的最后一条语句。例如:

sub roll{
  if(COND){
  ## 分支1
    2;
  } else {
  ## 分支2
    3;
  }
}

如果COND条件为真,则最后一条被执行的语句分支1中的2,2直接被返回。如果COND条件为假,则最后一条被执行的语句是分支2中的3,3直接被返回。

由于子程序中不使用return关键字时也有返回值,有时候要注意它可能带来的副作用。

例如,下面的子程序也有返回值:

sub roll{
  print 1 + int(rand(6));
}

上述子程序中没有使用return,因此最后一条被执行语句的计算结果(或它的返回值)是该子程序的返回值。最后一条被执行的语句是print语句,print函数的返回值为1,因此roll()的返回值为1。

say roll();  # 1

返回单值和列表

使用return返回时,可直接返回标量,也可以返回多个数据组成的列表:

# 返回单个值
return 1
return 0
return ""

# 返回多值组成的列表
return 1,2,3;
return (1,2,3);

返回数组和hash

Perl中的return无法直接返回数组和hash,因为它们会先转换成列表,再以列表的方式返回。

如果确实要返回数组或列表,应当返回它们的引用。

return $array_ref;
return $hash_ref;

也可以返回匿名数组或匿名hash:

return [qw(a b c)];   # 返回匿名数组
return {(name=>"junmajinlong", age=>23,)};   # 返回匿名hash

不同上下文的子程序return

使用return返回时,将根据所处上下文的不同进行对应的转换。

例如,对于return返回列表的子程序,当它处于一个标量上下文中时,返回的列表将转换为标量数据,即列表的最后一个元素(再次提醒,要区分标量上下文中列表和数组转换为标量的区别:列表取最后一个元素,数组取数组长度)。

如果想要根据上下文的不同决定不同的返回值,可使用wantarray关键字。wantarray用来检测子程序在何种上下文环境:

  • wantarray在列表上下文返回true
  • wantarray在标量上下文返回false
  • wantarray在空上下文返回未定义数据

wantarray典型的用法大致如下:

# 如果在空上下文,则直接终止,不返回数据
return unless defined wantarray;
# 如果在列表上下文,返回数组数据(会转换为列表),
# 在标量上下文,返回标量数据
return wantarray ? @array : $scalar;

实际上,wantarray更应该命名为wantlist,它期待的是列表上下文,而不是期待数组。在perldoc -f wantarray中也说明了,wantarray在未来的版本中可能会更换为wantlist。