21、Perl编程技巧:从引用替换到函数调用的多元方法

Perl编程技巧:从引用替换到函数调用的多元方法

1. 用真实引用替换软引用

在编程中,我们常常会遇到这样的疑问:“如何将一个变量用作变量名?” 当你开始思考这个问题时,意味着你在编程之路上又迈进了一步。不过,多数时候我们并不需要这么做,但在某些场景下,这可能是解决问题的最简单办法,比如重构他人编写的大型复杂代码时。

1.1 问题场景

假设你要维护一个销售报告应用程序,该程序处理多种商品的销售数据,每种商品都有各自的销售总额。程序使用了多个词法但文件全局的变量,且你没时间将其全部改成使用 %totals 哈希表。更糟糕的是,实际系统可能使用了符号引用,没有错误检查,销售总额还通过未加密的网络传输。

以下是示例代码:

use strict;
use warnings;
my ($books_total, $movies_total, $candy_total, $certificates_total, $total);
create_report( );
print_report( );
exit( );

sub print_report
{
    print <<END_REPORT;
SALES
    Books:             $books_total
    Movies:            $movies_total
    Candy;             $candy_total
    Gift Certificates: $certificates_total
TOTAL:                 $total
END_REPORT
}

sub create_report
{
    # your code here
}

sub get_row
{
    return unless defined( my $line = <DATA> );
    chomp( $line );
    return split( ':', $line );
}

__DATA__
books:10.00
movies:15.00
candy:7.50
certificates:8.00

1.2 解决方案

使用哈希表,将需要更新的变量引用填充到哈希表中。可以利用 Perl 的列表引用构造器简洁地实现这一点。列表引用构造器 \ 接受一个标量列表,返回这些标量的引用列表,非常适合用于哈希切片的值列表。

sub create_report
{
    my %totals;
    @totals{ qw( books movies candy certificates total )} =
    \( $books_total, $movies_total, $candy_total,
       $certificates_total, $total
     );
    while (my ($category, $value)  = get_row( ))
    {
        ${ $totals{ $category } } += $value;
        ${ $totals{total}       } += $value;
    }
}

1.3 优化建议

数据验证是个问题,可使用 exists 检查哈希表中是否存在有效键。不过,将 total 也存储在哈希表中可能会引发潜在的错误,此时可以考虑使用锁定哈希表。

2. 优化代码中的烦人操作

日常编程中,我们总会遇到一些重复且琐碎的操作,这些小烦恼虽不起眼,但却不断消耗我们的精力。我们可以将这些操作提取出来,封装成函数,让代码更加简洁易读。

2.1 添加换行符

如果你发现自己在大多数 print 语句末尾都要添加换行符 \n ,可以定义一个 say 函数来简化操作。

sub say { print @_, "\n" }
# 使用示例
say "Showing first ", @results / 2, " of ", scalar @results;
for (@results[ 0 .. @results / 2 - 1 ])
{
    if (m/($IDENT): \s* (.*?) \s* $/x)
    {
        say "$1 -> ", normalize($2);
    }
    else
    {
        say "Strange result: ", substr( $2, 0, 10 );
    }
}

2.2 读取文件内容

如果你经常需要打开文件并读取其内容,可以定义一个 slurp 函数来自动化这个过程。

sub slurp
{
    my ($file) = @_;
    open my $fh, '<', $file or croak "Couldn't open '$file'";
    local $/;
    return <$fh>;
}
# 使用示例
my $contents = slurp $filename;

2.3 优化总结

关键在于找出代码中重复、低级、机械的操作,将它们提取出来封装成更简洁、高级的缩写。这样可以使代码更易读,减少拼写错误和其他失误,让你更专注于解决实际问题。CPAN 上有许多优秀的模块可以实现这些微模式,例如 Perl6::Say File::Slurp 等。

3. 锁定哈希表

