5

我是perl的新手。我试图通过编写一些程序来理解它。perl 中的作用域让我很难过。

我写了以下内容:

use 5.16.3;
use strict;
use Getopt::Long;

Getopt::Long::Configure(qw(bundling no_getopt_compat));
&ArgParser;
our ($sqluser,$sqlpass);

$sqluser="root";
$sqlpass="mypassword";

sub ArgParser {
    print "Username is ".$sqluser." Password is ".$sqlpass."\n";
    my $crt='';
    my $delete='';
    GetOptions ('create|c=s' => \$crt,
        'delete|d=s' => \$delete
    );
    if ($crt) {
        &DatabaseExec("create",$crt);   
    } elsif ($delete) {
        &DatabaseExec("delete",$delete);    
    } else {
    print "No options chosen\n";
    }
}

sub DatabaseExec {
    use DBI;
    my $dbname=$_[1];
    print "Username is ".$sqluser." Password is ".$sqlpass."\n";
    my $dbh = DBI->connect("dbi:mysql:", $sqluser,$sqlpass);
    my $comand=$_[0];
    if ($_[0] eq "create") {
        my $db_com="create database ".$dbname;
        print 1 == $dbh->do($db_com) ? "Database created\n":"An error occured while creating database. Maybe it exists?\n";
        #print "Executing: ".$db_com."\n";
    } elsif ($_[0] eq "delete") {
        my $db_com="DROP DATABASE ".$dbname;
        #print "Executing: ".$db_com."\n";
        print 1 == $dbh->do($db_com) ? "Database deleted\n":"An error occured while creating database. Maybe it exists?\n";
    }
}

我的理解是,我们会将这些声明为全局变量,以供主代码和子程序使用。然而,这给出了以下输出:

#~/perlscripts/dbtest.pl -c hellos
Use of uninitialized value $sqluser in concatenation (.) or string at /root/perlscripts/dbtest.pl line 20.
Use of uninitialized value $sqlpass in concatenation (.) or string at /root/perlscripts/dbtest.pl line 20.
Username is  Password is
Use of uninitialized value $sqluser in concatenation (.) or string at /root/perlscripts/dbtest.pl line 44.
Use of uninitialized value $sqlpass in concatenation (.) or string at /root/perlscripts/dbtest.pl line 44.
Username is  Password is
DBI connect('','',...) failed: Access denied for user 'root'@'localhost' (using password: NO) at /root/perlscripts/dbtest.pl line 45.
Can't call method "do" on an undefined value at /root/perlscripts/dbtest.pl line 50.

我不想将这些作为参数传递给 sub,而是将它们用作全局变量。有人可以帮我确定我对范围界定的误解吗?

4

3 回答 3

11

调用子例程时未声明变量:

&ArgParser;                 # subroutine call
our ($sqluser,$sqlpass);    # declaration

$sqluser="root";            # assignment
$sqlpass="mypassword";

为了在子程序中使用这些全局变量,将子程序放在变量声明之后。

但是,使用全局变量是一件坏事,您应该尽可能避免使用它。您可以改为这样做,例如:

my $sqluser = "root";
my $sqlpass = "mypass";

ArgParser($sqluser, $sqlpass);    # you should not use & in subroutine calls

然后在子程序内部:

