可以在perl中定义其值不能在子例程中更改的变量吗?

如何解决可以在perl中定义其值不能在子例程中更改的变量吗?

在以下脚本中,我在主程序中声明和修改@basearray。在dosomething子例程中,我访问@basearray,将其分配给脚本本地的数组,然后修改本地副本。因为我一直小心地仅更改子例程中局部变量的值,所以@basearray不会被更改。

但是,如果我在为子例程内的@basearray分配值时犯了一个错误,它将被更改,并且该值在调用子例程后仍将保留。

这在第二个子例程doagain中得到了证明。

此外,doagain接收引用\@basearray作为参数,而不是直接访问@basearray。但是,要解决该额外的麻烦并不会增加安全性。为什么要这样呢?

是否有办法保证我不会在任何子例程中无意更改@basearray?我可以在我的代码中内置任何种类的硬安全设备,类似于use strict;,还是mylocal的某种组合?

我是否正确地认为答案是“否”,并且唯一的解决方案是不犯粗心的程序员错误?

#!/usr/bin/perl
use strict; use warnings;
my @basearray = qw / amoeba /;
my $count;

{
    print "\@basearray==\n"; 
    $count = 0; 
    foreach my $el (@basearray) { $count++; print "$count:\t$el\n" };
}

sub dosomething 
{
    my $sb_name = (caller(0))[3];
    print "entered $sb_name\n";
    my @sb_array=( @basearray,'dog' );
    {
        print "\@sb_array==\n"; 
        $count = 0; 
        foreach my $el (@sb_array) { $count++; print "$count:\t$el\n" };
    }
    print "return from $sb_name\n";
}

dosomething();
@basearray = ( @basearray,'rats' );

{
    print "\@basearray==\n"; 
    $count = 0; 
    foreach my $el (@basearray) { $count++; print "$count:\t$el\n" };
}


sub doagain
{
    my $sb_name = (caller(0))[3];
    print "entered $sb_name\n";
    my $sf_array=$_[0];
    my @sb_array=@$sf_array;
    @sb_array=( @sb_array,"piglets ... influenza" );
    {
        print "\@sb_array==\n"; 
        $count = 0; 
        foreach my $el (@sb_array) { $count++; print "$count:\t$el\n" };
    }
    print "now we demonstrate that passing an array as an argument to a subroutine does not protect it from being globally changed by programmer error\n";
    @basearray = ( @sb_array );
    print "return from $sb_name\n";
}
doagain( \@basearray );

{
    print "\@basearray==\n"; 
    $count = 0; 
    foreach my $el (@basearray) { $count++; print "$count:\t$el\n" };
}

解决方法

没有杂物或关键字之类的东西,但是有完善的“良好实践”,在这种情况下,它可以完全解决您合理考虑的问题。

  1. 第一个子dosomething犯下了使用在其作用域中可见但在较高作用域中定义的变量的缺点。相反,请始终将所需的数据传递给子例程(在非常清晰的情况下,例外情况很少见。)

    直接使用来自“外部”的数据违反了将函数视为封装过程的想法,它通过定义明确且清晰的界面与其用户交换数据。它使原则上完全不相关的代码部分纠缠(“耦合”)。实际上,它也可能是完全危险的。

    此外,@basearray即将在子程序中争夺的事实最好被认为是意外事件-当该子程序移至模块时该怎么办?还是引入另一个子来合并定义了@basearray的代码?

  2. 第二个子项doagain很好地引用了该数组。然后,为了保护调用方中的数据,可以将调用方的数组复制到子目录本地的另一个数组中。

    sub doagain {
        my ($ref_basearray) = @_;
        my @local_ba = @$ref_basearray;
        # work with local_ba and the caller's basearray is safe
    }
    

    局部词法变量的名称当然是任意的,但类似于调用方数据名称的约定可能会有用。

然后,为了安全起见,您可以采用一般惯例始终将输入变量复制到本地变量。直接使用仅在您要更改调用方数据时才传入的引用(在Perl中相对较少)。如果要对大量数据进行大量处理,或者涉及到非常大的数据结构,这可能会损害效率。因此,也许然后将其设置为异常并通过其引用更改数据,并格外小心。

,

(将我的评论作为答案) 确保不更改子例程中的变量的一种方法是不更改它。在子例程内仅使用词法范围的变量,并将子例程内所需的任何值作为参数传递给子例程。这是足够普遍的编码实践,即封装。

您可以使用的一个想法(主要是作为实践,我想说)是强迫自己使用封装,是在“主”代码周围放置一个块,并将子例程放置在其外部。这样,如果您不小心引用了(以前)全局变量,use strict将能够完成它的工作并产生致命错误。在运行前。