在 Perl 中,哈希表是非常有用的数据类型,但它有一个小缺陷: use strict 可以防止变量名的拼写错误,但无法防止哈希键的拼写错误。如果你使用的是 Perl 5.8.0 或更高版本,可以使用锁定哈希表来解决这个问题。

3.1 问题场景

假设你在处理多个哈希表,而同事经常拼写错误哈希键名,这会导致难以调试的问题。

3.2 解决方案

使用 Hash::Util 模块的 lock_keys 函数锁定哈希表的键。

use Test::More tests => 2;
use Hash::Util 'lock_keys';
my %locked   = ( foo => 1, bar => 2 );
my %unlocked = ( foo => 1, bar => 2 );
lock_keys( %locked );
eval {   $locked{fool} = 1 };
eval { $unlocked{fool} = 1 };
is( keys %locked,  2, 'hash with locked keys should disallow unknown key' );
is( keys %unlocked, 3, '... but unlocked hash should not' );

3.3 运行结果

尝试对锁定哈希表中不存在的键进行读写操作时,会抛出错误:

Attempt to access disallowed key 'fool' in a restricted hash...

运行测试套件,检查错误消息的行号,找出问题所在并修复。需要注意的是,调用 exists 不会触发异常。如果同事恶意调用 unlock_keys ,则需要完善测试套件。

4. 作用域结束时进行清理

成功的程序应该具有健壮性,即使出现错误,也能适应并干净利落地退出。在某些情况下,程序需要确保在作用域结束时进行一些清理操作,例如关闭子进程、刷新缓冲区、删除临时文件等。

4.1 问题场景

假设你要编写一个处理数据库记录的程序,处理过程不是幂等的,需要记录最后处理的记录 ID。管理员可能会中断程序,因此需要确保无论如何都能记录最后处理的记录 ID。

4.2 解决方案

使用 Scope::Guard 和闭包来安排作用域结束时的操作。

use Scope::Guard;

sub process_records
{
    my $records  = fetch_records( );
    my $last_rec = 0;
    my $cleanup  = sub { cleanup( $last_rec ) if $last_rec };
    my $guard    = Scope::Guard->new( $cleanup );
    for my $record ( @$records )
    {
        process_record( $record );
        $last_rec = $record;
    }
}

sub cleanup
{
    my $record = shift;
    # mark record as last record successfully completed
}

4.3 工作原理

process_records 函数声明了一个词法变量 $last_rec 来保存最后成功处理的记录。然后创建一个闭包 $cleanup ,在闭包中调用 cleanup 函数并传递 $last_rec 。接着创建一个 Scope::Guard 对象 $guard ,并将闭包传递给它。

在正常操作流程中,函数处理完所有记录后退出,Perl 垃圾回收 $guard 并调用闭包,从而调用 cleanup 函数记录最后成功处理的记录。即使处理过程中出现异常或程序被中断, $guard 仍然会超出作用域并调用 cleanup 函数。

4.4 扩展应用

Scope::Guard 不仅适用于清理操作,还可以在离开作用域时执行各种有趣的操作。例如,编写一个 chdir 替换函数,在切换目录后返回一个 Scope::Guard 对象,确保在作用域结束时返回原工作目录。

use Cwd;

sub change_directory
{
    my $newdir = shift;
    my $curdir = cwd( );
    chdir( $newdir );
    return Scope::Guard->new( sub { chdir $curdir } );
}

5. 以奇特方式调用函数

在 Perl 中,除了常见的函数调用方式,还有一些奇特的调用方式,这些方式通常不适合常规使用,但在特定场景下非常有用。

5.1 用裸词调用函数

如果有一个名为 foo 的函数,只要 Perl 在调用该函数之前已经看到其定义(或预定义),就可以不使用括号调用它。

sub foo
{
    # a bunch of code....
}
# 或者
sub foo;   # predefine 'foo'

# 调用方式
foo($x,$y,$z) 可以写成 foo $x,$y,$z

# 如果函数定义为无参数
sub foo ( )
{
    # a bunch of code...
}
# 或者
sub foo ( );

