Perl中 " 和 ' 的区别

4
根据这个评论:

你也不应该在print ">>${ '_<$filename' }<<\n"中使用单引号。 而是尝试使用:print ">>${ \"_<$filename\" }<<\n"

我一直认为"'之间的区别只是字符串是否被解释。我想问为什么在这种情况下:
print ">>${ \"_<$filename\" }<<\n"
print ">>${  '_<$filename'  }<<\n"

Perl 打印出不同的值?

我为什么应该在这里使用 \" 而不是只用 '


1
我的猜测是因为 "${ \"_<$filename\" }" 被解析了两次。如果你在其中使用单引号,它将不会插值 $filename(因为单引号不会插值)? - Håkon Hægland
4个回答

6
你的问题的简短回答是,在第一个示例中,$filename被插值化了,而在第二个示例中没有。为什么呢?好吧,准备好颠簸的旅程。

首先,理解 ${...} 表示法的用途。有两个原因可以使用它:一个是简单地将变量名与周围的文本区分开来;另一个是获取由代码块返回的值。

如果括号内的内容只是一个裸字(可能带有周围的空白),那么它只是一个简单的变量名。因此,字符串"$foo"、字符串"${foo}"和字符串"${ foo }"都会产生相同的结果。如果您需要在字符串中直接在变量后附加文本,这很方便:您可以说 "${foo}bar" 而不是 $foo.'bar'。这些表示法也适用于插值字符串之外:如果您喜欢,可以说 ${foo} = 'bar'

但这不是你的代码正在做的事情。哦,不。你正在将符号解除引用的双管猎枪直接对准自己的脚。您正在将括号内的内容评估为代码,并使用结果作为变量的引用。通过将整个内容放在插值字符串中使其变得更加混乱。由于这不是主要问题,因此我们不必深入研究解除引用的概念。关键点是花括号中的东西如果不是裸字,它就会被有效地评估。

现在,理解当Perl在您的插值字符串中达到 "${" 表示法时,它必须决定在这里插值化什么:普通变量名还是代码。在不插值化任何其他内容的情况下,它寻找相匹配的右括号。如果由这些括号分隔的子字符串看起来不像裸字,则它将被评估。在此区域内仍然需要转义双引号,因为任何未经转义的双引号都将计入外部字符串的最终定界符,在这个字符串之外的块的余下部分。

有了这个想法,我们可以看到你的第一个示例评估了 "_<$filename",而第二个示例则评估了 '_<$filename'。这只是插值字符串和非插值字符串之间的差异:第一个字符串替换了 $filename 的内容,而第二个字符串则是严格的字面值。

在这两种情况下,该字符串用于对具有可怕名称的全局标量变量执行符号解引用操作,如果您实际上定义了它,则应该感到羞愧。越少关于它的内容越好。

如果你认为我在开玩笑,可以试试这段代码。

print "This, ${die qq(oops!\n)}, and this.\n";

当然,字符串内的代码与eval()之间的关键区别在于eval()会捕获异常并设置$@,而这种方式不会:它只是像普通代码块一样终止。
这引出了Perl的一个隐藏技巧。您不能插入像$foo->bar这样的方法调用,但是您可以解引用包含它的数组的引用,就像这样。
print "@{[$foo->bar]}\n";

总之,为了上帝的爱,请不要写那样的代码。


3
答案很简单:当在字符串文字中的代码中找到$@字符时,它们都没有特殊意义。 ">>${ \"_<$filename\" }<<\n"等同于">>" . ${ "_<$filename" } . "<<\n"">>${ '_<$filename'" }<<\n"等同于">>" . ${ '_<$filename' } . "<<\n"
正如您所看到的,$filename并未像双引号字符串通常发生的那样被替换为$filename的值。
如果这不清楚,让我们考虑一个更常见的例子。 ">>$h{$key}<<"等同于">>" . $h{$key} . "<<"">>$h{\"$key\"}<<"等同于">>" . $h{"$key"} . "<<"">>$h{'$key'}<<"等同于">>" . $h{'$key'} . "<<"
现在考虑如果插值了$key会发生什么。(例如,使用$key = "2+3";。)行为将更加意外和更加危险。
同样,除非转义分隔符,否则在字符串文字中的代码中找到的\字符没有特殊含义。 "foo ${\f()} bar"等同于"foo " . ${\f()} . " bar"
正如您所看到的,\f并未像双引号字符串通常发生的那样被替换为一个进纸符。

