Perl5学习笔记-第二章-变量
2010-07-03 14:33:43 阿炯

Perl有三种内置变量类型:
1.纯变量(Scalar Varible)
2.数组(Array)
3.关联数组(Associative array or Hash)

在介绍完三种内置变量后,再对perl中的上下文做说明。


1.纯变量

又称标量变量,是Perl处理的最简单的数据类型。标量可以是数字(如2,3或2.5e6),也可以是字符串(如“hello”和“网上学园”)。Perl中的标量变量以美元符号$和一个字母开始,以后可以是字母、数字和下划线,大小写有区别,而且所有字母、数字和下划线都有效。一个标值可以是字符串,整数或浮点数,Perl会自动转换为它们需要的,但没有必要预先声明的变量类型。如:
$a和$A是不同的变量;
$this_is_a_long_variable_1和$this_is_a_long_variable_2是不同的变量;
Perl中的变量可以通过操作符(如+或.等)来产生新的变量。你可以从文件和设备中读取变量,也可以将其输出。
使用纯量变量时要在前面加上$符号, 注意:指定的纯变量是一个字符的话,就要加上""双引号或单引号;如果是数值的话,就不用加上""这个符号。

标量数据又可以分为数字和字符串两种:

数字
可分为整型变量和浮点变量。Perl 实际上把整数存在你的计算机中的浮点寄存器中,所以实际上被当作浮点数看待。在多数计算机中,浮点寄存器可以存贮约 16 位数字,长于此的被丢弃。整数实为浮点数的特例。浮点寄存器通常不能精确地存贮浮点数,从而产生误差,在运算和比较中要特别注意。指数的范围通常为 -309 到 +308。
整型变量:(如2,-200,3568等)。Perl支持8进制和16进制变量,8进制以0开头(如0255,表示8进制的255),16进制以0x或0X开头 (如-0x1a,代表负的1A)。
实型变量:(如2.5,-6.3e5,-2.3-e6等)。

字符串
最短的字符串可以没字符,最长可以把你的内存填满,这与Perl的“无内置限制”的原则一致。
字符串有两种格式:单引字符串和双引字符串。
单引字符串(single-quoted string): 就是用单引号括起来的一串字符。该单引字符串不是字符串的一部分。 引号中可以插入任何字符。只有两种情况例外,一种是中间插入单引号,并在前面有一反斜杠;一种是字符串有两个连着的反斜杠。
双引字符串(double-quoted string): 就是用双引号括起来的一串字符,其作用类似于C语言。

双引字符串中常用的一些转义字符如下所示:
转义字符     含义
\\     反斜线
\'     单引号
\"     双引号
\a     系统响铃
\b     退格
\e        跳出(ASCII编码的转义字符)
\f     换页符
\n     换行
\r     回车
\t     水平制表符
\v     垂直制表符
\0nn     创建八进制格式的数字
\xnn     创建十六进制格式的数字
\cX     控制字符,x可以是任何字符
\u     下一个字符为大写
\l     下一个字符为小写
\U     将所有字符转换为大写直到\E
\L     将所有的字符转换为小写直到\E
\Q     将到\E为止的非单词(non-word)字符加上反斜线
\E     结束\L、\U、\Q
\N{CHARACTER NAME}        以名称的方式引用的Unicode代码点
\x7f        十六进制的ASCII值
\x{nn}            十六进制的Unicode代码点


标量变量的运算符
1)、赋值运算符
如:$a=23; #将23赋值给$a
$b=$a=23; #将23赋值给$a和$b
$b=3+($a=2); #将2赋值给$a,再加3将值赋给$b,即$b为5

2)、二元赋值运算符
如:$a=+3; #等同于$a=$a+3
这与C语言中基本相同。

3)、自增自减运算符
如:$a++; #等同于$a=$a+1
这与C语言中基本相同。

4)、chop()运算符
如:$a="hello";
chop($a); #此时$a的值为"hell"。
这对于从屏幕获取文本后去除换行符很有用。
如:$a=<STDIN>; #获取文本
chop($a); #去除最后的换行符。
这两行可合并为:
chop($a=<STDIN>);