# 调用方式
foo( ) 可以写成 foo

例如,实现一个返回以天为单位的时间函数:

sub time_days ( )
{
    return time( ) / (24 * 60 * 60);
}
my $xd = time_days;

5.2 将标量变量绑定到函数

Perl 提供了 “绑定” 机制,允许在访问特定变量时调用函数。

{
    package TimeVar_YMDhms;
    use Tie::Scalar ( );
    use base 'Tie::StdScalar';
    use Date::Format 'time2str';
    sub FETCH { time2str('%Y-%m-%dT%H:%M:%S', time) }
}
tie my $TIME, TimeVar_YMDhms;
print "It is now $TIME\n";
sleep 3;
print "It is now $TIME\n";

也可以使用通用类实现相同的功能:

{
    package Tie::ScalarFnParams;
    sub TIESCALAR
    {
        my($class, $fn, @params) = @_;
        return bless sub { $fn->(@params) }, $class;
    }
    sub FETCH { return shift( )->( ) }
    sub STORE { return } # called for $var = somevalue;
}
use Date::Format 'time2str';
tie my $TIME, Tie::ScalarFnParams,
 # And now any function and optional parameter(s):
   sub { time2str(shift, time) }, '%Y-%m-%dT%H:%M:%S';
print "It is now $TIME\n";
sleep 3;
print "It is now $TIME\n";

5.3 将数组变量绑定到函数

可以将数组绑定到函数,使得 $somearray[123] 调用该函数并传递参数 123。

use Lingua::EN::Numbers::Ordinate 'ordinate';
{
    package Tie::Ordinalize;
    use Lingua::EN::Numbers::Ordinate 'ordinate';
    use base 'Tie::Array';
    sub TIEARRAY  { return bless { }, shift } # dummy obj
    sub FETCH     { return ordinate( $_[1] ) }
    sub FETCHSIZE { return 0 }
}
tie my @TH, Tie::Ordinalize;
print $TH[4], "!\n";

5.4 将哈希变量绑定到函数

绑定数组到函数时,索引必须是数字,而绑定哈希到函数时, FETCH 方法可以接受字符串参数。

{
    package Tie::TimeFormatty;
    use Tie::Hash ( );
    use base 'Tie::StdHash';
    use Date::Format 'time2str';
    sub FETCH { time2str($_[1], time) }
}
tie my %NowAs, Tie::TimeFormatty;
print "It is now $NowAs{'%Y-%m-%dT%H:%M:%S'}\n";
sleep 3;
print "It is now $NowAs{'%c'}\n";

也可以使用 Interpolation 模块实现相同的功能:

use Date::Format 'time2str';
use Interpolation NowAs => sub { time2str($_[0],time) };
print "It is now $NowAs{'%Y-%m-%dT%H:%M:%S'}\n";
sleep 3;
print "It is now $NowAs{'%c'}\n";

5.5 为文件句柄添加函数调用层

现代 Perl 版本提供了 “PerlIO 层” 机制,允许在程序和实际文件句柄之间的每个层调用特定函数来处理传递的数据。

package Function_IO_Layer;
# A dumb base class for simple PerlIO::via::* layers.
# See PerlIO::via::dynamic for a smarter version of this.
sub PUSHED { bless { }, $_[0] } # our dumb ctor
# when reading
sub FILL
{
    my($this, $fh) = @_;
    defined(my $line = readline($fh)) or return undef;
    return $this->change($line);
}
sub WRITE
{
    my($this,$buf,$fh) = @_;
    print {$fh} $this->change($buf)  or return -1;
    return length($buf);
}
sub change { my($this,$str) = @_;  $str; } #override!

# Puts everything in allcaps.
package PerlIO::via::Scream;
use base 'Function_IO_Layer';
sub change
{
    my($this, $str) = @_;
    return uc($str);
}

# Changes "I" to "me".
package PerlIO::via::Cookiemonster;
use base 'Function_IO_Layer';
sub change
{
    my($this, $str) = @_;
    $str =~ s<\bI\b><me>g;
    return $str;
}

