如何区分 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 = 00023
与FOO = '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 中的数字标量和字符串标量?的主要内容,如果未能解决你的问题,请参考以下文章