2
在这两种情况下,$filename 都不是由外部字符串 (">>...<<\n") 插值的,而是由 ${ ... } 插值的。(这只是从行为上推断出来的)。这意味着在 ">>${ '_<$filename' }<<\n" 中,$filename 根本没有被插值,而在 ">>${ \"_<$filename\" }<<\n" 中则被插值了。
如果它是由外部字符串插值的,那么如果你的字符串包含引号,你就会遇到一些麻烦:
$filename = "ab'cd";

如果外部字符串进行插值,那么"${'$filename'}"将等同于"${ab'cd'}",这是一种语法错误。或者可能更接近"${ab'cd}",它等效于"${ab::cd}",也不是你想要的结果。
而如果插值由${}执行,则在"${'$filename'}"中,${...}实际上是${"$filename"}(没有转义的双引号),它会插入$filename,你会得到类似${"ab'cd"}的结果,这正是你想要的。
考虑以下示例:
$abcd = "First var";
${'$v'} = "Second var";
$v = "abcd";

say "${\"$v\"}"; # prints "First var"
say "${'$v'}";   # prints "Second var"

这段代码表明,使用"${\"$v\"}"时,$v将被插值,而使用"${'$v'}"时,它不会被插值。

"最初的回答"翻译成中文是"Original Answer"。


$filename 的值为 Specio::Constraint::Simple->_optimized_constraint - Eugen Konkov
@EugenKonkov,这并不重要:我的例子展示了为什么插值需要通过${}而不是""来完成。如果Perl查看$filename的值,并根据此决定插值应该在哪里完成,那将是荒谬的。 - Dada
现在这个例子回答了这个问题。这个单引号在双引号字符串内切换插值的文档记录在哪里? - Eugen Konkov
@EugenKonkov 我在 perldata 和 perlop 中都没有找到关于这个的任何文档。我不知道它是否有任何文档记录。 - Dada

0

这只是我的猜测,我找不到任何官方文件来证明,但似乎有效。

此外,ilmari说我应该通过符号表进行操作:

${$main::{'_<foo::bar::baz'}}

因此,我的代码分为两种情况:

情况1:

Devel::Peek::Dump( ${       "::_<$filename"          } );
Devel::Peek::Dump( ${       '::_<' ."$filename"      } );
Devel::Peek::Dump( ${       "::_<Specio::Constraint::Simple->_optimized_constraint"  } );
Devel::Peek::Dump( ${       '::_<Specio::Constraint::Simple->_optimized_constraint'  } );

这些都是相同的。双引号或单引号:没有关系。

SV = PV(0xe64ab00) at 0xe300bc8
  REFCNT = 1
  FLAGS = ()
  PV = 0

情况2:

Devel::Peek::Dump( ${ ${ 'main::' }{ "_<$filename" } } );
Devel::Peek::Dump( ${       ${'::'}{ "_<$filename" } } );
Devel::Peek::Dump( ${           $::{ "_<$filename" } } );
Devel::Peek::Dump( ${     $::{"_<Specio::Constraint::Simple->_optimized_constraint"} } );
Devel::Peek::Dump( ${     $::{'_<Specio::Constraint::Simple->_optimized_constraint'} } );

而且无论如何引用,只要通过符号表,我就可以找到该值:

SV = PV(0x15e0a40) at 0x186cd10
  REFCNT = 1
  FLAGS = (POK,pPOK)
  PV = 0x1934880 "Specio::Constraint::Simple->_optimized_constraint"\0
  CUR = 49
  LEN = 51

发现
引用变量名的方式并不重要,唯一的区别在于我们如何进行:

  1. 直接引用
  2. 通过符号表引用

因此,如果您的变量名没有双冒号,则可以使用任何一种形式:

    Devel::Peek::Dump( ${ $::{ "$name" } } );
    Devel::Peek::Dump( ${    "::$name"   } );

两者将指向同一物体。

但是,如果您的变量名称双冒号,则必须通过符号表访问其值

更新
有人可能会注意到,这个答案没有直接回答我的问题。你是对的。 我在这里发布了两种不同方法访问变量值的测试结果,因为我认为这些发现很重要。


1
尽管这很有趣,但我不认为它回答了你的问题,这个问题归结为${}中的\"_<$filename\"'_<$filename'之间的区别。在您所提供的所有答案示例中,您都是在$filename周围使用双引号。 - Dada
2
也许这是对你之前问题的回答? - Dada
@Dada:并非所有示例都在文件名周围使用 '。请注意硬编码:Devel::Peek::Dump( ${ '::_<Specio::Constraint::Simple->_optimized_constraint' } ); - Eugen Konkov
@Dada:我同意这并没有直接回答我的问题。在这里,我展示了 ${ } 的一些方面的测试结果。我不会接受这个答案。不要担心。 - Eugen Konkov

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