# 使用示例
open my $fh, '>:via(Scream):via(Cookiemonster)',
   'author_bio.txt' or die $!;

5.6 总结

这些奇特的函数调用方式虽然不常用,但在特定场景下能发挥巨大作用。通过合理运用这些技巧,可以让代码更加灵活和强大。

总结

本文介绍了 Perl 编程中的多种技巧,包括用真实引用替换软引用、优化代码中的烦人操作、锁定哈希表、作用域结束时进行清理以及以奇特方式调用函数。这些技巧可以帮助你提高代码的健壮性、可读性和灵活性,让你在编程过程中更加得心应手。

技巧对比表格

技巧名称 作用 适用场景
用真实引用替换软引用 解决变量引用和数据处理问题 重构大型复杂代码
优化代码中的烦人操作 简化重复操作,提高代码可读性 日常编程中的琐碎操作
锁定哈希表 防止哈希键拼写错误 多人协作处理哈希表时
作用域结束时进行清理 确保作用域结束时进行清理操作 处理需要清理资源的程序
以奇特方式调用函数 隐藏函数调用,增加代码灵活性 特定场景下的函数调用

流程图

graph TD;
    A[开始] --> B[用真实引用替换软引用];
    B --> C[优化代码中的烦人操作];
    C --> D[锁定哈希表];
    D --> E[作用域结束时进行清理];
    E --> F[以奇特方式调用函数];
    F --> G[结束];

通过掌握这些技巧,你可以在 Perl 编程中更加游刃有余,写出高质量的代码。

6. 技巧应用案例分析

为了更好地理解上述 Perl 编程技巧的实际应用,下面通过几个具体案例进行分析。

6.1 销售报告应用优化案例

在之前提到的销售报告应用中,使用真实引用替换软引用和优化代码中的烦人操作技巧可以显著提升代码质量。

原问题回顾

原代码使用多个词法但文件全局的变量,且未使用哈希表,代码可维护性差。同时, print 语句中频繁添加换行符,读取文件内容的操作也较繁琐。

优化方案
  • 使用真实引用替换软引用 :通过构建哈希表存储变量引用,实现数据的动态更新。
  • 优化烦人操作 :定义 say 函数简化 print 语句,定义 slurp 函数自动化文件读取操作。
优化后代码示例
use strict;
use warnings;
use Cwd;

my ($books_total, $movies_total, $candy_total, $certificates_total, $total);

# 优化后的 create_report 函数
sub create_report
{
    my %totals;
    @totals{ qw( books movies candy certificates total )} =
    \( $books_total, $movies_total, $candy_total,
       $certificates_total, $total
     );
    while (my ($category, $value)  = get_row( ))
    {
        ${ $totals{ $category } } += $value;
        ${ $totals{total}       } += $value;
    }
}

# 优化后的 print_report 函数,使用 say 函数
sub print_report
{
    say "SALES";
    say "    Books:             $books_total";
    say "    Movies:            $movies_total";
    say "    Candy;             $candy_total";
    say "    Gift Certificates: $certificates_total";
    say "TOTAL:                 $total";
}

# 定义 say 函数
sub say { print @_, "\n" }

# 定义 slurp 函数
sub slurp
{
    my ($file) = @_;
    open my $fh, '<', $file or croak "Couldn't open '$file'";
    local $/;
    return <$fh>;
}

# get_row 函数保持不变
sub get_row
{
    return unless defined( my $line = <DATA> );
    chomp( $line );
    return split( ':', $line );
}

# 主程序
create_report( );
print_report( );
exit( );

__DATA__
books:10.00
movies:15.00
candy:7.50
certificates:8.00

6.2 目录切换与清理案例

在处理需要切换工作目录的程序时,使用 Scope::Guard 可以确保在作用域结束时返回原工作目录。

问题场景

在执行外部进程时,需要切换到特定的工作目录,但执行完毕后需要返回原工作目录。

