#!/usr/bin/perl -w
#创建子程序检查$values的值
# sub printOK
# {
# if($values>10){
# print "Values is $values.\n";
# }
# else {
# print "Value is too small.\n";
# }
# }
# $values=15;
# printOK;
#在默认情况下Perl变量在全局范围内有效,因此可以在子程序printOK内部变量引用变量$values。变量的范围指可以引用变量的程序代码部分。
#使用关键字local和my关键字来创建变量,这种变量的范围完全限于类似printOK这样的子程序内部。这种局部变量的范围完全限于子程序,这意味着
#它们可以和全局变量具有相同的名称,但根本不会影响全局变量的值。
# sub printOK
# {
# my $localvalues=$values;
# if($localvalues>10){
# print "Values is $values.\n";
# }
# else {
# print "Value is too small.\n";
# }
# }
# $values=15;
# printOK;
# $values=8;
# printOK ($values);
#向子程序传递值时,那些值存储在特殊的perl数组@_中,可以在子程序内部访问这个数组。下面例子使用shift函数从@_数组得到传递给printOK的值:
sub printOK
{
my $localvalues=shift(@_);
if($localvalues>10){
print "Values is $values.\n";
}
else {
print "Value is too small.\n";
}
}
$values=15;
printOK($values);#使用函数时必须使用这种方式
$values=8;
printOK ($values);


#返回值
#除了接收传递的值之外,子程序也可以返回值,下面的例子使用return函数从子程序返回传递的两个值之和
#从子程序返回值取代了子程序本身的名称,而且在print语句中直接连接在字符串内:
sub addem
{
($value1,$value2)=@_;
return $value1+$value2;
}
print "2+2=".addem(2,2)."\n";


#perl和其他语言不一样,在perl中,不需要再使用子程序之前的声明,除非在没有将参数包围在括号的情况下使用子程序,这中情况下,必须在使用之前声明或者定义子程序。
#perl中声明子程序的不同方法:
#sub SUBNAME;
#sub SUBNAME(PROTOTYPE);
#sub SUBNAME BLOCK;
#sub SUBNAME(PROTOTYPE) BLOCK;
#从perl程序包中导入子程序:
#use packagename qw(subname1 subname2 subname3);




#子程序也有前缀反引用符号,使用反引用符号是可选的。子程序的反引用符号是:&,出现在每个子程序名称开头的&字符是名称的隐含组成部分,可以忽略它。




#如果希望在addem的时候不添加括号:方法一是在使用addem之前声明
sub addem;
$value3=addem 2,2;
print "2+2=$value3\n";
sub addem
{
($value1,$value2)=@_;
return $value1+$value2;
}
#方法二:在使用子程序之前定义子程序:
sub addem
{
($value1,$value2)=@_;
return $value1+$value2;
}
print "2+2=".addem 2,2;
print "\n";


#使用子程序原型
#-----------------------------------------------------------------------------------------------------------------------------------------
# 声明 | 原型
# sub SUBNAME($) | SUBNAME $argument1;
# sub SUBNAME($$) | SUBNAME $argument1,$argument2;
# sub SUBNAME($$;$) | SUBNAME $argument1,$argument2,$optionalargument;
# sub SUBNAME(@) | SUBNAME $arrayargment1,$arrayargment2,$arrayargment3;
# sub SUBNAME($@) | SUBNAME $argument1,$arrayargment1,$arrayargment2;
# sub SUBNAME(\@) | SUBNAME $argument1;
# sub SUBNAME(\%) | SUBNAME %{$hashreference};
# sub SUBNAME(&) | SUBNAME anonymoussubroutine;
# sub SUBNAME(*) | SUBNAME *argument;
# sub SUBNAME() | SUBNAME ;


#定义子程序
#sub SUBNAME BLOCK
#sub SUBNAME (PROTOTYPE) BLOCK;
sub Fprinthello
{
print "Hello!\n";
}
Fprinthello;#调用printhello函数打印hello
printhello();#声明子程序
sub printhello
{
print "Hello!\n";
}


#调用子程序
#perl中调用子程序的方法:
#1.使用子程序前缀反引用符号&:&subname(argumentlist)
#2.使用括号,&是可选的:subname(argumentlist)
#3.预先声明子程序,或者从程序包中导入子程序,或者在代码中已经定义子程序,则可以忽略括号:subname argumentlist;
#4.将子程序名称存储在数量中,并称之为&$scalar:
# $scalar=subname;
# &$scalar(argumentlist);
#使用不同的方法调用addem函数:
$value=&addem(2,3);
print "Use &addem(2,3) :$value\n";
$value=addem(2,4);
print "Use addem(2,4):$value\n";
$value=addem 2,3;
print "Use addem 2,3:$value\n";
$name="addem";
$value=&$name(2,5);
print "use \&\$name(2,5):$value\n ";