5)、字符串的标量插入值
如:$a="zmd";
$b="hello! $a";
$b的值为"hello! zmd"。
综合示例
$url1='hello';#将hello这串字符赋给$url1变量;
$url2='don\'t';#将don't这串字符赋给$url2变量;
$url3='hello\n';#将hello\n这串字符赋给$url3变量;注意\n不被当作换行符而是\和n两个字符;
$url1="http://www.freeoa.net";#将http://www.freeoa.net这串字符赋给$url1变量;
$url2="/cgi-bin/";#将/cgi-bin/这个字符赋给$url2变量;
$url3=$url1.$url2;#将两个变量的字符串连起,
$url3="http://zmd.zb169.net/cgi-bin/";
$int=5;#将10赋给$int变量;
$int=5+6;#$int=11;
$int=5*6;#$int=30;
$int=5;$int++;#$int=6;
$int=5;$int+=8;#$int=13;
$a="\Uzmd";$b="\u\LZHENG";$c="$a $b"#$a="ZMD"; $b="Zheng";
$c="ZMD Zheng"

2.数组

数组是标量数据的有序列表。数组可以含任意多个元素。最小的数组可以不含元素,而最大的数组可以占满全部可用内存。
数组实量(array literal)是位于括号内用逗号分开的一系列值。如:
(1,2,3,4,5)#具有1,2,3,4,5五个数值的数组
("zmd",1974,173.5)#具有"zmd",1974,173.5三个数值的数组
()#空数组
($a,5)#两个数值:$a的值和5
($a+$b,6)#两个数值
数组变量具有单独的数组值,要以@打头而不是$。如:@zmd
注意@zmd与$zmd没任何联系。Perl为对象的不同类型保留独立的命名空间。

数组的赋值和标量赋值一样,也用等号表示。Perl根据赋值对象是标量还是数组变量来确定赋值操作是标量赋值还是数组赋值。
若数组实量中只含有变量引用(不是表达式),则此数组实量也可作为变量使用。它可以用在赋值运算符的左边。例如:($a,$b,$c)=(1,2,3) #将1赋给$a,2赋给$2,3赋给$3
如果把数值变量赋给标量变量,则赋给标量变量的就是数组长度,如:
@zmd=(1,2,3) # 将(1,2,3)赋给@zmd
$a=@zmd # $a为3,即@zmd的数组个数
数组元素的访问和C语言中类似,下标是按顺序整数排列的,编号从0开始。
($a,$b,$c)=(1,2,3) #将1赋给$a,2赋给$2,3赋给$3
如果把数值变量赋给标量变量,则赋给标量变量的就是数组长度,如:
@zmd=(1,2,3) # 将(1,2,3)赋给@zmd
$a=@zmd # $a为3,即@zmd的数组个数

综合举例
@user1=("zmd","cxm");    #将zmd和cxm两个字符串赋给@user1
@user2=@user1;    #这时@user2=@user1=("zmd","cxm")
@user3=("zk",@user1);    #这时@user3=("zk","zmd","cxm")
($one,@user4)=@user3;    #这时$one="zk"
@user1=();    #把@user1清空
@int1=(1,2,3,4,5); $x=@int1;    #将数组@int1的个数赋给$x纯变量,$x=5
$x=$#int1;    #$#这个变量返回数组最后个数的值(index)$x=4
($x)=@int1;    #$x等于数组的第一个个数值$x=1
$b=$int1[0];    #$b等于数组的第一个元素值$b=1
$c=@int1[0];    #$c同上$c=1,因些呼叫数组中值有两种方法
$int1[0]=3;    #将3这个数值赋给数组@int的第一个元素@int1=(3,2,3,4,5)
$int1[0,1]=[7,8];    #将7赋给数组的第一个元素将8赋给数组第二个元素@int1=(7,8,3,4,5)
@int1[0,1]=@int1[1,0];    #将数组前两个元素交换@int1(8,7,3,4,5)
($int1[0],$int1[1])=($int1[1],$int1[0]);    #同上@int1=(8,7,3,4,5)
@int2=@int1[0,1];    #int2=(8,7)
$int1[5]=6;    #将6赋给数组中第六个元素@int1=(1,2,3,4,5,6)

