Perl标量数据
Perl的数据类型非常简单。第一种数据类型我们称作scalar(标量),表示一个东西。数学和物理中也有scalar(标量)的概念,但它在Perl中的意思与此不同。再强调一次,标量表示一个东西,至于是什么东西,不加限定。因为对Perl来说,一个标量就是一个独立东西,对其的处理统一按照标量的去做,对于东西本身是什么形式,并不关心。
标量是Perl里面最简单的一种数据类型。一般用于表示数字或者字符序列。可能你会觉得数字和字符串是很不一样的东西,但对Perl来说,标量在内部是可以相应切换的。
如果你熟悉其他编程语言,可能已经习惯于把数字和字符串按照不同类型来处理的方式。比如C语言就用char声明字符串型数据,用int声明整形数据。Perl不严格区分两者,所以反而让很多人不太适应。没有关系,本书后续会逐步向你展视在处理数据的过程中,这种松散的定义带来的灵活性和优越性。
本章我们会展示两种标量的使用:一种是标量数据,表示数据的内容,也就是值;一种是标量变量,表示存储标量数据的容器。请注意,这是两个完全不同的概念,务必不要混淆。说到数据,我们指的是数据本身的内容,也就是值,它被写入内存后就是固定的,无法再改变。但是对于变量,正如其名,我们可以修改其中存储的数据,就像门牌号,租客在变而门牌号不变。开发者往往为了省事,都简单将其统称为标量。如无特别我们也简单统称为标量。
数字
虽然标量不外乎数字和字符串这两种情况,但我们还是分别讨论,这样比较容易理清头绪。我们先讨论数字类型的标量,然后再来讨论字符串类型的标量。
所有数字的内部格式都相同实际上,Perl处理数字时用的是底层C库,且统一使用双精度浮点数来存储数据。我们不用关心内部具体的处理和转化方式,但要知道这个依赖关系,所以编译于安装Perl的时候,选择了怎样的编译参数决定了最终Perl能达到的计算精度和大小范围,这是由底层库决定的,而不是perl解释器本身的限制。但Perl会在做数学计算时根据当前平台和库做最大限度地优化,以便快速运行。
后续章节中你会看到,定义整型数字和浮点数字的具体方式。但就内部而言,Perl都将它们统一转换为双精度浮点数来存储和计算。
也就是说,Perl内部是没有整型数字的,程序中的整型常量本质上还是双精度数字。所以在Perl里面,数字就是数字,不像其他语言,还要你事先确定数字的大小和类型。
整型直接量所谓直接量(literal),就是在源代码中直接写成数据内容形式。直接量不是某项计算的结果,也不是某次I/O操作后的结果,它是你直接写入程序代码的数据内容。整数直接量的定义相当直白,写法如下:
- 0
- 2001
- -40
- 137
- 61298040283768
最后一个数字读起来有些费力。Perl允许你在整数直接量中加入下划线,将若干位分开,写成下面这样看起来就很清楚了:
- 61_298_040_283_768
两种写法对计算机来说都表示同一个数字,只不过为了可读性,让人看起来不吃力,加了辅助的视觉分割符号。
非十进制整数的直接量和许多其他编程语言一样,除了十进制以外,Perl也可以用其他进制来表示数字直接量。八进制直接量以0开头,后跟数字0到7:
- 0377 # 等于十进制的255
十六进制直接以0x开头,后跟数字0到9以及字母A到F(或者小写字母a到f)表示十进制中的0到15的值:
- 0xff # 或者用大写FF,也等于十进制的255
二进制直接以0b开头,后跟数字0或1:
- 0b11111111 # 等于十进制的255
虽然这三个数字的写法不同,但对Perl来说都是同一个数字。所以写成0377也好,写成0xFF也罢,都表示数字255。选择哪种书写方式,取决于参与运算的需要。例如许多Unix系统中的shell命令惯用八进制数字。
非十进制数字直接量的长度如果超过4个字符将会难以阅读,可以用之前介绍的补充下划线的写法:
- 0x1377_0B77
- 0x50_65_72_7C
Perl的浮点数写法接近于我们平时的书写习惯。比如数字前面的加减号、小数点,也可以用以10为幂的科学计数法表示,以字母E标记次方。比如:
- 1.25
- 255.000
- 255.0
- 7.25e45 # 7.25乘以10的45次方(一个非常大的数)
- -6.5e24 # 负6.5乘以10的24次方
- -12e-24 # 负12乘以10的-24次方
- -1.2E-23 # 另一种写法,E换做大写
Perl 5.22 新增了十六进制浮点数直接量的写法。和用e表示以10为幂的写法类似,用p表示以2为幂。和十六进制整型数字一样,以0x开头:
- 0x1f.0p3
十六进制的浮点数直接量其实就是Perl内部保存数字时所使用的形式。所以就取值大小来说,完全不存在转换带来的近似问题。如果用十进制表示,Perl则无法完全精确表示以2为幂的数字。可能大部分人从未关注过这个细节,但因此产生的进位舍入(round-off)带来的计算偏差一直困扰着相当一部分人。通过这种写法我们能够自然规避这个问题。
数字操作符操作符相当于语言中的动词。操作符决定了处理名词的方式。Perl提供各种常见的运算操作符,加减乘除一个不少,我们用相应的字符来表示这些运算符号。数字操作符总是把处理对象看作数字:
- 2 + 3 # 2加3,得5
- 5.1 - 2.4 # 5.1减2.4,得2.7
- 3 * 12 # 3乘以12,得36
- 14 / 2 # 14除以2,得7
- 10.2 / 0.3 # 10.2除以0.3,得34
- 10 / 3 # 计算结果是浮点数3.333……
Perl的数字操作符返回的结果和用普通计算器得到的一样。计算出来的结果就是一个值,而不是什么数学中的记法,所以没有整数、分数的概念,统统都是浮点数。
Perl支持取模运算,用符号%表示。表达式10%3的结果是1,也就是10除以3的余数。注意,取模操作符先取整,再求余数。所以 10.5 % 3.2 和 10 % 3 的计算结果是相同的。
当取模操作符两边有一边(或两边)是负数的时候,在不同Perl版本中的计算结果可能会有所出入。因为人们对负数进位的方式一直存在争论,所以Perl依赖的底层库的行为飘忽不定,造成负数取模运算的结果不总是一致的。比如-10%3,如果按照距离-12来说,还差两步,答案是2;如果按距离-9来说,又多了一步,所以余-1。哪种结果才算正确?最好还是避免这种意义不明的计算。
Perl还提供一种类似FORTRAN语言的乘幂(exponentiation)操作符,写作两个星号 **,比如 2**3,表示2的3次方,结果是8。除了这里介绍的,还有一些数字操作符,后续我们会在讨论相应例子时具体说明。
字符串
字符串就是一连串的字符序列。字符串可以是任意字符的连续序列。最短的字符串不包含任何字符,所以也叫做空字符串。最长的字符串长度没有限制,它甚至可以填满所有内存(与此同时你也无法再对它进行任何操作)。这符合Perl尽可能遵循的“无内置限制(no built-in limits)”的原则。字符串通常是由可输出的字母、数字及标点符号组成,其范围介于ASCII编码的32到126之间。不过,因为字符串能包含任何字符,所以可用它创建、扫描或操控二进制数据,这是许多其他工作语言望尘莫及的。比如,你可以将一个图形文件或编译过的可执行文件读进Perl的字符串变量,修改它的内容后再写回去。
Perl完全支持Unicode,所以在字符串中可以使用任意一个合法的Unicode字符。不过由于Perl的历史原因,它不会自动将程序源代码当作Unicode编码的文本文件读入,所以如果你想要在源代码中使用Unicode书写直接量的话,得手工加上utf-8编译指令。最好养成习惯始终加上这句,除非有明确原因指出不该这么做:
- use utf8;
和数字一样,字符串也可以写作直接量。这其实就是在Perl程序中书写字符串的方式:单引号圈引的字符串以及双引号圈引的字符串。
单引号内的字符串直接量单引号内的字符串直接量就是写在一对单引号(')内的一串字符。前后两个单引号只是边界,并不是字符串的内容,用于让Perl判断字符串的开头与结尾。
除了单引号和反斜线字符外,单引号内所有字符都代表它们自己。如果要在字符串中使用单引号和反斜线,需要添加一个反斜线用作转义:
- 'Don\'t let an apostrophe end this string prematurely!'
- 'the last character is a backslash:\\'
- '\'\\' # 两个字符,一个单引号,一个是反斜线
一个字符如果太长,可以分成多行书写。两个单引号之间输入的换行符就表示字符串的换行:
- 'hello
- there' # hello,换行符,there(总共11个字符)
注意,单引号内看似转义的写法\n并不会转成最终的换行符,而只是字面上的两个字符,一个是反斜线本身,一个是字母n。
单引号圈引得字符串中,反斜线后面跟得只有反斜线或单引号时才表示转义。其实不用强记,道理很简单,为了边界字符能同时用作内容,需要引入转义符号作为区别,而因此引入得转义符号也需要转义以避免歧义。
双引号内的字符串直接量双引号内的字符串直接量同样也是字符串序列,只不过这次换成双引号表示收尾。不过双引号中的反斜线更为强大,可以转义许多控制字符,或是用八进制或十六进制写法来表示任何字符,还是看一些具体例子。
- "barney" # 和‘barney’写法一样的效果
- "hello world\n" # hello world,后面接着换行符
- "The last character of this string is a quote mark : \""
- "coke\tsprite" # coke、制表符(tab)和sprite
- "\x{2668}" # ♨ Unicode中名为HOT SPRINGS的字符的代码点(code point)
- "\N{SNOWMAN}" # ☃ Unicode中按名字Snowman定义的字符串
请注意,对Perl来说双引号内的字符串直接量"barney"和单引号内的字符串直接量'barney'是相同的,都是代表那6个字符组成的字符串。
反斜线后面跟上不同字符,可以表示各种不同的意义。
双引号内字符串可以使用变量内插(variable interpolated)的写法,说白了就是在字符串中附带变量,结果用变量内的值替换。
字符串操作符字符串可以用 . 操作符拼接起来,两边的原始字符串都不会因此操作而被修改,就像 2+3 的运算不会改变2或3一样。运算后得到一个更长的字符串,可以继续用于其他运算或保存在某个变量中。来看具体例子:
- "hello" . "world" # 等同于"helloworld"
- "hello" . ' ' . "world" # 等同于"hello world"
- "hello world" . "\n" # 等同于"hello world\n"
要注意的是,连接运算必须显示使用串接操作符,而不像其他某些语言那样,把两个字符串前后写在一起就算拼接。
还有个比较特殊的字符串重复操作符,记作小写的字母x。此操作符会将其左边的操作数与它本身重复连接,重复次数则由右边的操作数(某个数字)指定。来看具体的例子:
- "fred" x 3 # 得"fredfredfred"
- "barney" x (1 + 1) # 得"barney" x 2,亦即"barneybarney"
- 5 x 4.8 # 本质上就是"5" x 4,所以得到"5555"
最后一个例子有必要详加说明。因为重复操作符得左操作数必然是字符串类型,所以数字5在进行重复操作前,先被转化成单字符得的字符串"5",然后这个新字符串被复制4次,生成含有4个字符的新字符串5555。
重复次数(右操作数)在使用前会先取整(4.8变成4)。重复次数小于1时,会生成长度为0的空字符串。
Perl的内置警告信息
当Perl发现程序代码中有可疑之处时,可以通过警告信息提示你。从Perl5.6开始,我们可以通过编译指令启用警告机制:
- #!/usr/bin/perl
- use warnings;
也可以在命令行调用程序时给出-w选项表示启用警告机制。这种方式的作用域包括引入模块的代码,所以可能会看到有关别人代码的警告信息:
- $ perl -w my_program
或者在shebang行启用警告选项:
- #!/usr/bin/perl -w
现在,如果你把"12fred34"当数字用,Perl就会给你发出警告信息:
- Argument "12fred34" isn't numeric
虽然发出了警告,当Perl仍旧会按照规则把非数字的"12fred34"转化为数字12。
警告信息是给开发者看的,而不是给最终使用者。既然开发者看不到你的这些警告信息,也就没有啥用处了,所以启用全局警告反而容易带来一些噪音。启用警告机制只是让Perl提示一下可能有问题的地方,但Perl绝不擅自改变代码的行为。如果看不明白警告信息说的是什么,可以利用diagnostics编译指令。在perldiag文档中列出了每个警告信息的简短描述和详尽解释,这些都是diagnostics报告的核心内容:
- use diagnostics;
把use diagnostics加进程序后,可能你觉得程序启动有点慢。确实如此,这是程序需要预先加载警告和详细说明到内存,以便碰到需要时可以立即输出相关的错误信息。所以反过来看的话,这也提示了一种优化程序的方法:如果熟悉各种警告信息的含义,就不必在运行时发出详细的警告解释,去掉use diagnostics这个编译指令就会使程序启动变快,内存消耗也随之减少。当然,如果能修好程序,让它不再产生烦人的警告信息就再好不过了,或者干脆直接关闭警告信息。
更近一步,这种优化也可以通过Perl的命令行选项-M来实现。与其每次修改程序代码,不如仅在需要时加载diagnostics编译指令:
注意警告信息中出现的(W numeric),其中W的意思是警告级别属于普通警告,numeric的意思是警告类型属于数字操作一类。所以,,看到这两条就知道问题大致出在哪里。
随着后续深入介绍,我们还会看到其他错误类型的警告。不过请注意,将来Perl给出的警告文字可能会有所变化,所以不要依赖这些具体文字做应对的处理。
标量变量
所谓变量(variable),就是存储一个或多个值的容器。而标量变量,就是保存一个值的变量。后续章节我们还会看到其他类型的变量,比如数组和哈希,它们可以存储多个值。变量的名称在整个程序中保持不变,但它所持有的值可以在程序运行时不断修改变化。
Perl通过变量标识符前的魔符来区分变量的类型。所以不管你为变量取什么名字,都不会和Perl自带的函数或操作符的名字相冲突。
此外,Perl是通过魔符来判断改变量的使用意图。$的确切意思是“取单个东西”或者“取标量”。因为标量变量总是存储一项数据,所以它的意思就总是取得其中的“单个东西”的值。在第3章中你会看到“取单个东西”的魔符应用于其他类型变量的情况,比如说数组。这是Perl很关键的一个概念,魔符并不意味着变量的类型,而是表征取用变量数据的方式。
字符中的标量变量内插
一般我们用双引号圈引字符串,是希望把其中的变量替换为变量当前的内容,从而成为新的字符串。这个转变得过程我们称之为变量内插,来看示例:
- $meal = "brontosaurus steak";
- $barney = "fred ate a $meal"; # $barney 变成 "fred ate brontosaurus steak"
- $barney = 'fred ate a ' . $meal; # 效果相同的另一种写法
正如上面最后一行所示,不用双引号一样可以得到相同结果,但就书写和可读性来说,显然不如变量内插方式直观方便。变量内插有时候也被称作双引号内插,因为只有在双引号内部才可以这么做,在单引号中不行。Perl里面还有其他内插方式,之后我们会逐一介绍。
如果标量变量从未被赋值,内插时将使用空字符串替换。稍后在本章介绍空值undef的时候,将看到更多类似的情况。
如果内插时单单就是变量本身,不写双引号当然也可行。形式上,在单个变量两边加上双引号表示变量内插并不算错,但既然不是要构造更长的字符串,就没必要使用双引号的方式内插。
我们用美元符号表示将要内插的变量名,那么当我们真的希望打印美元符号本身的时候,用转义就行了。方法还是原来那套,用反斜线取消美元符号的特殊意义:
- $fred = 'hello';
- print "The name is \$fred.\n"; #打印美元符号
换种思路,如果不需要内插,那就直接单引号好了,免得混淆视线:
- print 'The name is $fred' . "\n"; #效果同上
变量内插时,Perl的原则是选用尽可能长且合法的变量名。所以,如果在内插变量名后面需要紧跟着合法的变量名字符,即字母、数字或下划线时,Perl就无从判断,需要引入免除歧义的写法。
解决思路就是隔离。和在shell下的写法相同,我们可以在变量名两边加上花括号,明确表示需要内插的变量。或者在变量名结尾断开,另外采用字符串串接操作符连接:
- $what = "brontosaurus steak";
- $n = 3;
- print "fread ate $n $whats.\n"; #不是牛排,而是变量 $whats 的值
- print "fread ate $n ${what}s.\n"; #内插的是 $what
- print "fread ate $n $what" . "s.\n"; #效果同上
- print "fread ate " . $n . ' ' . $what . "s.\n"; #另一种折腾的写法
比较操作符
比较数字大小时,Perl的比较操作符类似于代数系统:<、<=、==、>=、>、!=,这个是符合我们常规逻辑的。这些操作符的返回值要么是真(true),要么是假(false),我们将会在下一节详细讨论这些返回值的含义。这些操作符在Perl中的写法和在其他语言中的写法可能略有不同。比如,“相等”用的是 == 而不是单个的 =,因为=在Perl里表示变量赋值。另外,“不相等”用的是!=,因为<>在Perl里另有其义。而“大于等于”用的是>=不是=>,因为后者在Perl里也有别的意义。
比较字符串时,Perl有一系列的字符串比较操作符,看起来像是些奇怪的短语:lt、le、eq、ge以及ne。它们会逐一对比两个字符串里的字符,判定它们是否彼此相等或是哪一个排在前面。请注意,字符在ASCII编码或者Unicode编码中的顺序并不总是对应于字符本身意义上的顺序。