#调用之前检查子程序是否存在:
#在调用子程序之前,可以通过内置的perl的defined函数来检查它是否存在:
@_=(2,8);
$value=&addem if defined addem;#用defined函数判定是否存在addem函数,如果存在将addem计算的结果值赋给$value
print "for \@\_=(2,8) in (\$value=\&addem if defined addem)=$value\n";
#perl v5.6.0版本,可以使用exists来检查是否定义了子程序:
@_=(2,10);
$value=&addem if exists &addem;#用exists函数判定是否存在addem函数,如果存在将addem计算的结果值赋给$value
print "for \@\_=(2,10) in (\$value=\&addem if exists \&addem)=$value\n";
#如果编写的代码将被其他人复制到他们的应用程序中,在调用子程序之前检查是否定义了子程序是非常用用的




#读取传递给子程序的参数:
#1.可以在子程序中的代码中使用特殊数组@_读取传递给子程序的参数。这个数组专门用于保存传递给子程序的参数。
#2.如果像子程序传递了两个传递了两个参数,则子程序的代码可以用$_[0]和$_[1]的形式来得到参数的值。
#用子程序addem加两个数字,并打印结果,用addem(2,6)的形式来使用这个子程序。
sub addem1
{
$value1=$_[0];
$value2=$_[1];
print "\$value1+\$value2=".($value1+$value2)."\n";
}
addem1(2,6);
#从@_中获取值的方法和使用数组的方法一样。
#用shift函数从@_中获取值:
sub addem2
{
$value1=shift @_;
$value2=shift @_;
print "Use \shift \@\_ \$value1+\$value2=".($value1+$value2)."\n";
}
addem2(10,5);
#子程序中默认shift使用@_,上方代码可以这样:
sub addem3
{
$value1=shift;
$value2=shift;
print "Use \shift in \$value1+\$value2=".($value1+$value2)."\n";
}
addem3(10,8);


#使用列表赋值来一次得到@_中的所有值:
# sub addem4
# {
# ($value3,$value4)=@_;
# print "Use\(\$value3,\$value4\)=\@\_ in \$value3+\$value4=".($value3+$value4)."\n";
# }
# addem4(10,10);
#PS:当向子程序传递标量时,它们按引用传递,实际传递了标量值的引用(标量引用的作用类似存储在内存中的标量地址)。当修改传递的值时,也修改了传递这个值的最初代码的那个值。
#增加传递给子程序addone的值(传递的值¥value将增加):
sub addone
{
++@_[0];
}
$value=1;
addone($value);
print "Use \+\+\@\_\[0] in addone(\$value):$value\n";


#增加数组中每个元素的值,并返回增加值以后的数组:
sub addone_1
{
foreach $_(@_){
$_++;
}
return @_;
}
@b=(1,2,3,4,5,6,7,8,9,10);
@b_addone=addone_1(@b);
print join (" ,",@b_addone);
print "\n";


#当传递数组或者哈希表时,它将复制到@_中,这对于单个数组没有问题,但哈希表将被展开为键/值组合的列表。
#在子程序中将@_赋值给哈希表是可行的,通过初始化的方法:通过将键/值组合表赋值给它.
#下面例子想子程序printem传递给了哈希表,这个子程序将打印哈希表的所有元素:
$hash{fruit}=peach;
$hash{vegetable}=broccoli;
$hash{pie}=blueberry;
sub printem
{
$hash=@_;
foreach $key(keys %hash){
print "$key=>$hash{$key}\n";
}
}
printem(%hash);
#PS:因为传递给子程序的参数会展开到一个平面列表中,如果传递了两个或则跟多的数组或哈希表,则那些数组或者哈希表中的元素将会构成@_中长长列表。
#PS:为了在传递数组或者哈希表的时候保持各自的完整性,要按引用传递给他们。(按引用传递 按引用返回)




