如何在Perl中区分数字标量和字符串标量?

20

Perl通常会自动透明地将数字转换为字符串值,反之亦然。但是肯定有某种方法允许例如Data::Dumper区分二者,就像这个示例中的情况:

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

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

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


3
需要了解区别的情况是什么? - TLP
我想构建SQL条件,并区分需要引用的值和不需要引用的值。考虑 FOO = 00023FOO ='00023' 的区别。 - Stefan Majewsky
3
为什么不使用占位符,让你的DBI模块担心引号问题呢? - TLP
是的,我想要那个,但是在这个代码库中,DBI被一个抽象层隐藏了。 - Stefan Majewsky
1
那很傻。有办法确定或强制变量成为您想要的类型,但是由于信息太少,很难给您建议。 - TLP
10个回答

19

标量有许多不同的字段。当使用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

16

这更加复杂。根据变量所用的上下文,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

11

使用纯Perl无法找到这个信息。Data::Dumper使用C库来实现。如果被强制使用Perl,则无法区分类似十进制数字的字符串和数字。

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

#output
$VAR1 = [
          1,
          1
        ];

1
我选择接受这个答案,因为它明确表示我无法解决我的问题。其他答案的见解仍然非常有帮助。 - Stefan Majewsky
2
实际上,可以在不编写C代码或安装任何模块的情况下实现。请查看我的答案。 - ikegami
哇,没想到,谢谢!那我很好奇为什么 Data::Dumper 没有使用它,而是使用普通的正则表达式来实现这个目的。 - Bohdan

6

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

通常情况下,您不应直接将变量插入到查询字符串中。


4

autobox::universal 模块是 autobox 的一部分,它提供了一个 type 函数,可用于此目的:

use autobox::universal qw(type);

say type("42");  # STRING
say type(42);    # INTEGER
say type(42.0);  # FLOAT 
say type(undef); # UNDEF 

4

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


3

当变量被用作数字时,会导致该变量在后续上下文中被视为数值。然而,反过来并非完全如此,就像这个例子所展示的:

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的输出包含单引号,这意味着它显示的是以字符串形式表示的变量。

3
你可能想尝试使用Params::Util::_NUMBER
use Params::Util qw<_NUMBER>;

unless ( _NUMBER( $scalar ) or $scalar =~ /^'.*'$/ ) { 
   $scalar =~ s/'/''/g;
   $scalar = "'$scalar'";
}

0
以下函数返回 true(1),如果输入为数字则返回 false("")如果是字符串。如果输入是数字 Inf 或 NaN,则函数还会返回 true(-1)。类似的代码可以在 JSON::PP 模块中找到。
sub is_numeric {
    my $value = shift;
    no warnings 'numeric';
    # string & "" -> ""
    # number & "" -> 0 (with warning)
    # nan and inf can detect as numbers, so check with * 0
    return unless length((my $dummy = "") & $value);
    return unless 0 + $value eq $value;
    return 1 if $value * 0 == 0;  # finite number
    return -1;                    # inf or nan
}

-1

我认为没有perl函数可以找到值的类型。我们可以找到DS(标量,数组,哈希)的类型。可以使用正则表达式查找值的类型。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接