解决方案

编写 change_directory 函数,使用 Scope::Guard 实现目录切换和恢复。

代码示例
use Cwd;

sub change_directory
{
    my $newdir = shift;
    my $curdir = cwd( );
    chdir( $newdir );
    return Scope::Guard->new( sub { chdir $curdir } );
}

# 使用示例
{
    my $guard = change_directory("/path/to/new/directory");
    # 执行需要在新目录下的操作
    # ...
    # 作用域结束时,自动返回原目录
}

6.3 函数调用技巧案例

在某些特定场景下,使用奇特的函数调用方式可以让代码更加简洁和灵活。

裸词调用函数案例
sub greet
{
    my $name = shift;
    say "Hello, $name!";
}

# 预定义函数
sub greet;

# 使用裸词调用函数
greet "John";
绑定变量到函数案例
{
    package Tie::RandomNumber;
    use Tie::Scalar ( );
    use base 'Tie::StdScalar';
    sub FETCH { int(rand(100)) }
}

tie my $random, Tie::RandomNumber;
say "Random number: $random";

7. 技巧使用注意事项

虽然这些 Perl 编程技巧非常有用,但在使用时也需要注意一些事项。

7.1 真实引用替换软引用

  • 数据验证 :在使用哈希表存储变量引用时,要注意对外部数据进行验证,防止因数据错误导致程序崩溃。
  • 代码重构 :该技巧只是临时解决方案,在有时间的情况下,应尽量对代码进行全面重构,使用更合理的数据结构。

7.2 优化代码中的烦人操作

  • 函数命名 :定义的简化函数(如 say slurp )要使用有意义的名称,避免与其他函数冲突。
  • 模块依赖 :使用 CPAN 模块时,要确保项目环境中已正确安装和配置这些模块。

7.3 锁定哈希表

  • 异常处理 :在使用锁定哈希表时,要处理好可能出现的异常,确保程序的健壮性。
  • 团队协作 :在多人协作项目中,要确保团队成员了解锁定哈希表的使用规则,避免误操作。

7.4 作用域结束时进行清理

  • 资源管理 :确保清理操作能够正确释放所有占用的资源,避免资源泄漏。
  • 异常情况 :考虑在异常情况下, Scope::Guard 是否能正常工作,必要时添加额外的异常处理代码。

7.5 以奇特方式调用函数

  • 可读性 :奇特的函数调用方式可能会降低代码的可读性,使用时要确保团队成员能够理解代码的意图。
  • 兼容性 :某些奇特的调用方式可能在不同版本的 Perl 中存在兼容性问题,使用前要进行充分测试。

8. 技巧拓展与未来趋势

随着 Perl 语言的不断发展,这些编程技巧也可以进行拓展和创新。

8.1 技巧拓展

  • 结合其他模块 :可以将上述技巧与其他 Perl 模块结合使用,实现更复杂的功能。例如,将锁定哈希表与数据库操作模块结合,确保数据库查询中的哈希键正确无误。
  • 自定义扩展 :根据实际需求,自定义一些扩展函数和类,进一步优化代码结构。

8.2 未来趋势

  • 自动化工具 :未来可能会出现更多的自动化工具,帮助开发者更方便地应用这些技巧,减少手动编写代码的工作量。
  • 语言特性增强 :Perl 语言可能会不断增强其语言特性,使得这些技巧的实现更加简洁和高效。

总结与展望

8.3 总结

本文详细介绍了 Perl 编程中的多种实用技巧,包括用真实引用替换软引用、优化代码中的烦人操作、锁定哈希表、作用域结束时进行清理以及以奇特方式调用函数。通过具体案例分析,展示了这些技巧在实际应用中的效果,并阐述了使用时的注意事项。

8.4 展望

掌握这些技巧可以让开发者在 Perl 编程中更加得心应手,提高代码的质量和效率。未来,随着 Perl 语言的发展和技术的进步,这些技巧将不断拓展和创新,为开发者带来更多的便利和可能性。希望开发者能够积极探索和应用这些技巧,在 Perl 编程领域取得更好的成果。

