第 5 章:运算符
第 5 章:运算符
“运算符是表达式的骨架”
Perl 拥有极其丰富的运算符系统,尤其擅长字符串操作和正则表达式。
5.1 算术运算符
| 运算符 | 说明 | 示例 | 结果 |
|---|
+ | 加法 | 3 + 2 | 5 |
- | 减法 | 3 - 2 | 1 |
* | 乘法 | 3 * 2 | 6 |
/ | 除法 | 7 / 2 | 3.5 |
% | 取模 | 7 % 2 | 1 |
** | 幂运算 | 2 ** 10 | 1024 |
++ | 自增 | $x++ | 先返回再加 |
-- | 自减 | --$x | 先减再返回 |
use strict;
use warnings;
my $a = 10;
my $b = 3;
printf "%d + %d = %d\n", $a, $b, $a + $b;
printf "%d - %d = %d\n", $a, $b, $a - $b;
printf "%d * %d = %d\n", $a, $b, $a * $b;
printf "%d / %d = %.2f\n", $a, $b, $a / $b;
printf "%d %% %d = %d\n", $a, $b, $a % $b;
printf "%d ** %d = %d\n", 2, 10, 2 ** 10;
# 自增自减
my $x = 5;
print $x++, "\n"; # 输出 5,然后 $x 变为 6
print $x, "\n"; # 输出 6
print ++$x, "\n"; # $x 变为 7,输出 7
5.2 字符串运算符
| 运算符 | 说明 | 示例 | 结果 |
|---|
. | 字符串拼接 | "Hello" . " " . "World" | "Hello World" |
x | 字符串重复 | "ab" x 3 | "ababab" |
ne | 字符串不等于 | "abc" ne "def" | 1 (真) |
eq | 字符串等于 | "abc" eq "abc" | 1 (真) |
lt | 字符串小于 | "abc" lt "def" | 1 (真) |
gt | 字符串大于 | "def" gt "abc" | 1 (真) |
le | 字符串小于等于 | "abc" le "abc" | 1 (真) |
ge | 字符串大于等于 | "def" ge "abc" | 1 (真) |
cmp | 字符串比较 | "abc" cmp "def" | -1 |
# 拼接
my $greeting = "Hello" . " " . "World" . "!";
print "$greeting\n"; # Hello World!
# 重复
my $line = "-" x 40;
print "$line\n"; # ----------------------------------------
# 字符串比较
my $a = "apple";
my $b = "banana";
if ($a lt $b) {
print "$a 在 $b 前面(字典序)\n";
}
# cmp 三路比较
my $result = $a cmp $b;
print "比较结果: $result\n"; # -1($a < $b)
字符串比较 vs 数值比较
# 这是 Perl 新手最容易犯的错误!
my $x = "02";
my $y = "10";
# 字符串比较(字典序)
print "字符串: $x eq $y ? ", ($x eq $y ? "是" : "否"), "\n"; # 否
print "字符串: $x gt $y ? ", ($x gt $y ? "是" : "否"), "\n"; # 是('0' > '1' 错误,实际 '02' gt '10' 因为 '0' < '1')
# 数值比较
print "数值: $x == $y ? ", ($x == $y ? "是" : "否"), "\n"; # 否
print "数值: $x > $y ? ", ($x > $y ? "是" : "否"), "\n"; # 否
# 正确理解:
# "02" lt "10" 因为 '0' 的 ASCII 码 (48) < '1' 的 ASCII 码 (49)
# 2 < 10 数值上
| 比较类型 | 运算符 | 示例 |
|---|
| 数值相等 | == | $a == $b |
| 数值不等 | != | $a != $b |
| 数值大小 | <, >, <=, >= | $a > $b |
| 数值三路 | <=> | $a <=> $b |
| 字符串相等 | eq | $a eq $b |
| 字符串不等 | ne | $a ne $b |
| 字符串大小 | lt, gt, le, ge | $a gt $b |
| 字符串三路 | cmp | $a cmp $b |
5.3 逻辑运算符
传统风格
| 运算符 | 说明 | 短路 |
|---|
&& | 逻辑与 | ✅ 左侧为假则不计算右侧 |
|| | 逻辑或 | ✅ 左侧为真则不计算右侧 |
! | 逻辑非 | - |
词组风格(推荐)
| 运算符 | 说明 | 短路 |
|---|
and | 逻辑与 | ✅ |
or | 逻辑或 | ✅ |
not | 逻辑非 | - |
# 传统风格(优先级高)
my $x = $a && $b; # $a 为真则返回 $b,否则返回 $a
my $y = $a || $b; # $a 为真则返回 $a,否则返回 $b
# 词组风格(优先级低,适合流程控制)
open my $fh, '<', 'file.txt' or die "无法打开: $!\n";
send_email($to, $body) and log("邮件已发送");
逻辑运算符的返回值
# && 返回第一个假值或最后一个值
print 0 && "hello"; # 0
print 1 && "hello"; # "hello"
print "a" && "b" && "c"; # "c"
# || 返回第一个真值或最后一个值
print 0 || "hello"; # "hello"
print "a" || "b"; # "a"
print 0 || "" || undef; # undef
# // (Perl 5.10+) - 定义或运算符
# 返回第一个已定义的值
my $name = $input // "默认值"; # $input 未定义则用默认值
5.4 位运算符
| 运算符 | 说明 | 示例 |
|---|
& | 按位与 | 0b1010 & 0b1100 → 0b1000 |
| | 按位或 | 0b1010 | 0b1100 → 0b1110 |
^ | 按位异或 | 0b1010 ^ 0b1100 → 0b0110 |
~ | 按位取反 | ~0b1010 → 取反结果 |
<< | 左移 | 1 << 3 → 8 |
>> | 右移 | 16 >> 2 → 4 |
my $perms = 0755;
printf "权限: %04o\n", $perms; # 0755
printf "所有者: %03o\n", ($perms >> 6) & 07;
printf "组: %03o\n", ($perms >> 3) & 07;
printf "其他: %03o\n", $perms & 07;
5.5 赋值运算符
| 运算符 | 等价于 | 说明 |
|---|
$x += 5 | $x = $x + 5 | 加法赋值 |
$x -= 5 | $x = $x - 5 | 减法赋值 |
$x *= 2 | $x = $x * 2 | 乘法赋值 |
$x /= 2 | $x = $x / 2 | 除法赋值 |
$x .= "str" | $x = $x . "str" | 字符串拼接赋值 |
$x x= 3 | $x = $x x 3 | 字符串重复赋值 |
$x //= "val" | $x = $x // "val" | 定义或赋值 |
$x &&= "val" | $x = $x && "val" | 与赋值 |
$x ||= "val" | $x = $x || "val" | 或赋值 |
# 默认值设置
my $name = $ENV{USER} // "anonymous";
my $count ||= 0; # 如果未定义则设为 0
# 字符串构建
my $html = "";
$html .= "<h1>标题</h1>\n";
$html .= "<p>内容</p>\n";
5.6 范围运算符
数字范围
my @nums = 1..10; # (1, 2, 3, ..., 10)
my @reverse = 10..1; # () 空!范围必须从小到大
my @letters = 'a'..'z'; # (a, b, c, ..., z)
范围运算符在布尔上下文中
在布尔上下文中,.. 成为 Flip-Flop 运算符:
# 打印第 3 到第 7 行
while (<STDIN>) {
print if 3..7;
}
# 打印从 START 到 END 之间的内容
while (<STDIN>) {
print if /^START/ .. /^END/;
}
5.7 三元运算符
my $age = 20;
my $status = ($age >= 18) ? "成年人" : "未成年人";
print "$status\n";
# 嵌套三元(不推荐过度嵌套)
my $level = ($score >= 90) ? "优秀"
: ($score >= 80) ? "良好"
: ($score >= 60) ? "及格"
: "不及格";
5.8 正则绑定运算符
正则表达式的详细内容在第 8 章,这里先介绍基本运算符:
| 运算符 | 说明 | 示例 |
|---|
=~ | 绑定匹配 | $str =~ /pattern/ |
!~ | 绑定不匹配 | `$str !~ /pattern/ |
m// | 匹配 | m/pattern/ |
s/// | 替换 | s/old/new/ |
qr// | 引用正则 | my $re = qr/pattern/ |
my $email = "[email protected]";
# 匹配
if ($email =~ /^[\w.]+@[\w.]+\.\w+$/) {
print "有效的邮箱地址\n";
}
# 不匹配
if ($email !~ /spam/i) {
print "不是垃圾邮件\n";
}
# 替换
(my $redacted = $email) =~ s/^(.{3}).*/$1***@***/;
print "$redacted\n"; # use***@***
5.9 逗号运算符与胖箭头
# 逗号 , 是列表运算符
my @list = (1, 2, 3);
# 胖箭头 => 自动为左侧加引号
my %hash = (
name => "Perl", # 等价于 "name" => "Perl"
year => 1987,
motto => "TMTOWTDI",
);
5.10 运算符优先级
从高到低:
| 优先级 | 运算符 | 说明 |
|---|
| 最高 | () [] {} | 括号、下标、块 |
| ** | 幂 |
| ! ~ \ ++ -- | 一元运算符 |
| =~ !~ | 正则绑定 |
| * / % x | 乘法类 |
| + - . | 加法类 |
| << >> | 移位 |
| 命名运算符 | -X 文件测试、print 等 |
| < > <= >= lt gt le ge | 比较 |
| == != <=> eq ne cmp | 相等 |
| & | 按位与 |
| | ^ | 按位或 |
| && | 逻辑与 |
| || // | 逻辑或 |
| .. ... | 范围 |
| ?: | 三元 |
| = += -= 等 | 赋值 |
| 最低 | , => | 逗号 |
| not | 逻辑非 |
| and | 逻辑与 |
| or xor | 逻辑或 |
最佳实践:不确定优先级时,使用括号!
# 不清晰
my $result = $a + $b * $c;
# 清晰明了
my $result = $a + ($b * $c);
5.11 业务场景:模板引擎
#!/usr/bin/env perl
use strict;
use warnings;
# 简单的模板变量替换引擎
sub render_template {
my ($template, %vars) = @_;
# 替换 {{variable}} 占位符
$template =~ s/\{\{(\w+)\}\}/$vars{$1} // "{{$1}}"/ge;
# 替换 {{if condition}}...{{/if}}
$template =~ s/\{\{if (\w+)\}\}(.*?)\{\{\/if\}\}/
$vars{$1} ? $2 : ""/ges;
return $template;
}
my $tpl = <<'TEMPLATE';
<h1>{{title}}</h1>
<p>作者: {{author}}</p>
{{if published}}<span class="badge">已发布</span>{{/if}}
TEMPLATE
my %data = (
title => "Perl 入门教程",
author => "张三",
published => 1,
);
print render_template($tpl, %data);
本章小结
| 要点 | 内容 |
|---|
| 数值 vs 字符串比较 | == vs eq,千万不能混用 |
| 逻辑运算符 | ` |
// | 定义或运算符(Perl 5.10+) |
x | 字符串重复运算符 |
.. | 范围运算符 / Flip-Flop |
| 优先级 | 不确定时加括号 |
练习
- 编写代码验证
"10" eq "10" 和 "10" == "10" 的结果 - 使用
x 运算符生成一个 40 个字符宽的分隔线 - 编写代码演示
|| 和 // 的区别(使用 undef、0、"") - 使用范围运算符生成字母表并打印
- 实现一个简单的计算器,支持
+ - * / % **
扩展阅读