sub ArgParser {
    my ($sqluser, $sqlpass) = @_;
    ...

通过这种方式,您的变量被很好地封装并且不会被意外操作。

关于&子例程调用中的 & 符号,这记录在perldoc perlsub中:

To call subroutines:

NAME(LIST);   # & is optional with parentheses.
NAME LIST;    # Parentheses optional if predeclared/imported.
&NAME(LIST);  # Circumvent prototypes.
&NAME;        # Makes current @_ visible to called subroutine.
于 2013-04-16T11:11:50.177 回答
7

Perl 没有全局变量。Perl 有:

  • 包变量。
  • 词法范围的变量。

包是一个命名空间。在 Perl 中,命名空间有时称为。您的默认包名称是main. 例如。这是完全合法的:

use strict;
use warnings;

$main::variable = "What? Where's my 'our' or 'my' declaration?";

print "Look, I can print $main::variable without using 'my' or 'our'!";

我只是在我的包变量名称前面加上一个包,然后就搞定了!他们存在!

这让我惊愕:

use strict;
use warnings;

$variable = "What? Where's my 'our' or 'my' declaration?";

print "I'm not going to print 'cause you're going to get a compilation error";

使用use strict;,您必须将变量声明为ourmy,或者在其前面加上它所在的的名称。

包变量最容易理解。包变量实际上存储在 Perl 变量结构中,因此一旦声明它们就始终可用:

use strict;
use warnings;

if ( 1 == 1 ) {  #Yes, I know this is always true
    our $foo = "I have a value!";
}

say "Looks like $foo has a value";

词法范围的变量更难理解。基本上,一个词法范围的变量在它定义的的范围内,但是一旦你离开那个块就超出了范围。它也可用于子块:

use strict;
use warnings;

my $foo = "Foo has a value";

if ( $foo ) {   #Always true
    my $bar = "bar has a value";
    print "$foo\n";    # $foo has a value. This is a sub-block
    print "$bar\n";    # $bar has a value. It was defined in this block
}

print "$foo\n";    # $foo still has a value.
print "$bar\n";    # You'll get en error here. $bar out of scope here

这里有一些建议:

  • 您不需要预先声明子例程。那只是自找麻烦。
  • 如果您在程序的开头定义变量,则可以使用my变量,并且它们将在您的子例程中可用,因为它们仍在范围内。
  • 离开&子程序调用。它们会导致子例程的工作方式发生细微的变化,而这些细微的变化可能不是您想要的。标准只是调用子程序。
    • 避免使用? ... : ...,尤其是在您不使用空格的情况下。它使您的程序更难阅读,并且不会节省任何执行时间。
    • 调用子程序后立即将子程序参数拉入变量。
  • Perl 为您插入变量。Perl 有很多问题。它没有真正的面向对象。它不是面向异常的语言。它有很多杂物。最大的优势之一是您不必通过各种诡计来打印变量值。当你在 Python 粉丝周围闲逛时,使用它并自豪地抬起头来。
  • 使用空格使您的代码更易于阅读。
  • 也许你真正想要的是常量my变量会起作用,但常量会确保这些值不会在程序中被意外更改。

这是您重写的代码。请注意,常量前面没有印记。这些通常不能插入到字符串中。但是,如果你用 包围它们@{[...]},你也可以对它们进行插值。我在下面两种方式都做了:

use 5.16.3;
use strict;
use Getopt::Long;

use constant {
    SQL_USER => "root",
    SQL_PASS => "mypassword",
};

Getopt::Long::Configure qw(bundling no_getopt_compat);

sub ArgParser {
    print "Username is " SQL_USER . " Password is " . SQL_PASS . "\n";
    my $crt;
    my $delete;
    GetOptions (
        'create|c=s' => \$crt,
        'delete|d=s' => \$delete,
    );
    if ( $crt ) {
        DatabaseExec( "create", $crt );   
    }
    elsif ( $delete ) {
        DatabaseExec( "delete", $delete );    
    }
    else {
        print "No options chosen\n";
    }
}

sub DatabaseExec {
    use DBI;

    my $comand = shift;
    my $dbname = shift;

    print "Username is @{[SQL_USER]} Password is @{[SQL_PASS]}\n";

    my $dbh = DBI->connect(
        "dbi:mysql:",
        SQL_USER,
        SQL_PASS
    );

    if ( $command eq "create" ) {
        my $db_com = "create database $dbname";
        if ( $dbh->do( $db_com ) ) {
            print "Database created\n"
        }
        else {
            print "An error occured while creating database. Maybe it exists?\n";
        }
    } elsif ( $command eq "delete" ) {
        my $db_com = "DROP DATABASE $dbname";
        #print "Executing: ".$db_com."\n";
        if ( $dbh->do($db_com) ) {
            print "Database deleted\n";
        }
        else {
            print "An error occured while creating database. Maybe it exists?\n";
        }
    }
}
于 2013-04-16T14:43:33.257 回答
0

我推荐阅读的关于变量作用域的经典资源是Mark-Jason Dominus 的Coping with scoping:它描述了 Perl 变量族(包和词法变量)的基本划分,并警告初学者可能会遇到的一些不良做法。

于 2013-04-16T11:19:14.713 回答