为什么是两个构造?
关于 print 和 echo 的真相是,虽然它们在用户看来是两个不同的结构,但如果您深入了解它们,它们实际上都是回声的阴影,即看内部源代码。该源代码涉及解析器以及操作码处理程序。考虑一个简单的动作,例如显示数字零。无论您使用 echo 还是 print,都会调用相同的处理程序“ZEND_ECHO_SPEC_CONST_HANDLER”。 print 处理程序在调用 echo 处理程序之前做了一件事,它确保 print 的返回值为 1,如下所示:
ZVAL_LONG(&EX_T(opline->result.var).tmp_var, 1);
(见here for reference)
如果希望在条件表达式中使用 print,返回值是一种便利。为什么是 1 而不是 100?那么在 PHP 中,1 或 100 的真实性是相同的,即 true,而布尔上下文中的 0 等同于 false 值。在 PHP 中,所有非零值(正数和负数)都是真值,这源自 PHP 的 Perl 遗产。
但是,如果是这种情况,那么人们可能想知道为什么 echo 需要多个参数,而 print 只能处理一个。对于这个答案,我们需要求助于解析器,特别是文件 zend_language_parser.y。您会注意到 echo 具有内置的灵活性,因此它可以打印一个或多个表达式(请参阅here)。而 print 仅限于打印一个表达式(请参阅there)。
语法
在 C 编程语言和受其影响的语言(如 PHP)中,语句和表达式是有区别的。从语法上讲,echo expr, expr, ... expr 是一个语句,而 print expr 是一个表达式,因为它的计算结果是一个值。因此,与其他语句一样,echo expr 独立存在,无法包含在表达式中:
5 + echo 6; // syntax error
相比之下,print expr,可以单独形成一个语句:
print 5; // valid
或者,成为表达式的一部分:
$x = (5 + print 5); // 5
var_dump( $x ); // 6
人们可能会将print 视为一元运算符,例如! 或~,但它不是运算符。 !, ~ and print 的共同点是它们都内置在 PHP 中,并且每个都只接受一个参数。您可以使用print 创建以下奇怪但有效的代码:
<?php
print print print print 7; // 7111
乍一看,最后一个 print 语句打印其操作数 '7' first 的结果可能看起来很奇怪。但是,如果您深入挖掘并查看实际的操作码,那是有道理的:
line # * op fetch ext return operands
---------------------------------------------------------------------------------
3 0 > PRINT ~0 7
1 PRINT ~1 ~0
2 PRINT ~2 ~1
3 PRINT ~3 ~2
4 FREE ~3
5 > RETURN 1
生成的第一个操作码是对应于“打印 7”的操作码。 '~0' 是一个临时变量,其值为 1。该变量成为下一个打印操作码的操作数,该操作码又返回一个临时变量并重复该过程。最后一个临时变量根本没有被使用,所以它被释放了。
为什么print 会返回值而echo 不会?
表达式求值。例如,2 + 3 的计算结果为 5,abs(-10) 的计算结果为 10。由于print expr 本身就是一个表达式,那么它应该保存一个值并且确实如此,1 的一致值表示一个真实的结果,并且通过返回一个非零值,该表达式对于包含在另一个表达式中变得有用。例如在这个 sn-p 中,print 的返回值在确定函数序列时很有用:
<?php
function bar( $baz ) {
// other code
}
function foo() {
return print("In and out ...\n");
}
if ( foo() ) {
bar();
}
当涉及到动态调试时,您可能会发现具有特定价值的打印,如下一个示例所示:
<?php
$haystack = 'abcde';
$needle = 'f';
strpos($haystack,$needle) !== FALSE OR print "$needle not in $haystack";
// output: f not in abcde
作为旁注,一般来说,语句不是表达式;他们不返回值。例外当然是使用 print 的表达式语句,甚至是用作语句的简单表达式,例如1;,这是 PHP 从 C 继承的语法。表达式语句可能看起来很奇怪,但它非常有用,可以将参数传递给函数。
print 是一个函数吗?
不,它是一种语言结构。虽然所有函数调用都是表达式,但print (expr) 是一个表达式,尽管视觉上看起来好像它正在使用函数调用语法。事实上,这些括号是括号表达式语法,对表达式求值很有用。这解释了一个事实,如果表达式是一个简单的表达式,例如print "Hello, world!",它们有时是可选的。对于更复杂的表达式,例如print (5 ** 2 + 6/2); // 28,括号有助于表达式的评估。与函数名称 print is syntactically a keyword 和语义上的 "language construct" 不同。
PHP 中的术语“语言构造”通常指“伪”函数,如isset 或empty。虽然这些“构造”看起来和函数一模一样,但它们实际上是fexprs,即参数传递给它们而不进行求值,这需要编译器进行特殊处理。 print 恰好是一个 fexpr,它选择以与函数相同的方式评估其参数。
打印get_defined_functions()可以看出区别:没有列出print函数。 (虽然printf 和朋友们是:不像print,它们是真正的函数。)
为什么 print(foo) 会起作用?
出于与echo(foo) 相同的原因。这些括号与函数调用括号完全不同,因为它们与表达式有关。这就是为什么人们可以编码echo ( 5 + 8 ) 并期望显示13 的结果(参见reference)。这些括号涉及评估表达式而不是调用函数。注意:PHP中括号还有其他用途,比如if条件表达式、赋值列表、函数声明等。
为什么print(1,2,3) 和echo(1,2,3) 会导致语法错误?
语法为print expr、echo expr 或echo expr, expr, ..., expr。当 PHP 遇到 (1,2,3) 时,它会尝试将其解析为单个表达式并失败,因为与 C 不同,PHP 并没有真正的二进制逗号运算符;逗号更多地用作分隔符。 (你可能会在 PHP 的 for 循环中找到一个二进制逗号,它继承自 C 的语法。)
语义
语句echo e1, e2, ..., eN;可以理解为echo e1; echo e2; ...; echo eN;的语法糖。
由于所有的表达式都是语句,而echo e总是和print e有同样的副作用,而print e作为语句的返回值被忽略,我们可以把echo e理解为语法糖为print e。
这两个观察意味着echo e1, e2, ..., eN; 可以被视为print e1; print e2; ... print eN; 的语法糖。 (但是,请注意下面的非语义运行时差异。)
因此我们只需要定义print 的语义。 print e,评估时:
- 将其单个参数
e 和type-casts 评估为字符串s。 (因此,print e 等价于 print (string) e。)
- 将字符串
s 流式传输到the output buffer(最终将流式传输到标准输出)。
- 计算为整数
1。
字节码级别的差异
print 涉及填充返回变量的少量开销(伪代码)
print 125;
PRINT 125,$temp ; print 125 and place 1 in $temp
UNSET $temp ; remove $temp
单个echo 编译为一个操作码:
echo 125;
ECHO 125
多值echo 编译成多个操作码
echo 123, 456;
ECHO 123
ECHO 456
请注意,多值 echo 不会连接其参数,而是一个接一个地输出它们。
参考:zend_do_print、zend_do_echo。
运行时差异
ZEND_PRINT实现如下(伪代码)
PRINT var, result:
result = 1
ECHO var
所以它基本上将1 放在结果变量中,并将真正的工作委托给ZEND_ECHO 处理程序。 ZEND_ECHO 执行以下操作
ECHO var:
if var is object
temp = var->toString()
zend_print_variable(temp)
else
zend_print_variable(var)
zend_print_variable() 执行实际的“打印”(实际上,它只是重定向到专用的 SAPI 函数)。
速度:echo x vs print x
与 echo 不同,print 分配一个临时变量。然而,花在这个活动上的时间是微不足道的,所以这两种语言结构之间的差异可以忽略不计。
速度:echo a,b,c vs echo a.b.c
第一个编译成三个单独的语句。第二个计算整个表达式a.b.c.,打印结果并立即处理它。由于串联涉及内存分配和复制,第一个选项会更有效。
那么用哪一个呢?
在 Web 应用程序中,输出主要集中在模板中。由于模板使用<?=,这是echo 的别名,因此在代码的其他部分也使用echo 似乎是合乎逻辑的。 echo 的另一个优点是能够打印多个表达式而不连接它们,并且不涉及填充临时返回变量的开销。所以,请使用echo。