强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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)
字符串→数组splitsplit /,/, $str
数组→字符串joinjoin ",", @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 转数字,. "" 转字符串

练习

  1. 创建一个包含 5 种水果的数组,打印中间元素
  2. 创建一个人的信息哈希(姓名、年龄、城市),遍历输出
  3. 解释 my @a = (1,2,3); my $b = @a;$b 的值
  4. 编写一个子程序,使用 state 变量实现调用计数器
  5. 解释为什么 "0" 是假值但 "0.0" 是真值

扩展阅读