3.关联数组
关联数组和前面说的数组类似,它包含标量数据,可用索引值来单独选择这些数据,和数组不同的是,关联数组的索引值不是非负的整数而是任意的标量。这些标量称为Keys,可以在以后用于检索数组中的数值。

关联数组的元素没有特定的顺序,你可以把它们想象为一组卡片。每张卡片上半部分是索引而下半部分是数值。关联数组是Perl语言中特有的,关联数组是一个功能强大的数组。使用关联数组时要在前面加上%号,关联数组的格式如:
%ARRAY=(key1,value1,key2,value2,key3,value3);
每一个key都有一个相对应的值(value)。
和数组类似,$zmd,@zmd,%zmd之间没有任何联系。Perl为对象的不同类型保留独立的命名空间。

下面介绍关联数组的操作:
1.在关联数组中增加、更改一对数据:ARRAY={key}=value; 在关联数组ARRAY中加上一对key-value,要在关联数组名称前加上$号,而且key的名称要在{}符号之间,最后再指定key所对应的value值。如果在这个关联数组中已经有这个key了.就会更改这个key所对应的value。
2.keys(%array)操作符可生成由关联数组%array中的所有当前关键字组成的列表。即返回奇数个元素(第1,3,5,7...个)的列表。
3.values(%array)操作符返回由关联数组%array中所有当前值组成的列表,即返回偶数个列表。
4.echo(%array)操作符返回由一个关键字和一个值对构成的两个元素的表。对同一数组再操作时返回下一对值直至结束。若没有更多的对时,echo()返回空表。 (这在打印全部列表中很有用)
5.删除一对在关联数组中的数据:delete $ARRAY{key};delete是Perl 所提供的函数,作用是删除关联数组中的一个key以及这个key所对应的value。使用方法是在 delete 函数之后,指定关联数组中要删除的key名称。

关联数组的综合举例:
%fred=(one,"zmd",two,"cxm"); $a=$fred{one};#$a等于"zmd"
$b=$fred{two};#$b等于"cxm"
$fred{two}="yes";#%fred=(one,"zmd",two,"yes")
@number=keys(%fred);#@list=(one,two)
@user=values(%fred);#@user=("zmd","cxm")
($number,$name)=echo(%fred);#此时$number的值为one,$name的值为"zmd",再执行一次$number为值为two,$name的值为"cxm")
@a=%fred;#将关联数组fred指定给数组a,这时@a=(one,"zmd",two,"cxm")
%b=@a;#将数组a指定给关联数组b,这时%b=(one,"zmd",two,"cxm")
delete $fred{one};#将关联数组中key值为one的一对key-value删除,这时%fred=(two,"cxm")
%name=();#把%name关联数组置空


几大类型的数据简介


1、整型
2、浮点数
3、字符串

基本上,简单变量就是一个数据单元,这个单元可以是数字或字符串。

1、整型
1)、整型
Perl最常用的简单变量,由于其与其它语言基本相同,不再赘述。例:
$x = 12345;
if (1217 + 116 == 1333) {
# statement block goes here
}
整型的限制:Perl实际上把整数存在你的计算机中的浮点寄存器中,所以实际上被当作浮点数看待。在多数计算机中,浮点寄存器可以存贮约16位数字,长于此的被丢弃。整数实为浮点数的特例。

2)、8进制和16进制数
8进制以0打头,16进制以0x打头。
例:$var1 = 047; (等于十进制的39)
$var2 = 0x1f; (等于十进制的31)

2、浮点数
如 11.4 、 -0.3 、.3 、3. 、54.1e+02 、5.41e03
浮点寄存器通常不能精确地存贮浮点数,从而产生误差,在运算和比较中要特别注意。指数的范围通常为-309到+308。例:
#!/usr/local/bin/Perl
$value = 9.01e+21 + 0.01 - 9.01e+21;
print ("first value is ", $value, "\n");
$value = 9.01e+21 - 9.01e+21 + 0.01;
print ("second value is ", $value, "\n");
---------------------------------------------------------
$ program3_3
first value is 0
second value is 0.01

