Perl标量数据

Perl的数据类型非常简单。第一种数据类型我们称作scalar(标量),表示一个东西。数学和物理中也有scalar(标量)的概念,但它在Perl中的意思与此不同。再强调一次,标量表示一个东西,至于是什么东西,不加限定。因为对Perl来说,一个标量就是一个独立东西,对其的处理统一按照标量的去做,对于东西本身是什么形式,并不关心。

标量是Perl里面最简单的一种数据类型。一般用于表示数字或者字符序列。可能你会觉得数字和字符串是很不一样的东西,但对Perl来说,标量在内部是可以相应切换的。

如果你熟悉其他编程语言,可能已经习惯于把数字和字符串按照不同类型来处理的方式。比如C语言就用char声明字符串型数据,用int声明整形数据。Perl不严格区分两者,所以反而让很多人不太适应。没有关系,本书后续会逐步向你展视在处理数据的过程中,这种松散的定义带来的灵活性和优越性。

本章我们会展示两种标量的使用:一种是标量数据,表示数据的内容,也就是值;一种是标量变量,表示存储标量数据的容器。请注意,这是两个完全不同的概念,务必不要混淆。说到数据,我们指的是数据本身的内容,也就是值,它被写入内存后就是固定的,无法再改变。但是对于变量,正如其名,我们可以修改其中存储的数据,就像门牌号,租客在变而门牌号不变。开发者往往为了省事,都简单将其统称为标量。如无特别我们也简单统称为标量。

数字

虽然标量不外乎数字和字符串这两种情况,但我们还是分别讨论,这样比较容易理清头绪。我们先讨论数字类型的标量,然后再来讨论字符串类型的标量。

所有数字的内部格式都相同

实际上,Perl处理数字时用的是底层C库,且统一使用双精度浮点数来存储数据。我们不用关心内部具体的处理和转化方式,但要知道这个依赖关系,所以编译于安装Perl的时候,选择了怎样的编译参数决定了最终Perl能达到的计算精度和大小范围,这是由底层库决定的,而不是perl解释器本身的限制。但Perl会在做数学计算时根据当前平台和库做最大限度地优化,以便快速运行。

后续章节中你会看到,定义整型数字和浮点数字的具体方式。但就内部而言,Perl都将它们统一转换为双精度浮点数来存储和计算。

也就是说,Perl内部是没有整型数字的,程序中的整型常量本质上还是双精度数字。所以在Perl里面,数字就是数字,不像其他语言,还要你事先确定数字的大小和类型。

整型直接量

所谓直接量(literal),就是在源代码中直接写成数据内容形式。直接量不是某项计算的结果,也不是某次I/O操作后的结果,它是你直接写入程序代码的数据内容。整数直接量的定义相当直白,写法如下:

  1. 0
  2. 2001
  3. -40
  4. 137
  5. 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. 1.25
  2. 255.000
  3. 255.0
  4. 7.25e45     # 7.25乘以1045次方(一个非常大的数)
  5. -6.5e24     6.5乘以1024次方
  6. -12e-24     12乘以10-24次方
  7. -1.2E-23    另一种写法,E换做大写

Perl 5.22 新增了十六进制浮点数直接量的写法。和用e表示以10为幂的写法类似,用p表示以2为幂。和十六进制整型数字一样,以0x开头:

  • 0x1f.0p3

十六进制的浮点数直接量其实就是Perl内部保存数字时所使用的形式。所以就取值大小来说,完全不存在转换带来的近似问题。如果用十进制表示,Perl则无法完全精确表示以2为幂的数字。可能大部分人从未关注过这个细节,但因此产生的进位舍入(round-off)带来的计算偏差一直困扰着相当一部分人。通过这种写法我们能够自然规避这个问题。

数字操作符

操作符相当于语言中的动词。操作符决定了处理名词的方式。Perl提供各种常见的运算操作符,加减乘除一个不少,我们用相应的字符来表示这些运算符号。数字操作符总是把处理对象看作数字:

  1. 2 + 3       # 23,得5
  2. 5.1 - 2.4   # 5.12.4,得2.7
  3. 3 * 12      # 3乘以12,得36
  4. 14 / 2      # 14除以2,得7
  5. 10.2 / 0.3  # 10.2除以0.3,得34
  6. 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编译指令。最好养成习惯始终加上这句,除非有明确原因指出不该这么做:

  1. use utf8;

和数字一样,字符串也可以写作直接量。这其实就是在Perl程序中书写字符串的方式:单引号圈引的字符串以及双引号圈引的字符串。

单引号内的字符串直接量

