Perl 完全指南 / 第 4 章:变量与数据类型
第 4 章:变量与数据类型
“数据是程序的灵魂” — 匿名
Perl 的变量系统是其最具特色的部分之一。通过变量名前缀区分类型,配合上下文(context)机制,Perl 提供了极其灵活的数据操作能力。
4.1 变量前缀系统
Perl 使用 Sigil(符号前缀)来区分变量类型:
| 前缀 | 类型 | 含义 | 示例 |
|---|---|---|---|
$ | Scalar | 标量(单个值) | $name = "Perl" |
@ | Array | 数组(有序列表) | @nums = (1, 2, 3) |
% | Hash | 哈希(键值对) | %info = (name => "Perl") |
& | Code | 子程序引用 | &my_func() |
* | Glob | 类型全局量 | *STDOUT |
注意:同一名称可以用不同前缀表示不同类型:
$name、@name、%name是三个不同的变量。
4.2 标量(Scalar)
标量是 Perl 中最基本的数据类型,存储单个值——可以是数字或字符串。
数字标量
use strict;
use warnings;
my $integer = 42; # 整数
my $float = 3.14159; # 浮点数
my $negative = -17; # 负数
my $octal = 0777; # 八进制 (511)
my $hex = 0xFF; # 十六进制 (255)
my $binary = 0b1010; # 二进制 (10)
my $large = 1_000_000; # 下划线分隔 (1000000)
my $sci = 2.5e10; # 科学计数法
print "整数: $integer\n";
print "浮点: $float\n";
print "科学: $sci\n";
字符串标量
my $single = '单引号 - 不插值 $variable'; # 单引号:原样输出
my $double = "双引号 - 插值 $variable\n"; # 双引号:变量插值
my $escape = "换行\n制表\t反斜杠\\"; # 支持转义字符
单引号 vs 双引号
| 特性 | 单引号 '' | 双引号 "" |
|---|---|---|
| 变量插值 | ❌ | ✅ |
\n 换行 | ❌ | ✅ |
\t 制表 | ❌ | ✅ |
\\ 反斜杠 | ✅(\' 也行) | ✅ |
| 性能 | 略快 | 略慢 |
特殊标量值
my $undef = undef; # 未定义值
my $empty = ""; # 空字符串
my $zero = "0"; # 字符串零
my $bool_t = 1; # 真
my $bool_f = 0; # 假
# undef 在数值上下文中是 0,在字符串上下文中是 ""
printf "数值: %d\n", undef; # 0
printf "字符串: '%s'\n", undef; # ''
布尔值
Perl 没有专门的布尔类型。以下值为 假(false):
# 假值列表:
0 # 数字零
0.0 # 浮点零
"0" # 字符串零
"" # 空字符串
undef # 未定义
() # 空列表(在某些上下文中)
其他一切为真:
# 以下都是真:
1
-1
"0.0"
"false" # 这是真!字符串"false"不是假
" " # 空格也是真
警告:字符串
"0"是假值,但字符串"false"和"0.0"是真值!这是 Perl 新手常见的陷阱。
4.3 数组(Array)
数组存储有序的标量列表。
数组操作
use strict;
use warnings;
# 创建数组
my @fruits = ("apple", "banana", "cherry");
my @nums = 1..10; # 范围操作符
my @mixed = (1, "two", 3.0, undef); # 混合类型
my @empty = (); # 空数组
# 访问元素(下标从 0 开始)
print $fruits[0], "\n"; # apple
print $fruits[-1], "\n"; # cherry(负索引从末尾开始)
print $fruits[99], "\n"; # undef(越界不报错)
# 修改元素
$fruits[1] = "blueberry";
# 添加元素
push @fruits, "date"; # 末尾添加
unshift @fruits, "avocado"; # 开头添加
# 删除元素
my $last = pop @fruits; # 末尾弹出
my $first = shift @fruits; # 开头弹出
# 数组长度
my $count = @fruits; # 标量上下文 = 元素个数
my $count2 = scalar @fruits; # 显式标量上下文
数组常用函数
| 函数 | 说明 | 示例 |
|---|---|---|
push @a, $v | 末尾添加 | push @arr, "new" |
pop @a | 末尾弹出 | my $x = pop @arr |
unshift @a, $v | 开头添加 | unshift @arr, "first" |
shift @a | 开头弹出 | my $x = shift @arr |
splice @a, $o, $l | 删除/替换元素 | splice @arr, 2, 1 |
sort @a | 排序 | my @s = sort @arr |
reverse @a | 反转 | my @r = reverse @arr |
grep { } @a | 过滤 | my @f = grep { $_ > 3 } @arr |
map { } @a | 映射 | my @m = map { $_ * 2 } @arr |
join $s, @a | 连接 | join ", ", @arr |
split $p, $s | 分割 | split /,/, $str |
scalar @a | 元素个数 | scalar @arr |
数组切片
my @colors = qw(red green blue yellow purple);
# 列表切片
my @selected = @colors[1, 3]; # ("green", "yellow")
my @range = @colors[1..3]; # ("green", "blue", "yellow")
# 赋值切片
@colors[0, 2] = ("white", "black"); # 修改多个元素
4.4 哈希(Hash)
哈希存储键值对,键必须是字符串。
哈希操作
use strict;
use warnings;
# 创建哈希
my %person = (
name => "张三",
age => 30,
email => "zhangsan@example.com",
);
# 也可以用 => 或逗号
my %same = (
"name", "张三",
"age", 30,
);
# 访问值
print $person{name}, "\n"; # 张三
print $person{age}, "\n"; # 30
print $person{phone}, "\n"; # undef(不存在的键)
# 修改/添加
$person{city} = "北京"; # 添加新键值对
$person{age} = 31; # 修改已有值
# 删除
delete $person{email};
# 检查键是否存在
if (exists $person{name}) {
print "name 存在\n";
}
# 获取所有键/值
my @keys = keys %person;
my @values = values %person;
my $count = keys %person; # 键值对个数
# 遍历
while (my ($key, $value) = each %person) {
print "$key => $value\n";
}
哈希切片
my %info = (name => "张三", age => 30, city => "北京");
# 批量获取
my ($name, $city) = @info{qw(name city)};
# 批量赋值
@info{qw(age city)} = (31, "上海");
哈希排序
my %scores = (张三 => 85, 李四 => 92, 王五 => 78);
# 按键排序
for my $name (sort keys %scores) {
print "$name: $scores{$name}\n";
}
# 按值排序
for my $name (sort { $scores{$b} <=> $scores{$a} } keys %scores) {
print "$name: $scores{$name}\n";
}
4.5 上下文(Context)
上下文是 Perl 最独特的特性之一。同一个表达式在不同上下文中会产生不同的结果。
三种上下文
| 上下文 | 说明 | 触发条件 |
|---|---|---|
| 标量上下文 | 期望单个值 | $x = ...、scalar(...) |
| 列表上下文 | 期望多个值 | @x = ...、(...)、print ... |
| 布尔上下文 | 期望真/假 | if(...)、while(...) |
上下文的影响
my @array = (1, 2, 3, 4, 5);
# 标量上下文 → 元素个数
my $count = @array; # 5
my $count2 = scalar @array; # 5
# 列表上下文 → 元素本身
my @copy = @array; # (1, 2, 3, 4, 5)
my ($first, $second) = @array; # $first=1, $second=2
# 函数在不同上下文中的行为
my $line = <STDIN>; # 标量上下文:读一行
my @lines = <STDIN>; # 列表上下文:读所有行
# localtime
my $time_string = localtime; # 标量上下文:"Sat May 10 14:30:00 2026"
my @time_parts = localtime; # 列表上下文:(秒, 分, 时, ...)
常见上下文陷阱
# 陷阱 1:print 是列表上下文
print @array; # 打印所有元素(无空格)
print scalar @array; # 打印元素个数
# 陷阱 2:比较运算符的上下文
# 数值比较
$a <=> $b # 数值比较,返回 -1, 0, 1
# 字符串比较
$a cmp $b # 字符串比较,返回 -1, 0, 1
# 陷阱 3:sort 的上下文
my @sorted = sort @array; # 列表上下文:返回排序后的列表
my $count = sort @array; # 标量上下文:返回 undef(不是个数!)
4.6 变量声明与作用域
my — 词法作用域变量
use strict;
use warnings;
my $x = 10; # 文件作用域
{
my $y = 20; # 块作用域
print $x + $y, "\n"; # 30
}
# print $y; # 错误!$y 在块外不可见
our — 包变量声明
package MyApp;
our $VERSION = "1.00"; # 包全局变量
our @EXPORT = qw(func1); # 与 Exporter 配合使用
state — 持久局部变量(Perl 5.10+)
use strict;
use warnings;
sub counter {
state $count = 0; # 只初始化一次
$count++;
return $count;
}
print counter(), "\n"; # 1
print counter(), "\n"; # 2
print counter(), "\n"; # 3
local — 临时修改全局变量
# local 临时备份并修改全局变量,作用域结束时恢复
$main::greeting = "Hello";
{
local $main::greeting = "Hi";
print $main::greeting, "\n"; # Hi
}
print $main::greeting, "\n"; # Hello(已恢复)
声明方式对比
| 方式 | 作用域 | 推荐度 | 说明 |
|---|---|---|---|
my | 词法作用域 | ⭐⭐⭐⭐⭐ | 最常用,strict 模式下必须 |
our | 包作用域 | ⭐⭐⭐ | 配合 Exporter 使用 |
state | 词法 + 持久 | ⭐⭐⭐⭐ | 类似 C 的 static 变量 |
local | 临时修改 | ⭐⭐ | 改全局变量,较少使用 |
| 无声明 | 全局 | ❌ | strict 下禁止 |
4.7 类型转换
字符串 → 数字
my $str = "42";
my $num = $str + 0; # 数字 42
my $str2 = "3.14pi";
my $num2 = $str2 + 0; # 3.14(截断到非数字字符)
my $str3 = "hello";
my $num3 = $str3 + 0; # 0(警告:Argument "hello" isn't numeric)
数字 → 字符串
my $num = 42;
my $str = "$num"; # "42"
my $str2 = $num . ""; # "42"
my $str3 = sprintf "%d", $num; # "42"
类型转换速查表
| 转换 | 方法 | 示例 |
|---|---|---|
| 字符串→数字 | + 0 | "42" + 0 → 42 |
| 数字→字符串 | "" 拼接 | 42 . "" → “42” |
| 数字→整数 | int() | int(3.9) → 3 |
| 数字→布尔 | 自动 | if ($num) |
| 字符串→数组 | split | split /,/, $str |
| 数组→字符串 | join | join ",", @arr |
| 数组→哈希 | 列表赋值 | %hash = @array |
| 哈希→数组 | keys/values | @k = keys %hash |
4.8 特殊变量
常用特殊变量
| 变量 | 含义 | 示例 |
|---|---|---|
$_ | 默认变量 | print for @array; |
@_ | 子程序参数 | sub f { my ($x) = @_; } |
$. | 当前行号 | while (<>) { print $.; } |
$! | 错误信息 | open(...) or die $! |
$@ | eval 错误 | eval { ... }; warn $@ if $@; |
$/ | 输入分隔符 | $/ = undef; # slurp 模式 |
$\ | 输出分隔符 | $\ = "\n"; |
$, | print 分隔符 | $, = ", "; |
$" | 列表分隔符 | $" = ", "; |
4.9 业务场景:配置文件解析器
#!/usr/bin/env perl
use strict;
use warnings;
# 简单的 INI 配置文件解析器
sub parse_config {
my ($filename) = @_;
my %config;
my $section = 'default';
open my $fh, '<', $filename or die "无法打开 $filename: $!\n";
while (my $line = <$fh>) {
chomp $line;
$line =~ s/#.*//; # 移除注释
$line =~ s/^\s+|\s+$//g; # 去除首尾空格
next unless $line; # 跳过空行
if ($line =~ /^\[(.+)\]$/) {
$section = $1;
} elsif ($line =~ /^(\w+)\s*=\s*(.+)$/) {
$config{$section}{$1} = $2;
}
}
close $fh;
return %config;
}
# 使用示例
my %cfg = parse_config("app.conf");
for my $section (sort keys %cfg) {
print "[$section]\n";
for my $key (sort keys %{$cfg{$section}}) {
print " $key = $cfg{$section}{$key}\n";
}
}
本章小结
| 要点 | 内容 |
|---|---|
$ 标量 | 存储单个值(数字或字符串) |
@ 数组 | 有序列表,下标从 0 开始 |
% 哈希 | 键值对,键必须是字符串 |
| 上下文 | 同一表达式在不同上下文产生不同结果 |
my | 词法作用域变量,strict 模式下必须使用 |
state | 持久局部变量,只初始化一次 |
| 转换 | +0 转数字,. "" 转字符串 |
练习
- 创建一个包含 5 种水果的数组,打印中间元素
- 创建一个人的信息哈希(姓名、年龄、城市),遍历输出
- 解释
my @a = (1,2,3); my $b = @a;中$b的值 - 编写一个子程序,使用
state变量实现调用计数器 - 解释为什么
"0"是假值但"0.0"是真值