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

Posted

技术标签:

【中文标题】如何区分 Perl 中的数字标量和字符串标量?【英文标题】:How to tell apart numeric scalars and string scalars in 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 被这个代码库中的抽象层隐藏了。 嗯,这很愚蠢。有多种方法可以确定或强制变量成为您想要的样子,但是,由于信息太少,很难给您建议。 【参考方案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) 的类型。可以使用正则表达式来查找值的类型。

【讨论】:

以上是关于如何区分 Perl 中的数字标量和字符串标量?的主要内容,如果未能解决你的问题,请参考以下文章

Perl中的标量介绍

Perl 变量:标量变量

Perl:标量,数组,哈希

perl 第五弹 变量 I

Perl语言学习,不要停哟~~

perl学习基本语法