perl 将hash的键列表赋值给数组_深入理解Perl的变量

本文深入探讨Perl变量的特性,包括它们作为引用的本质,原地修改内存数据的机制,以及Sigil(变量前缀)在访问内存空间中的作用。Perl的$、@、%等 Sigil 分别对应标量、数组和哈希,影响内存的划分和访问方式。此外,还介绍了如何通过引用进行赋值操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Perl变量是一个引用

对于下面的变量赋值代码,这表示将内存中的字符串数据"junmajinlong"保存到变量name中去。

my $name = "junmajinlong";

注意,「这里的变量名是name,而不是,前缀符号具有特殊意义」。不仅如此,数组变量的前缀@、hash变量的前缀%也一样如此。这些前缀的意义将在后文详细解释。

# 下面两个变量的变量名分别为names和person,而不是@names、%person
my @names = ("junmajinlong", "gaoxiaofang", "fairy");
my %person = (name => "junmajinlong", age => 23, );

「Perl变量完全使用引用的方式去存储数据」。这意味着,对于上面的name变量,它保存的是指向字符串数据"junmajinlong"的引用,而不是直接保存该字符串,或者干脆说变量name就是一个指向字符串数据的引用。

严格来说,perl会在堆内存中存放好字符串数据"junmajinlong",然后将该内存空间的地址保存在栈中某个位置,变量name就是这个栈中数据。

如下图所示:

c8bc3bfd35c28d2a0c215efd34f3f717.png

Perl允许原地修改内存数据

「Perl允许原地修改内存数据」。这意味着修改变量的数据或为变量重新赋值时,修改后的数据或新数据直接保存在原内存中,变量保存在栈中的引用地址不会发生改变,也即它所指向的内存仍然是原来的内存。

但注意,重新声明同名变量时,会创建新的变量并掩盖(mask)原有的同名变量,它们的内存地址是不一样的。

如图:

b5f212ecf3b4e030d2fb8c6dcef976ee.png

在Perl中,「可以在某个变量前加上反斜线来获取该变量所保存实际数据的内存地址」,例如\$name将获取变量name所保存堆内存数据的地址。

my $name = "junmajinlong";
print \$name;     #  输出:SCALAR(0x685bf8)

因此,只要\$x\$y获取到的地址是相同的,那么就可以说明变量x和变量y指向了同一个内存位置,反之,如果地址不同,则说明它们是不同变量。

因此,也可以用代码验证Perl是否真的是在原地修改内存数据的。

use v5.12;

my $x = 33;
say "x: $x, ", "addr: ", \$x;

# 为变量重新赋值,变量指向的内存地址不变
$x = 44;
say "x: $x, ", "addr: ", \$x;

# 修改变量数据,变量指向的内存地址不变
++$x;
say "x: $x, ", "addr: ", \$x;

# 但重新声明同名变量,原有变量将被掩盖,新变量指向新内存地址
my $x = 55;
say "x: $x, ", "addr: ", \$x;

输出结果:

x: 33, addr: SCALAR(0x6a05c0)
x: 44, addr: SCALAR(0x6a05c0)
x: 45, addr: SCALAR(0x6a05c0)
x: 55, addr: SCALAR(0x6a0bd8)

注意,其他有些语言,不允许原地修改数据,这意味着修改变量数据时,会申请一个新的内存空间来存放修改后的数据,然后让变量指向新的内存空间。这种方式会让变量所保存的地址发生改变。如图:

9e5cb16b029a002520cafe9ba40f6155.png

理解Perl的变量前缀:sigil

下面的代码声明了变量name并为其赋值,然后在print中使用了该变量:

my $name = "junmajinlong";
print "$name\n";

需要注意的是,变量名为name,而不是$name

Perl在做变量赋值时、在使用变量时,都会在变量前加上变量前缀。对于标量标量,它的前缀是$符号,对于数组变量,它的前缀是@符号,对于hash变量,它的前缀是%。这些特殊的前缀,在Perl中称为Sigil。

Sigil前缀隐含了多种含义,其中之二是:

  • (1).「根据变量所保存的内存地址去访问该地址所指向的内存空间」
  • (2).「根据Sigil类型决定如何划分以及如何访问内存空间」
    • $前缀表示标量,只划分或只访问一个内存数据空间
    • @前缀表示数组,划分或访问多个内存数据空间
    • %前缀表示hash,划分或访问多个用于存放key-value的内存数据空间

下面将详细介绍Sigil的这两种含义,更多的含义要在以后遇到了再做解释。

对于含义(1):「根据变量所保存的内存地址去访问该地址所指向的内存空间」

也就是说,Sigil前缀的作用类似于导航的功能,根据变量名导航到该变量对应的内存空间处。至于导航到那里之后,是读取内存数据还是向该内存写入数据,由上下文决定。

例如,print "$name";语句中使用了$name,perl将根据变量name在栈中所保存的地址找到对应的内存空间。由于该语句中的"$name"是print参数的一部分,因此perl会将该内存地址处的数据"junmajinlong"读取出来并进行变量内插。

「当带有Sigil前缀的变量放在赋值操作符=的左边时,表示将数据保存到该变量所指向的内存中」。例如$name = "junmajinlong",perl解析该语句时,发现$name出现在左边,perl就知道这是为变量name赋值,perl会找到name变量并将字符串数据放进变量name所指向的内存空间处。如果找不到name变量,则声明name变量并为其初始化赋值为undef。