单引号内的字符串直接量就是写在一对单引号(')内的一串字符。前后两个单引号只是边界,并不是字符串的内容,用于让Perl判断字符串的开头与结尾。

除了单引号和反斜线字符外,单引号内所有字符都代表它们自己。如果要在字符串中使用单引号和反斜线,需要添加一个反斜线用作转义:

  1. 'Don\'t let an apostrophe end this string prematurely!'
  2. 'the last character is a backslash:\\'
  3. '\'\\'  # 两个字符,一个单引号,一个是反斜线

一个字符如果太长,可以分成多行书写。两个单引号之间输入的换行符就表示字符串的换行:

  1. 'hello
  2. there'  # hello,换行符,there(总共11个字符)

注意,单引号内看似转义的写法\n并不会转成最终的换行符,而只是字面上的两个字符,一个是反斜线本身,一个是字母n。

单引号圈引得字符串中,反斜线后面跟得只有反斜线或单引号时才表示转义。其实不用强记,道理很简单,为了边界字符能同时用作内容,需要引入转义符号作为区别,而因此引入得转义符号也需要转义以避免歧义。

双引号内的字符串直接量

双引号内的字符串直接量同样也是字符串序列,只不过这次换成双引号表示收尾。不过双引号中的反斜线更为强大,可以转义许多控制字符,或是用八进制或十六进制写法来表示任何字符,还是看一些具体例子。

  1. "barney"        # ‘barney’写法一样的效果
  2. "hello world\n" # hello world,后面接着换行符
  3. "The last character of this string is a quote mark : \""
  4. "coke\tsprite"  # coke、制表符(tab)和sprite
  5. "\x{2668}"      # Unicode中名为HOT SPRINGS的字符的代码点(code point
  6. "\N{SNOWMAN}"   # Unicode中按名字Snowman定义的字符串
  7.  

请注意,对Perl来说双引号内的字符串直接量"barney"和单引号内的字符串直接量'barney'是相同的,都是代表那6个字符组成的字符串。

反斜线后面跟上不同字符,可以表示各种不同的意义。

双引号内字符串可以使用变量内插(variable interpolated)的写法,说白了就是在字符串中附带变量,结果用变量内的值替换。

字符串操作符

字符串可以用 . 操作符拼接起来,两边的原始字符串都不会因此操作而被修改,就像 2+3 的运算不会改变2或3一样。运算后得到一个更长的字符串,可以继续用于其他运算或保存在某个变量中。来看具体例子:

  1. "hello" . "world"       # 等同于"helloworld"
  2. "hello" . ' ' . "world" # 等同于"hello world"
  3. "hello world" . "\n"    # 等同于"hello world\n"

要注意的是,连接运算必须显示使用串接操作符,而不像其他某些语言那样,把两个字符串前后写在一起就算拼接。

还有个比较特殊的字符串重复操作符,记作小写的字母x。此操作符会将其左边的操作数与它本身重复连接,重复次数则由右边的操作数(某个数字)指定。来看具体的例子:

  1. "fred" x 3          # "fredfredfred"
  2. "barney" x (1 + 1)  # "barney" x 2,亦即"barneybarney"
  3. 5 x 4.8             # 本质上就是"5" x 4,所以得到"5555"

最后一个例子有必要详加说明。因为重复操作符得左操作数必然是字符串类型,所以数字5在进行重复操作前,先被转化成单字符得的字符串"5",然后这个新字符串被复制4次,生成含有4个字符的新字符串5555。

重复次数(右操作数)在使用前会先取整(4.8变成4)。重复次数小于1时,会生成长度为0的空字符串。

Perl的内置警告信息

当Perl发现程序代码中有可疑之处时,可以通过警告信息提示你。从Perl5.6开始,我们可以通过编译指令启用警告机制:

  1. #!/usr/bin/perl
  2. use warnings;

也可以在命令行调用程序时给出-w选项表示启用警告机制。这种方式的作用域包括引入模块的代码,所以可能会看到有关别人代码的警告信息:

  • $ perl -w my_program

或者在shebang行启用警告选项:

  1. #!/usr/bin/perl -w

现在,如果你把"12fred34"当数字用,Perl就会给你发出警告信息:

  • Argument "12fred34" isn't numeric

虽然发出了警告,当Perl仍旧会按照规则把非数字的"12fred34"转化为数字12。

警告信息是给开发者看的,而不是给最终使用者。既然开发者看不到你的这些警告信息,也就没有啥用处了,所以启用全局警告反而容易带来一些噪音。启用警告机制只是让Perl提示一下可能有问题的地方,但Perl绝不擅自改变代码的行为。如果看不明白警告信息说的是什么,可以利用diagnostics编译指令。在perldiag文档中列出了每个警告信息的简短描述和详尽解释,这些都是diagnostics报告的核心内容:

  1. use diagnostics;

把use diagnostics加进程序后,可能你觉得程序启动有点慢。确实如此,这是程序需要预先加载警告和详细说明到内存,以便碰到需要时可以立即输出相关的错误信息。所以反过来看的话,这也提示了一种优化程序的方法:如果熟悉各种警告信息的含义,就不必在运行时发出详细的警告解释,去掉use diagnostics这个编译指令就会使程序启动变快,内存消耗也随之减少。当然,如果能修好程序,让它不再产生烦人的警告信息就再好不过了,或者干脆直接关闭警告信息。

更近一步,这种优化也可以通过Perl的命令行选项-M来实现。与其每次修改程序代码,不如仅在需要时加载diagnostics编译指令:

注意警告信息中出现的(W numeric),其中W的意思是警告级别属于普通警告,numeric的意思是警告类型属于数字操作一类。所以,,看到这两条就知道问题大致出在哪里。

随着后续深入介绍,我们还会看到其他错误类型的警告。不过请注意,将来Perl给出的警告文字可能会有所变化,所以不要依赖这些具体文字做应对的处理。

标量变量

所谓变量(variable),就是存储一个或多个值的容器。而标量变量,就是保存一个值的变量。后续章节我们还会看到其他类型的变量,比如数组和哈希,它们可以存储多个值。变量的名称在整个程序中保持不变,但它所持有的值可以在程序运行时不断修改变化。

Perl通过变量标识符前的魔符来区分变量的类型。所以不管你为变量取什么名字,都不会和Perl自带的函数或操作符的名字相冲突。

此外,Perl是通过魔符来判断改变量的使用意图。$的确切意思是“取单个东西”或者“取标量”。因为标量变量总是存储一项数据,所以它的意思就总是取得其中的“单个东西”的值。在第3章中你会看到“取单个东西”的魔符应用于其他类型变量的情况,比如说数组。这是Perl很关键的一个概念,魔符并不意味着变量的类型,而是表征取用变量数据的方式

字符中的标量变量内插

一般我们用双引号圈引字符串,是希望把其中的变量替换为变量当前的内容,从而成为新的字符串。这个转变得过程我们称之为变量内插,来看示例:

  1. $meal = "brontosaurus steak";
  2. $barney = "fred ate a $meal";       # $barney 变成 "fred ate brontosaurus steak"
  3. $barney = 'fred ate a ' . $meal;    # 效果相同的另一种写法

正如上面最后一行所示,不用双引号一样可以得到相同结果,但就书写和可读性来说,显然不如变量内插方式直观方便。变量内插有时候也被称作双引号内插,因为只有在双引号内部才可以这么做,在单引号中不行。Perl里面还有其他内插方式,之后我们会逐一介绍。

如果标量变量从未被赋值,内插时将使用空字符串替换。稍后在本章介绍空值undef的时候,将看到更多类似的情况。

如果内插时单单就是变量本身,不写双引号当然也可行。形式上,在单个变量两边加上双引号表示变量内插并不算错,但既然不是要构造更长的字符串,就没必要使用双引号的方式内插。

我们用美元符号表示将要内插的变量名,那么当我们真的希望打印美元符号本身的时候,用转义就行了。方法还是原来那套,用反斜线取消美元符号的特殊意义:

  1. $fred = 'hello';
  2. print "The name is \$fred.\n";      #打印美元符号

换种思路,如果不需要内插,那就直接单引号好了,免得混淆视线:

  1. print 'The name is $fred' . "\n";   #效果同上

变量内插时,Perl的原则是选用尽可能长且合法的变量名。所以,如果在内插变量名后面需要紧跟着合法的变量名字符,即字母、数字或下划线时,Perl就无从判断,需要引入免除歧义的写法。

解决思路就是隔离。和在shell下的写法相同,我们可以在变量名两边加上花括号,明确表示需要内插的变量。或者在变量名结尾断开,另外采用字符串串接操作符连接:

  1. $what = "brontosaurus steak";
  2. $n = 3;
  3. print "fread ate $n $whats.\n";      #不是牛排,而是变量 $whats 的值
  4. print "fread ate $n ${what}s.\n";    #内插的是 $what
  5. print "fread ate $n $what" . "s.\n"; #效果同上
  6. print "fread ate " . $n . ' ' . $what . "s.\n"; #另一种折腾的写法

比较操作符

比较数字大小时,Perl的比较操作符类似于代数系统:<、<=、==、>=、>、!=,这个是符合我们常规逻辑的。这些操作符的返回值要么是真(true),要么是假(false),我们将会在下一节详细讨论这些返回值的含义。这些操作符在Perl中的写法和在其他语言中的写法可能略有不同。比如,“相等”用的是 == 而不是单个的 =,因为=在Perl里表示变量赋值。另外,“不相等”用的是!=,因为<>在Perl里另有其义。而“大于等于”用的是>=不是=>,因为后者在Perl里也有别的意义。

比较字符串时,Perl有一系列的字符串比较操作符,看起来像是些奇怪的短语:lt、le、eq、ge以及ne。它们会逐一对比两个字符串里的字符,判定它们是否彼此相等或是哪一个排在前面。请注意,字符在ASCII编码或者Unicode编码中的顺序并不总是对应于字符本身意义上的顺序。