技巧拓展表格

技巧名称 拓展方向 示例
用真实引用替换软引用 结合数据库操作 使用哈希表存储数据库查询结果的引用
优化代码中的烦人操作 自定义扩展函数 定义更多简化操作的函数
锁定哈希表 结合数据库模块 确保数据库查询中的哈希键正确
作用域结束时进行清理 结合多线程编程 在多线程环境中确保资源清理
以奇特方式调用函数 结合新的语言特性 利用 Perl 新特性实现更灵活的函数调用

流程图

graph TD;
    A[技巧应用] --> B[注意事项];
    B --> C[技巧拓展];
    C --> D[未来趋势];
    D --> E[总结与展望];

通过不断学习和实践这些 Perl 编程技巧,开发者可以提升自己的编程能力,应对各种复杂的编程任务。

内容概要:本文设计了一种基于PLC的全自动洗衣机控制系统内容概要:本文设计了一种,采用三菱FX基于PLC的全自动洗衣机控制系统,采用3U-32MT型PLC作为三菱FX3U核心控制器,替代传统继-32MT电器控制方式,提升了型PLC作为系统的稳定性与自动化核心控制器,替代水平。系统具备传统继电器控制方式高/低水,实现洗衣机工作位选择、柔和过程的自动化控制/标准洗衣模式切换。系统具备高、暂停加衣、低水位选择、手动脱水及和柔和、标准两种蜂鸣提示等功能洗衣模式,支持,通过GX Works2软件编写梯形图程序,实现进洗衣过程中暂停添加水、洗涤、排水衣物,并增加了手动脱水功能和、脱水等工序蜂鸣器提示的自动循环控制功能,提升了使用的,并引入MCGS组便捷性与灵活性态软件实现人机交互界面监控。控制系统通过GX。硬件设计包括 Works2软件进行主电路、PLC接梯形图编程线与关键元,完成了启动、进水器件选型,软件、正反转洗涤部分完成I/O分配、排水、脱、逻辑流程规划水等工序的逻辑及各功能模块梯设计,并实现了大形图编程。循环与小循环的嵌; 适合人群:自动化套控制流程。此外、电气工程及相关,还利用MCGS组态软件构建专业本科学生,具备PL了人机交互C基础知识和梯界面,实现对洗衣机形图编程能力的运行状态的监控与操作。整体设计涵盖了初级工程技术人员。硬件选型、; 使用场景及目标:I/O分配、电路接线、程序逻辑设计及组①掌握PLC在态监控等多个方面家电自动化控制中的应用方法;②学习,体现了PLC在工业自动化控制中的高效全自动洗衣机控制系统的性与可靠性。;软硬件设计流程 适合人群:电气;③实践工程、自动化及相关MCGS组态软件与PLC的专业的本科生、初级通信与联调工程技术人员以及从事;④完成PLC控制系统开发毕业设计或工业的学习者;具备控制类项目开发参考一定PLC基础知识。; 阅读和梯形图建议:建议结合三菱编程能力的人员GX Works2仿真更为适宜。; 使用场景及目标:①应用于环境与MCGS组态平台进行程序高校毕业设计或调试与运行验证课程项目,帮助学生掌握PLC控制系统的设计,重点关注I/O分配逻辑、梯形图与实现方法;②为工业自动化领域互锁机制及循环控制结构的设计中类似家电控制系统的开发提供参考方案;③思路,深入理解PL通过实际案例理解C在实际工程项目PLC在电机中的应用全过程。控制、时间循环、互锁保护、手动干预等方面的应用逻辑。; 阅读建议:建议结合三菱GX Works2编程软件和MCGS组态软件同步实践,重点理解梯形图程序中各环节的时序逻辑与互锁机制,关注I/O分配与硬件接线的对应关系,并尝试在仿真环境中调试程序以加深对全自动洗衣机控制流程的理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值