3、字符串
惯用C的程序员要注意,在Perl中,字符串的末尾并不含有隐含的NULL字符,NULL字符可以出现在串的任何位置。双引号内的字符串中支持简单变量替换,例如:
$number = 11;
$text = "This text contains the number $number.";
则$text的内容为:"This text contains the number 11."

双引号内的字符串中支持转义字符

Table Escape sequences in strings.
Escape Sequence    Description
\a    Bell (beep)
\b    Backspace
\cn    The Ctrl+n character
\e    Escape
\E    Ends the effect of \L, \U or \Q
\f    Form feed
\l    Forces the next letter into lowercase
\L    All following letters are lowercase
\n    Newline
\r    Carriage return
\Q    Do not look for special pattern characters
\t    Tab
\u    Force next letter into uppercase
\U    All following letters are uppercase
\v    Vertical tab
\L、\U、\Q功能可以由\E关闭掉,如:
$a = "T\LHIS IS A \ESTRING"; # same as "This is a STRING"
要在字符串中包含双引号或反斜线,则在其前加一个反斜线,反斜线还可以取消变量替换,如:
$res = "A quote \" and A backslash \\";
$result = 14;
print ("The value of \$result is $result.\n")的结果为:
The value of $result is 14.
可用\nnn(8进制)或\xnn(16进制)来表示ASCII字符,如:
$result = "\377"; # this is the character 255,or EOF
$result = "\xff"; # this is also 255

#同时使用\u\L来表示后续字符全部转为小写,但首字母大写。
use v5.14;

$_='I saw FreeOA with Net.';
my $changeit=$_ =~ s/(www|freeoa)/\u\L$1/igr;

>I saw Freeoa with Net.

