【问题标题】:How to tell apart numeric scalars and string scalars in Perl?如何区分 Perl 中的数字标量和字符串标量?
【发布时间】:2021-10-07 07:04:45
【问题描述】:

Perl 通常将数字转换为字符串值,反之亦然。然而,必须有一些东西可以让例如Data::Dumper 区分两者,如下例所示:

use Data::Dumper;
print Dumper('1', 1);

# output:
$VAR1 = '1';
$VAR2 = 1;

是否有一个 Perl 函数可以让我以类似的方式区分标量的值是存储为数字还是字符串?

【问题讨论】:

  • 在什么情况下需要知道区别?
  • 我想构建 SQL 条件,并区分需要引用的值和不需要引用的值。想想FOO = 00023FOO = '00023'
  • 为什么不使用占位符,让您的 DBI 模块担心引用?
  • 是的,我想要,但是 DBI 被这个代码库中的抽象层隐藏了。
  • 嗯,这很愚蠢。有多种方法可以确定或强制变量成为您想要的样子,但是,由于信息太少,很难给您建议。

标签: string perl types integer


【解决方案1】:

标量有许多不同的字段。当使用 Perl 5.8 或更高版本时,Data::Dumper 检查 IV(整数值)字段中是否有任何内容。具体来说,它使用类似于以下内容的内容:

use B qw( svref_2object SVf_IOK );

sub create_data_dumper_literal {
    my ($x) = @_;  # This copying is important as it "resolves" magic.
    return "undef" if !defined($x);

    my $sv = svref_2object(\$x);
    my $iok = $sv->FLAGS & SVf_IOK;
    return "$x" if $iok;

    $x =~ s/(['\\])/\\$1/g;
    return "'$x'";
}

检查:

  • 有符号整数 (IV):($sv->FLAGS & SVf_IOK) && !($sv->FLAGS & SVf_IVisUV)
  • 无符号整数 (IV):($sv->FLAGS & SVf_IOK) && ($sv->FLAGS & SVf_IVisUV)
  • 浮点数(NV):$sv->FLAGS & SVf_NOK
  • 降级字符串(PV):($sv->FLAGS & SVf_POK) && !($sv->FLAGS & SVf_UTF8)
  • 升级字符串(PV):($sv->FLAGS & SVf_POK) && ($sv->FLAGS & SVf_UTF8)

您可以使用类似的技巧。但请记住,

  • 在不丢失的情况下对浮点数进行字符串化是非常困难的。

  • 您需要正确转义字符串文字中的某些字节(例如 NUL)。

  • 一个标量可以存储多个值。例如,!!0 包含一个字符串(空字符串)、一个浮点数 (0) 和一个有符号整数 (0)。如您所见,不同的值甚至并不总是相等的。如需更生动的示例,请查看以下内容:

      $ perl -E'open($fh, "non-existent"); say for 0+$!, "".$!;'
      2
      No such file or directory
    

【讨论】:

    【解决方案2】:

    它更复杂。 Perl 根据使用变量的上下文改变变量的内部表示:

    perl -MDevel::Peek -e '
        $x = 1;    print Dump $x;
        $x eq "a"; print Dump $x;
        $x .= q(); print Dump $x;
    '
    SV = IV(0x794c68) at 0x794c78
      REFCNT = 1
      FLAGS = (IOK,pIOK)
      IV = 1
    SV = PVIV(0x7800b8) at 0x794c78
      REFCNT = 1
      FLAGS = (IOK,POK,pIOK,pPOK)
      IV = 1
      PV = 0x785320 "1"\0
      CUR = 1
      LEN = 16
    SV = PVIV(0x7800b8) at 0x794c78
      REFCNT = 1
      FLAGS = (POK,pPOK)
      IV = 1
      PV = 0x785320 "1"\0
      CUR = 1
      LEN = 16
    

    【讨论】:

      【解决方案3】:

      没有办法使用纯 perl 来解决这个问题。 Data::Dumper 使用一个 C 库来实现它。如果强制使用 Perl,如果字符串看起来像十进制数字,它不会区分字符串和数字。

      use Data::Dumper;
      $Data::Dumper::Useperl = 1;
      print Dumper(['1',1])."\n";
      
      #output
      $VAR1 = [
                1,
                1
              ];
      

      【讨论】:

      • 我接受了这个,因为它清楚地表明我无法解决我的问题。其他答案对他们的洞察力仍然非常有帮助。
      • 其实不用写C代码也不需要安装任何模块就可以实现。看我的回答。
      • 哇,不知道,谢谢!我很好奇为什么它没有在 Data::Dumper 中使用,他们为此使用了普通的正则表达式。
      【解决方案4】:

      根据您的评论,这是为了确定 SQL 语句是否需要引用,我认为正确的解决方案是使用 DBI 文档中描述的占位符。

      通常,您不应在查询字符串中直接插入变量。

      【讨论】:

        【解决方案5】:

        未提及的一个简单解决方案是 Scalar::Util 的 looks_like_number。 Scalar::Util 是自 5.7.3 以来的核心模块,looks_like_number 使用perlapi 来确定标量是否为数字。

        【讨论】:

          【解决方案6】:

          autobox 附带的autobox::universal 模块提供了可用于此目的的type 函数:

          use autobox::universal qw(type);
          
          say type("42");  # STRING
          say type(42);    # INTEGER
          say type(42.0);  # FLOAT 
          say type(undef); # UNDEF 
          

          【讨论】:

            【解决方案7】:

            当变量用作数字时,这会导致变量在后续上下文中被假定为数字。然而,反过来并不完全正确,如下例所示:

            use Data::Dumper;
            
            my $foo = '1';
            print Dumper $foo;  #character
            my $bar = $foo + 0;
            print Dumper $foo;  #numeric
            $bar = $foo . ' ';
            print Dumper $foo;  #still numeric!
            $foo = $foo . '';
            print Dumper $foo;  #character
            

            人们可能期望第三个操作将$foo 放回字符串上下文中(反转$foo + 0),但事实并非如此。

            如果您想检查某事物是否为数字,标准方法是使用正则表达式。您检查的内容因您想要的数字而异:

            if ($foo =~ /^\d+$/)      { print "positive integer" }
            if ($foo =~ /^-?\d+$/)    { print "integer"          }
            if ($foo =~ /^\d+\.\d+$/) { print "Decimal"          }
            

            等等。

            检查某些内容的内部存储方式通常没有用——您通常无需担心这一点。但是,如果你想复制 Dumper 在这里所做的事情,那没问题:

            if ((Dumper $foo) =~ /'/) {print "character";}
            

            如果 Dumper 的输出包含单引号,则表示它显示的是一个以字符串形式表示的变量。

            【讨论】:

              【解决方案8】:

              你可能想试试Params::Util::_NUMBER

              use Params::Util qw<_NUMBER>;
              
              unless ( _NUMBER( $scalar ) or $scalar =~ /^'.*'$/ ) { 
                 $scalar =~ s/'/''/g;
                 $scalar = "'$scalar'";
              }
              

              【讨论】:

                【解决方案9】:

                我认为没有 perl 函数来查找值的类型。可以找到 DS(scalar,array,hash) 的类型。可以使用正则表达式来查找值的类型。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2010-09-24
                  • 2023-03-10
                  • 2016-05-15
                  • 2010-10-14
                  • 2020-11-13
                  • 2020-04-27
                  相关资源
                  最近更新 更多