也就是说,「变量初始化之后就在它的栈中保存了一个指向堆内存中某块空间的地址,由于Perl允许原地修改内存,因此栈中的这个地址在perl程序运行期间将永不改变」

use v5.12;

my $x;
say "addr x: ", \$x;

$x = "hello";
say "addr x: ", \$x;

$x = 33;
say "addr x: ", \$x;
# 输出结果:
#   addr x: SCALAR(0x26169b8)
#   addr x: SCALAR(0x26169b8)
#   addr x: SCALAR(0x26169b8)

此处需要注意,虽然重新声明同名变量会掩盖已有变量使之不可用,但原有变量并未失效,它仍然指向某个堆内存地址,并将持续到程序退出。

对于含义(2):「根据Sigil类型决定如何划分以及如何访问内存空间」

例如,当perl发现赋值操作符左边的是$name,由于前缀$表示标量,perl就知道要为变量name划分一个内存数据空间用来保存单个标量数据。因此,「$前缀的意思是:要访问或要保存单份数据,而不是多份数据」

当perl发现赋值操作符左边的是@names,由于前缀@表示数组,perl就知道要为变量names划分多个内存数据空间,至于具体要划分多少个内存数据空间,perl将自己决定。「@前缀的意思是:要访问或者要保存多份数据,而不是单独的一份标量数据」

「%前缀的意思是:要访问或者要保存多份key/value键值对数据,而不是单独的一份标量数据,也不是多份没有键值对映射关系的数据」

如果理解了以上结论,那么就很容易理解下面这两个Perl中令人感觉别扭的用法:

  • 访问数组、hash的某个元素时,使用$前缀,而不是@或%前缀,这是因为访问单个元素时,是要访问单个内存数据空间
  • 要进行切片时,使用的是@前缀,因为访问多个元素时,是要访问多个内存数据空间
my @arr = (11, 22, 33);
my %person = (name => "junmajinlong", age => 23,);
# 访问数组单个元素
say $arr[1];
# 访问hash单个元素的值
say $person{name};

# 数组切片,保存到数组slice_arr中
my @slice_arr = @arr[0, 2];
say "@slice_arr";

# hash切片,保存到数组slice_hash中
my @slice_hash = @person{"name", "age"};
say "@slice_hash";

关于数组、hash和切片,后面的文章将会详细介绍。

初步理解Perl的引用

由于可以通过前缀加变量名取得变量所指向的堆内存数据,而变量本身是一个引用,因此,可以换一个角度来思考,只要获取到一个堆数据的引用并将其保存到变量中,就可以在这个变量前面加上前缀来取得它所指向的堆内存数据。

在Perl中,获取变量的引用的方式是在变量前加上反斜线,例如\$name得到变量name所保存堆数据的引用。

# 变量name是一个引用,指向堆数据"junmajinlong"
# 即:(name)0xabc -> "junmajinlong"
my $name = "junmajinlong";

# 获取堆数据的引用,即它的地址,将地址保存到另一个变量中
my $name_ref = \$name;

现在变量name_ref保存的实际数据是指向"junmajinlong"的地址。

注意,变量name_ref本身是一个标量变量,而每个变量都是一个引用,它在栈中保存一个指向实际数据的地址。因此,既然name_ref保存的实际数据是指向"junmajinlong"的地址,那么name_ref变量在栈中保存的是一个指向该地址的地址。即:

(name_ref)0xdef -> 0xabc
0xabc -> "junmajinlong"

看图更容易理解:

b1a77c640cc847b5ac75f98faf5ae24b.png

因此,$name_ref等价于变量name,它们都是指向堆中字符串数据的地址。

既然可以使用$name获取堆中字符串数据,当然也可以使用$$name_ref获取这份堆中字符串数据。或者换个写法会更清晰:${name}${$name_ref}是等价的,因为name$name_ref是等价的。

my $name = "junmajinlong";
my $name_ref = \$name;

print "$name\n";        # ${name}
print "$$name_ref\n";   # ${$name_ref}

输出:

junmajinlong
junmajinlong

理解Perl的赋值:左值lvalue

当Sigil出现在赋值操作符左边时,表示对变量进行赋值。例如:

my $name = "junmajinlong";

这个赋值过程实际上是找到name变量的内存地址,然后将字符串数据写入该内存。

但是,Perl并不仅仅只允许为变量赋值,还可以为数组的元素赋值,为hash的元素赋值,甚至还可以为某些函数调用后的返回结果进行赋值。

# 变量赋值
my $name = "junmajinlong";

# 为数组元素赋值
my @arr;
$arr[0] = "junmajinlong";

# 为hash元素赋值
my %person;
$person{name} = "junmajinlong";

# 对某些函数调用的返回结果赋值
my $name = 'fred';
substr($name, 4) = 'dy';

实际上,「所有可以进行赋值的目标都出现在赋值操作符的左边,它们有一个更为通用的称呼:左值(lvalue)」

与左值对应的概念是右值。左值和右值并非是Perl独有的概念,而是编程领域中的通用概念:「左值用来向内存中保存数据,右值用来从内存读取数据(即从内存返回数据)」

当我们说某个东西是左值或表现为左值的时候,就代表可以通过它写入数据从而将数据保存到内存中。

例如,Perl中的变量是一种左值,Perl的substr函数也可以表现为左值,Perl中赋值操作的返回值也可以作为左值。

(my $name = "junmajinlong") =~ s/j/J/;
say "$name";   # 输出:Junmajinlong
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值