直接在双引号内进行大小写的转换:
say qq[Hello,\L\u$_\E, it's really.];

>Hello,I saw freeoa with net., it's really


单引号字符串

单引号字符串与双引号字符串有两个区别,一是没有变量替换功能,二是反斜线不支持转义字符,而只在包含单引号和反斜线时起作用。单引号另一个特性是可以跨多行,如:
$text = 'This is two
lines of text
';
与下句等效:
$text = "This is two\nlines of text\n";

字符串和数值的互相转换

$string = "43";
$number = 28;
$result = $string + $number; # $result = 71
若字符串中含有非数字的字符,则从左起至第一个非数字的字符,如:
$result = "hello" * 5; # $result = 0
$result = "12a34" +1; # $result = 13

变量初始值

在Perl中,所有的简单变量都有缺省初始值:"",即空字符。但是建议给所有变量赋初值,否则当程序变得大而复杂后,很容易出现不可预料且很难调试的错误。

变量的上下文

Perl根据不同语境相同的变量,例如:
my @animals = ("camel", "llama", "owl");

在这里,@animals是一个数组,但它是在标量上下文中使用时,然后返回元素接触的数量。
if (@animals < 5) { ... } # Here @animals will return 3

另一个例子:my $number = 30;

这里$number是标量和它包含的数量,但是当它被称为字符串然后它成为数为0,字符串代替:
$result = "This is " + "$number";
print "$result";

这里将输出30

转义字符

在Perl中,我们使用反斜杠(\)字符来转义任何类型的字符,可能会干扰我们的代码。
$result = "This is " . "\"number\"";
print "$result";

这里输出将是这是“number”

大小写敏感性

变量名是大小写敏感的$foo, $FOO和$fOo都是单独。

变量作用域

整个上一节中所有的例子中,使用下面的语法:
my $var = "value";

其实我是不需要的,你可以只使用:
$var = "value";

然而,上述用法将创建全局变量在整个程序中,这是不好的编程习惯。但my创建词法作用域的变量。变量的作用域在定义它们的块(即一串由大括号包围的语句)。有一个看看下面的例子:
my $a = "foo";
if($some_condition){
   my $b = "bar";
   print $a;# prints "foo"
   print $b;# prints "bar"
}
print $a;# prints "foo"
print $b;# prints nothing; $b has fallen out of scope

结合使用my严格变量;在你的Perl脚本的顶部,意味着解释将拿起一些常见的编程错误。例如,在上面的例子,最后打印$b的会导致一个编译时错误和防止运行程序,强烈建议使用严格。以下是使用:

use strict;
my $a = "foo";
if ($some_condition){
   my $b = "bar";
   print $a;# prints "foo"
   print $b;# prints "bar"
}
print $a;# prints "foo"
print $b;# prints nothing; $b has fallen out of scope

私有变量通过my()

我的运算符宣布列出的变量被词法不外乎以下但不限于:
Enclosing blocks,
Conditional (if/unless/elsif/else)
Loop (for/foreach/while/until/continue)
Subroutine
eval or do/require/use'd file

如果列出了多个值,列表必须放在括号中。列出的所有元素都必须是合法的左值。当前必须与当地的本地化,而不是像$/作用域 - 魔法的内置插件。
my $foo; # declare $foo lexically local
my (@wid, %get);# declare list of variables local
my $foo = "flurp";# declare $foo lexical, and init it
my @oof = @bar;# declare @oof lexical, and init it
my $x : Foo = $y;# similar, with an attribute applied

控制结构的词法作用域范围内精确地划定控制块由括号控制表达式是该范围内的一部分。因此,在循环$line范围延伸整个循环结构(包括继续条款)的其余部分从它的声明,但不超过。
while (my $line = <>) {
   $line = lc $line;
}continue {
    print $line;
}

同样,在有条件$answer的范围延伸,有条件的其余部分通过其声明,包括任何的elseif语句及其他子句,但不超过。
if ((my $answer = <STDIN>) =~ /^yes$/i) {
    user_agrees();
} elsif ($answer =~ /^no$/i){
    user_disagrees();
}else{
chomp $answer;
    die "'$answer' is neither 'yes' nor 'no'";
}

我们鼓励使用词法作用域的变量。使用以下行,在你的程序文件的顶部,以避免任何错误。这会提醒你的变量范围使用local 或my 关键字。
use strict 'vars';

临时变量通过local()

本地修改其上市的变量是“local”封闭块,eval,或做文件 - 从该区块内调用的任何子程序。一个地方,只是给全局意义包变量的临时值。它不创建一个局部变量。这被称为动态范围。

词法作用域是my,这更像C语言的自动声明工作.
local $foo;# make $foo dynamically local
local (@wid, %get);# make list of variables local
local $foo = "flurp";# make $foo dynamic, and init it
local @oof = @bar;# make @oof dynamic, and init it

因为当地是一个运行时间的运算符,它通过一个循环,每次被执行。因此,它是更有效的本地化外循环的变量。如果你定位一个特殊的变量,你给它一个新的价值,但它的魔法不会消失。这意味着,所有有关这种神奇的副作用仍然使用本地化值。此功能允许像这样的代码工作:
# Read the whole contents of FILE in $slurp
{ local $/ = undef; $slurp = <FILE> ; }

V-Strings

V型字符串可以是一个有用的方式引入到Perl的版本号和IP地址,它们是任何文字与一个V开始,是由一个或多个圆点分隔的元素:
$name = v77.97.114.116.105.110;

以 v 开头,后面跟着一个或多个用句点分隔的整数,会被当作一个字串文本。当想为每个字符 直接声明其数字值时,v-字串提供了一种更清晰的构造这类字串的方法,而不像 "\x{1}\x{14}\x{12c}\x{fa0}" 这种不易于理解,可以看下面的实例:
use v5.20;

binmode STDOUT,'encoding(UTF-8)';

my $smile = v9786;
my $freeoa = v102.111.111;
my $martin = v77.97.114.116.105.110;
 
say "smile = $smile";
say "freeoa = $foo";
say "martin = $martin";


数字标量

Perl支持一个相当基本的方法,用于指定一个十进制的数字文本:
$num = 123;# integer
$num = 123.45;# floating point
$num = 1.23e45;# scientific notation

也可以指定通过指定一个前面的字符的十六进制,八进制和二进制的文字突出显示的号码类型:
$num = 0xff;# Hexadecimal
$num = 0377;# Octal
$num = 0b0010_0000; # Binary


数值和字符串的常见操作

数值

perl中以双精度(浮点数)方式保存和运算数值的方式,就算写的是整数,在内部也会转换成等效的浮点数类型保存。但在perl内部,有些运算会将浮点数转换成整型进行,而且也有integer编译器可以强制编译成整型。perl中整数的表示方式有3种:
123
0123
61_123_234

这里使用下划线(_)代替逗号表示千分位,因为perl中逗号有特殊意义。

算术运算

+ - * / % **

**的优先级在这里最高。
取模时,先两个数取整,再取模。例如5.2 % 2.1等价于5 % 2。
多个取幂符号时,**的优先级是从右向左进行的。
3 ** 4 ** 2   # 3 ** (4 ** 2) = 3的16次方

单双引号和反引号

perl对待单双引号的方式和shell有点类似:
单引号内,只有反斜线的转义效果存在(如\n不再是换行),其它全都当作字面符号(单引号自身除外)
双引号内,反斜线、反斜线序列(\n,\t...)、变量替换都允许
反引号内,内容会交给shell执行

引号的文本形式:q、qq和qx

q等价于单引号
qq等价于双引号
qx等价于反引号

q(hello world) #等价于'hello world'
qq(hello world) #等价于"hello world"
qx(echo hello world) #等价于`echo hello world`

上面的括号可以替换为其它符号,只要前后能配对(特指括号类)或者一致即可:
qq{ hello }
qq! hello !
qq# hello #
qq% hello %


字符串连接和重复

字符串连接使用点(.)。

下面是等价的:
abc.cde
abccde

字符串重复使用小写字母"x",后跟一个重复倍数。如果倍数是一个小数,如3.2,则会取整。如果倍数小于1,则取整为0,所以倍数为0,返回空。
"abc" x 3
"abc" x 3.2

都将输出"abcabcabc"。

"abc" x 0.4  # 返回空

"x"符号左边会强制转换为字符串。
03 x 3 # 返回333
"03" x 3 # 返回030303
3 x 3 # 返回333
3.2 x 3 # 返回3.23.23.2

数值和字符串的类型自动转换

什么时候转换,以及向哪个方向转换,取决于操作符。例如算数运算符(+-*/等)需要的是数值,会强制转换为数值。"."操作符需要的是字符串,会强制转换为字符串。
"0333" + 22 # 返回355
"033".22 # 返回03322
033.22 # 返回2722,033表示8进制,转换为十进制为27(3*8+3)

在perl中数值开头的字符串也可以强制转换为数值,但只能转换数值开头(允许前导空白)的字符串。
"12abc" * 3 # 36
"abc12" * 4 # 0
"  12abc" * 3 # 36

两种类型的操作符一起用的时候,会按照优先级进行运算:
"abc".5*3 # 返回abc15,乘法先运算
"abc".5 + 3 # 返回3,"."先运算
"abc".(5+3) # 返回abc8

heredoc

perl中也支持heredoc,所谓heredoc,即表示将后面的内容当作文档。既然是文档,就需要有文档起始符和文档结束符,分别标识文档从哪里起始,到哪里结束。

一般来说,所有支持heredoc的语言,文档起始符和文档结束符都必须相同(一般使用EOF或eof作为起始符和结束符),且结束符必须单独占行且顶格书写。

perl中支持的heredoc格式如下,以print为例:
print <<EOF;
    line1
    line2
    line3
EOF

这里以EOF作为文档起始符和结束符
起始符EOF后面必须加上分号结尾,分号后的所有内容都忽略(即使它后面的不是注释内容)
结束符EOF单独占用一行,且顶格书写
起始符和结束符中间是怎样的数据,输出时就是怎样的数据

perl的heredoc还有自己的特性,可以为起始符加上单引号和双引号以及其它符号。加单双引号后的效果和一般单引号、双引号的能力是一样的:
单引号是强引用
双引号是弱引用
不加引号等价于加双引号
加反引号`,则将字符串放进shell环境执行

加单双引号:
$name="freeoa";
 
print <<'EOF';
        haha
        \$name  # 反斜线转义功能失效
        
        $name   # 变量无法替换
EOF

print <<"EOF";
        haha
        \$name   # 反斜线成功转义
        
        $name    # 变量成功替换
EOF

加反引号:
print <<`EOF`;
        date +"%F %T"
EOF

可以将heredoc直接赋值给变量:
$msg = <<EOF;
    HELLO
    WORLD
EOF

print $msg;  # $msg不要加换行符,因为$msg是一个heredoc,自带换行符。


Perl中的上下文

在perl中有很多地方会切换上下文。所谓上下文,即同一个表达式出现在不同地方得到的结果不同。换句话说,同一个表达式,它表达的值不是固定的。这就像是同一个单词,在不同语境下的意思不同。例如运算操作符决定数值是一个数字还是一个字符串:
2 * 3
2 x 3

2 * 3中的2和3都是数值,因为操作符*是算术运算符,它要求两边都是数字;而2 x 3中的2是字符串,3是数字,因为操作符x是这样要求的。还有对数组@arr的两种操作:
my @arr=qw{perl,python,shell};
print @arr,"\n";    # 返回:perlpythonshell
print @arr."\n";    # 返回:3

使用逗号分隔@arr和\n是产生一个列表,这时的@arr会替换为该数组中的元素值。使用点号连接@arr和\n,这时点号要求两边的都是字符串,数组在这种环境下(标量上下文)返回的是它的元素个数,所以@arr返回一个数值(但其实是字符串)。

在perl解析表达式的时候,你要么希望它返回一个标量,要么希望它返回一个列表(其实还有很多种上下文,但至今无人知晓有多少种上下文,perl长老团也不知道)。所以perl中常见的两种上下文是:标量上下文和列表上下文,除此之外还有一个很常见的上下文类型:空上下文(void context)。

1.标量上下文:表达式在标量上下文中返回一个标量
2.列表上下文:期待表达式返回一个列表
3.空上下文:不使用表达式的返回结果,即返回结果被丢弃

上下文不仅决定了表达式的返回结果类型,还限制了某些环境下只能使用某些上下文。比如需要传递一个列表的时候却传递了一个标量,这可能会报错误。例如:
42 + something;    # 这里的something必须是标量
sort something;    # 这里的something必须是列表

数组@arr为例:
@arr=qw(perl shell python);
@sorted=sort @arr;    # 列表上下文:返回(perl python shell)
@num=@arr + 42;    # 标量上下文:返回45

即便是赋值这种操作,都有不同的上下文:
@arr1=@arr;    # 列表上下文,赋值@arr给另一个数组@arr1
$arr1=@arr;    # 标量上下文,赋值@arr的元素个数给变量arr1

比较悲剧的是,无法总结一个通用的规则来解释什么时候用什么上下文,只能通过一些经验来感受它。

列表操作切换到标量上下文

那些操作列表的表达式(如sort)用在标量上下文会如何?没人知道会如何。不同的操作,返回的内容没有规律可言。例如对列表排序的sort操作放在标量上下文只会返回undef,reverse操作放在标量上下文则是返回字符的逆排序(先将所有元素按照字符串格式连接起来,再对整体进行反转)。

@arr=qw(perl python shell);
$sorted=sort @arr;    # 返回undef
$reversed=reverse @arr;    # perlpythonshell-->llehsnohtyplrep
print $sorted,"\n";
print $reversed,"\n";

以下是常见的上下文:
$var = something;    # 标量上下文
@arr = something;    # 列表上下文
($var1,$var2) = something;        # 列表上下文
($var1) = something;        # 列表上下文

以下是常见的标量上下文:
$var = something;
$arr[3] = something;
123 + something;
if (something) {...}
wihle(something) {...}
$var[something] = something;

以下是常见的列表上下文:
@arr = something;
($var1,$var2) = something;
($var1) = something;
push @arr,something;
foreach $var (something){...}
sort something;
reverse something;
print something;

需要注意的几点,将数组赋值给标量变量,得到的是数组的长度(元素个数),将列表赋值给标量变量,得到的是最后一个元素,除了最后一个元素外,其它元素都被丢弃,也就是放进了void context。

@arr = qw(a b c d);
$x = @arr;    # 结果:$x=4

$y = qw(a b c d);    # 结果:$y=d,开启了warnings的情况下会警告
($y) = qw(a b c d);    # 结果:$y=d,不会警告
($a,$b,$c,$d) = qw(a b c d)    # 结果:$a=a,$b=b,$c=c,$d=d

标量操作切换到列表上下文

这种情况很简单,如果某个操作的返回结果是标量值,但却在列表上下文中,则直接生成一个包含此返回值的列表。
@arr = 6 * 7;    # 结果:@arr=(42)
@arr = "hello".' '.'world';    # 结果:@arr=("hello world")

但关于undef和空列表有一个陷阱:
@arr1 = undef;
@arr2 = ();

上面的undef是一个标量值,所以赋值后@arr1=(undef),它不会清空数组;而()是空列表,它表示未定义的,所以赋值后@arr2被清空。

强制指定标量上下文

有时候如果想要强制指定标量上下文,可以使用伪函数scalar进行强制切换,它会告诉perl这里要切换到标量上下文。
my @arr=(perl python shell);
print "How many subject do you learn?\n";
print "I learn ",@arr," subjects!\n";         # 错误,这里会输出课程名称
print "I learn ",scalar @arr," subjects!\n";  # 正确,这里输出课程数量

另一种切换为标量上下文的方式是使用~~,这是两个比特位取反操作,因为比特位操作环境是标量上下文,两次位取反相当于不做任何操作,所以将环境变成了标量上下文。这在写精简程序时可能会用上,而且因为~~是符号,可以和函数之间省略空格分隔符:
my @arr = qw(Shell Perl Python);
print ~~@arr;
print~~@arr;

还可以使用下面的技巧从列表上下文切换成标量上下文:
$var = () = expr

Perl中赋值操作总是先评估右边,所以上面等价于$var = (() = expr)。() = expr表示转换成列表上下文,使得expr以列表上下文的环境工作。最后的赋值操作,由于左边是$var,会将列表转换成标量上下文。这种技巧比较常见的是$num = () = <>,因为<>在不同上下文环境下工作的方式是不一样的,这个表达式表示以列表上下文环境读取所有行,然后赋值给标量,所以赋值给标量的是列表的元素个数,也就是文件的行数。

它等价于$num = @tmp = <>。而且完全可以使用scalar()替代scalar(@tmp = <>)。

只有强制切换到标量上下文的伪函数scalar,没有切换到列表上下文的函数,因为根本用不到。

列表上下文中的STDIN

<STDIN>放在列表上下文时,会一次性读取所有输入(文件/键盘等)。一次性意味着大文件需要大量内存,一般400M的文件,perl可能会花上1G内存,因为perl会事先分配好富裕的空间避免事后问题。例如:
@lines=<STDIN>;
foreach (@lines){
    print $_;
}

在目前来说,这正是我们所需的方式。可以读取每行,并对每行进行操作。但因为是一次性读取,对于大文件来说,这种方法不可取,应该想其它方法。

<STDIN>放在列表上下文时,它会一行一行读取,直到读取到文件结尾(EOF)。但如果是读取键盘输入,如何给出EOF?在Linux中,按下CTRL+D(Windows下是CTRL+Z)即可,默认它会发送EOF给操作系统,操作系统会通知perl到了文件结尾。因为<STDIN>读取的每一行中默认就带有换行符,在列表上下文中,同样可以使用chomp()函数来去除每一行的换行符:
@lines=<STDIN>;
chomp(@lines);

或者采用更简洁的方式:chomp(@lines=<STDIN>);