#使用不同个数的参数
#参数是在@_数组中传递的,perl可以很轻松的向子程序传递个数不同的参数。
#为了确定传递了多少个参数,仅需检查数组长度$#_(PS,$#_是@_中最后一个元素的下标值,所以$#加1就得到了基于0的数组@_中的元素总数)。
sub addem_4
{
print "You passed ".($#_+1)." element.\n";
foreach $element(@_){
$sum+=$element;
}
print join(" ,",@_)."=$sum\n";
}
addem_4(2,3,4);


#为参数设置默认值:
#||=运算符为用户可能忽略的参数提供默认值:
sub addem_5
{
($value1,$value2)=@_;
$value2 ||=1;
print "\$value1+\$value2=". ($value1+$value2) ."\n";
}
addem_5(5);
#列表赋值使变量¥value2为零时,为变量提供了默认值1。在传递5这个值时的到5+1=6;
#PS:假设用户不会传递值0或者空字符串。更好的方法是使用defined函数:
sub addem_6
{
($value1,$value2)=@_;
if(!defined($value2)){
$value2 =1;
};
print "\$value1+\$value2=". ($value1+$value2) ."\n";
}
addem_6(6);


#在某些情况下,可选择参数处于参数列表末尾,可以明确检查@_中元素的个数,也就是$#_+1,以了解用户传递了多少个参数:
sub addem_7
{
$value1==shift @_;
if($#_>0){
$value2 =@_[1];
}else{
$value2=1;
};
print "\$value1+\$value2=". ($value1+$value2) ."\n";
}
addem_7(7);
#子程序的返回值就是计算的最后一个表达式,或者可以用return语句明确退出子程序,并指定返回值。返回值在适当的上下文中计算,这取决于子程序调用的上下文。
#------------------------------------------------Demo-----------------------------------------------
#返回哈希表
sub gethash
{
$hash{fruit}=peach;
$hash{fruit1}=peach1;
$hash{fruit2}=peach2;
$hash{fruit3}=peach3;
$hash{fruit4}=peach4;
return %hash;
}
%myhash=gethash;
foreach $key(keys %myhash){
print "$key=>$myhash{$key}\n";
}


#返回undef指出操作失败
#编写一个函数getdata,它从文件中读取数据。然而,如果在文件中读取数据时出现了问题,getdata将返回undef。
#PS:这个例子使用eval函数来捕获错误,所以它不会对程序造成致命影响,而且可以通过检查$@a中的值,而了解是否出现了错误,eval将所有的错误都放在$@中:
# sub getdata
# {
# open FILEHANDLE ,"<nonexist.dat>";
# $line=<FILEHANDLE> if FILEHANDLE;
# }
# if($@){
# return;
# }else{
# return $line;
# }
# $data=getdata();
# if(defined($data))
# {
# print $data;
# }else{
# print "Sorry,getdata failed!\n";
# }


#用my设置范围:
#默认情况下,perl中的变量是全局性的。
#使用my关键字可以将变量限制在封闭的程序块、条件、循环、子程序、eval语句、或者用do、require、use包含的文件中。
#my声明的变量具有“词汇意义的”范围,local声明的变量具有动态范围。
#my与local的主要差异在:具有动态范围的变量在变量范围内调用的子程序是可以看见的。而具有词汇意义范围的变量并不是这样。
#使用子程序的结果就是用my声明的变量将完全局限于子程序内使用。
#为了用my声明数量值,以限制标量值的范围,需使用my $scalar。如果用my列出了多个元素,则列表必须出现在括号中。和my一起使用的所有元素都必须是合法的左值。
#PS:只有数字字母标识符可以具有词汇范围,诸如$_这样的特殊内置元素必须使用local声明。
#----------------------------------------------------------------DEMO-----------------------------------------------------------------------
sub printhem
{
my $inner=shift @_;
print $inner;
}
printhem "Hello\n";
#print $inner;
#PS:my仅适用于标量值、数组和哈希表
# my $variable;
# my ($variable1,$variable2);
# my $variable=5;
# my @array=(1,2,3);
# my %hash;
#声明为词汇范围的变量并没有限制在代码块中,相关的控制表达式也词汇范围的组成部分。






#要求词汇范围的变量
#把所用的全部变量都作为词汇范围的变量,可以使用附注:use strict 'vars'。这样,将从那个点开始到封闭程序块或者范围末尾之间对变量的任何引用,都必须引用专门声明的词汇变量,或者必须用它的包名称来限定。




#local创建临时变量:
#local创建动态范围变量,建立全局变量的临时副本,并使用这个临时副本,直至超出范围(那时,将恢复全局变量的值)
#必须使用local的地方:创建类似$_特殊变量的局部副本、修改数组或哈希表中的一个元素、局部使用文件句柄和perl格式。
#PS:local并不会创建变量,只创建全局变量的副本(而且保留全局变量的值,以供本地副本超出范围时候恢复)
#使用local将建立所列变量的副本,并使它们局限于封闭的程序块、eval语句、do语句、从那个块内调用的任何子程序。
#-----------------------------------------------------Demo-----------------------------------------------------------


sub printifok
{
local $local_value=$V_value;
if($local_value>10){
print "V_value is $V_value.\n";
}else{
print "V_value is too small.\n";
}
}
$V_value=10;
printifok;


$V_value=12;
printifok;


#确定my和local之间的差别
#PS:my创建了一个新变量,local将保存现存变量的副本。
#local是运行期间的结构,而不是编译期间的结构,用local对全局变量你的改动在通过子程序调用离开局部范围时任然有效。
#通常情况下,应该使用my,而不是local。my的执行速度快,而且没有全局副作用。然而,注意,必须使勇local来局部化任何以$开头的特殊变量。
#用my声明的变量存储在专用符号表中,而不是作为整个包的符号表的组成部分。




#用our设置变量:处理全局变量
#our声明方法声明了变量在封闭的程序块、文件或者eval语句内是合法的全局变量。
#our不会创建任何局部变量。
#----------------------------------------------------Demo-----------------------------------------------
# package package1;
# $value_1=5;


# package package2;
# print "\$value_1 =" .$value_1;


package package1;
our $value_1;
$value_1=5;


package package2;
print "\$value_1 =" .$value_1."\n";


#创建永久(静态)变量:
#有时候希望让子程序中的变量在两次调用子程序之间保留它的值,然而,如果用my或者local在子程序中声明变量,这些变量将在每次进入子程序的时候重置。
#--------------------------------------Demo--------------------------------------
# sub incrementcount
# {
# my $count;
# return ++$count;
# }
# print incrementcount ."\n";
# print incrementcount ."\n";
# print incrementcount ."\n";
# print incrementcount ."\n";
#如果$count成为静态变量可以解决这个问题,但perl不直接支持静态变量,默认情况下,全局变量是静态的,但用my声明的子程序变量不是静态的。
#词汇变量只要处于范围之内就不会重置。
#将my声明放在子程序外进行,然后将代码(声明和子程序)都放在括号内,这使得它的层次和对子程序的调用的层次相同:
{
my $count;
sub incrementcount
{
return ++$count;
}
}
print incrementcount ."\n";
print incrementcount ."\n";
print incrementcount ."\n";
print incrementcount ."\n";


#也可以将所有内容放在BEGIN块中,这个块将在程序加载的时候运行:
# sub BEGIN
# {
# my $count;
# sub incrementcount
# {
# return ++$count;
# }
# print incrementcount ."\n";
# print incrementcount ."\n";
# print incrementcount ."\n";
# print incrementcount ."\n";
# }




#得到子程序的名称和caller:
#caller函数返回当前子程序上下文的信息。一般情况下,可以这样使用caller:
# caller EXPR
# caller
#在标量上下文中,如果在子程序使用,则返回主调代码包的名称,或者返回eval或者requore,否则返回不确定的值。
#在表上下文中,这个函数像这样返回列表:
# ($package,$filename,$line)=caller;


#如果包含了EXPR,则caller返回其他信息,调试人员可以用这些信息打印堆栈跟踪记录。EXPR的值说明在当前堆栈架之前有多少堆栈框架需要返回(也就是子程序调用)。
# ($package,$filename,$line,$subroutine,$hasargs,$wantarray,$evaltext,$is_require)=caller($s);


#----------------------------------------------------Demo--------------------------------------
# sub addem_1
# {
# ($value_1,$value_2) = @_;
# $value_1+$value_2;
# print join(" ,",caller);
# }
# $value_3=addem_1(2,2);




#递归调用子程序:
sub factorial
{
my $value=shift (@_);
return $value== 1? $value:$value * factorial ($value-1);
}
$result_1=factorial(6);
print $result_1."\n";
#在递归的情况下,需要my来局部化变量


#嵌套子程序:
#perl支持嵌套子程序,可以在子程序的内部嵌套子程序。
#----------------------------------Demo----------------------------------------
sub outer
{
my $s="Inside the inner subroutine.\n";
sub inner
{
my $s2=$s;
print $s2;
}
inner();
}
outer();


#内联函数:如果函数的原型是(),既没有任何参数,则该函数在perl便以其中就是内联的。这样的函数必须由常量或者词汇范围内的标量构成。不能用&或者do引用。


#覆盖内置子程序
#当覆盖子程序时,就为它提供了一个新定义。可以覆盖子程序,包括内置在perl中的函数,但仅能覆盖从模块中导入的子程序。
#可以使用subs附注用导入语法预先声明子程序,可以使用那些名称来覆盖内置函数。
#---------------------------覆盖内置子程序例子-----------------------------------------
# sub subs 'exit';
# sub exit
# {
# print "Do you really want to exit?";
# $answer= <>;
# if($answer =~ m/^y/i){
# CORE::exit;
# }
# }
# while(1){
# exit;
# };




#重新定义子程序:
{
#----------------------创建子程序sub_1-----------------------
my $text=shift;
sub sub_1
{
$text=shift;
print "$text there!\n";
}
#----------------------创建子程序sub_2-----------------------
sub sub_2
{
$text=shift;
print "$text everyone!\n";
}
#-----调用sub_1,重新定义它,使得它引用sub_2,并再次调用它-------
sub_1("Hello");
*sub_1=\&sub_2;
sub_1("Hello");
}