use strict;
use warnings;

main: {                      # lexical scope reduced to this block
    my @basearray = qw / amoeba /;
    print foo(@basearray);   # works
    print bar();             # fatal error
} # END OF MAIN                lexical scope of @basearray ends here

sub foo {
    my @basearray = @_;      # encapsulated 
    return $basearray[1]++;
}
sub bar {
    return $basearray[1]++;  # out of scope ERROR
}

这将不会编译,并且会产生错误:

Global symbol "@basearray" requires explicit package name at foo.pl line 15.
Execution of foo.pl aborted due to compilation errors.

我会认为这是一种训练工具,可以强迫自己使用良好的编码习惯,而不必在生产代码中使用。

,

从“只要不更改它”到“使用对象或绑定数组并锁定更新功能”,有几种解决方案具有不同的可靠性。中间解决方案与定义使用带有getter方法的对象不同,它是定义一个返回数组但只能作为右值操作的函数,并在子例程中使用该函数。

my @basearray = (...);
sub basearray { return @basearray }

sub foo {
    foreach my $elem (basearray()) {
       ...
    }
    @bar = map { $_ *= 2 } basearray();  # ok
    @bar = map { $_ *= 2 } @basearray;   # modifies @basearray!
    
}
,

TLDR:是的,但是。

我将从“ but”开始。但是最好设计代码,以使变量在定义不受信任的函数的范围内根本不存在。

sub untrusted_function {
  ...
}

my @basearray = qw( ... );  # declared after untrusted_function

如果untrusted_function需要能够访问数组的内容,请将其的数组的副本作为参数传递给它,这样它就不能修改原始数组。

现在这是“是”。

您可以在调用不受信任的函数之前将数组标记为只读。

Internals::SvREADONLY($_,1) for @basearray;
Internals::SvREADONLY(@basearray,1);

然后在功能完成后将其标记为可读写。

Internals::SvREADONLY(@basearray,0);
Internals::SvREADONLY($_,0) for @basearray;

使用Internals::SvREADONLY(@basearray,$bool)修改数组本身的只读状态,从而防止在数组中添加或删除元素; Internals::SvREADONLY($_,$bool) for @basearray也可能会修改数组中每个元素的只读状态。

当然,如果您的数组包含诸如祝福对象之类的引用,则需要考虑是否需要递归到这些引用中,并将它们也标记为只读。 (但也可能与我在首选解决方案中提到的数组的浅表副本有关!)

因此,可以通过在调用子变量之前将其标记为只读来防止该子变量意外修改变量,但重新组织代码,以便子根本根本无法访问变量。

是,但是。

,

这是使用@TLP答案的原型。

#!/usr/bin/perl
use strict; use warnings;

{   # block_main BEG
    my @basearray = qw / amoeba elephants sequoia /;
    print join ( ' ','in main,@basearray==',join ( ' ',@basearray ),"\n" );
    print "Now we call subroutine to print it:\n"; enumerateprintarray ( \@basearray );
    my $ref_basearray = changearray ( \@basearray,'wolves or coyotes . . . ' );
    @basearray = @$ref_basearray;
    print "Now we call subroutine to print it:\n"; enumerateprintarray ( \@basearray );
}   # block_main END

sub enumerateprintarray
{
    my $sb_name = (caller(0))[3];
    #print join ( '',@basearray ); # mortal sin! for in the day that thou eatest thereof thou shalt surely die.
    my $sb_exact_count_arg = 1;
    die "$sb_name must have exactly $sb_exact_count_arg arguments" unless ( ( scalar @_ ) == $sb_exact_count_arg );
    my $sf_array = $_[0];
    my @sb_array = @$sf_array;
    my $sb_count = 0;
    foreach (@sb_array)
    {
        $sb_count++; 
        print "\t$sb_count:\t$_\n";
    }
}

sub changearray
{
    my $sb_name = (caller(0))[3];
    #print join ( '',@basearray ); # in the day that thou eatest thereof thou shalt surely die.
    my $sb_exact_count_arg = 2;
    die "$sb_name must have exactly $sb_exact_count_arg arguments" unless ( ( scalar @_ ) == $sb_exact_count_arg );
    my ( $sf_array,$addstring ) = @_;
    my @sb_array = @$sf_array;
    push @sb_array,$addstring; 
    return ( \@sb_array );
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 <select id="xxx"> SELECT di.id, di.name, di.work_type, di.updated... <where> <if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 <property name="dynamic.classpath" value="tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams['font.sans-serif'] = ['SimHei'] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -> systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping("/hires") public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate<String
使用vite构建项目报错 C:\Users\ychen\work>npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-