Operators:运算符

运算符是通过给出的一个或多个值(或表达式)并产生另一个值(以便构造本身成为表达式)的东西。例如我们熟知的加减乘除运算符。

运算符按照其能接受值的数量可分为以下几种:

  • 一元运算符:只能接受一个值(只能对一个值生效),例如,!逻辑取反运算符或者++递增运算符
  • 二元运算符:可以接受两个值,例如>大于号或者+加号等
  • 三元运算符:只有一个三元运算符,也叫条件运算符。形如?:,语法是(条件)?为真时返回值:为假时返回值

运算符优先级

运算符的优先级规定了表达式该以怎样的顺序进行运算。必要时可以使用括号来改变优先级(通过括号的配对使用明确运算顺序,可增加代码的可读性)。下表按照优先级从高到低列出了各种运算符(相同优先级的运算符,其结合方向决定了求值顺序):

结合方向 运算符 附加信息
clone new clone 和 new
[ array()
** 算术运算符
++ -- ~ (int) (float) (string) (array) (object) (bool) @ 类型和递增/递减
instanceof 类型
! 逻辑运算符
* / % 算术运算符
+ - . 算术运算符和字符串运算符
<< >> 位运算符
< <= > >= 比较运算符
== != === !== <> <=> 比较运算符
& 位运算符和引用
^ 位运算符
| 位运算符
&& 逻辑运算符
|| 逻辑运算符
?? 比较运算符
? : 条件运算符
= += -= *= **= /= .= %= &= |= ^= <<= >>= 赋值运算符
and 逻辑运算符
xor 逻辑运算符
or 逻辑运算符

一些简单的示例:

<?php

// 运算符的优先级

$exp = 7 ** 2 * 2;// 相当于 7x7x2 = 98
echo $exp . PHP_EOL;// 98

$exp = 7 ** (2 * 2);// 相当于 7的(2x2)次方 
echo $exp . PHP_EOL;// 2401

$exp = 6 + 7 > 10;// 相当于 (6 + 7) > 10
echo $exp . PHP_EOL;// 1。相当于true

按照功能或使用场景的不同,运算符可分为以下几种:

  • 算术运算符
  • 赋值运算符
  • 位运算符
  • 比较运算符
  • 错误控制运算符
  • 执行运算符
  • 递增/递减运算符
  • 逻辑运算符
  • 字符串运算符
  • 数组运算符
  • 类型运算符

下面分别讲解以上运算符的使用。

算术运算符

算术运算符大概是我们最熟悉的运算符了。加减乘除是我们最开始接触的运算符,除此之外算术运算符还有取反取模。下表是算术运算符的具体使用:

例子 名称 结果
-a 取反 a 的负值。
a + b 加法 a 和 b 的和。
a - b 减法 a 和 b 的差。
a * b 乘法 a 和 b 的积。
a / b 除法 a 除以 b 的商。
a % b 取模 a 除以 b 的余数。
a ** b 返回 a 的 b 次幂。

其中,除法运算符总是返回浮点数,除非两个操作数都是整数并且正好能够整除(即结果也是整数),此时它返回一个整数。

取模运算符的操作数在运算之前都会被转为整数,且其结果与被除数的符号(正负号)相同。例如以下代码:

<?php

// 算术运算符

// 取反运算符
$negation = 7.9;
$negation = -$negation;
var_dump($negation);// 输出:float(-7.9)
$negation = +$negation;
var_dump($negation);// 输出:float(-7.9)

// 加法
$addition = 7 + 6;
var_dump($addition);// 输出:int(13)

// 减法
$subtraction = 7 - 9;
var_dump($subtraction);// 输出:int(-2)

// 乘法
$multiplication = 6 * 6;
var_dump($multiplication);// 输出:int(36)

// 除法
$division = 7 / 6;
var_dump($division);// 输出:float(1.1666666666667)

// 取模
$modulo = 89 % 9;
var_dump($modulo);// 输出:int(8)
$modulo = 89.3 % 9;
var_dump($modulo);// 输出:int(8)
$modulo = 89.9 % 9;
var_dump($modulo);// 输出:int(8)
$modulo = -89 % 9;
var_dump($modulo);// 输出:int(-8)
$modulo = 89 % -9;
var_dump($modulo);// 输出:int(8)

// 幂
$exponentiation = 2 ** 3;
var_dump($exponentiation);// 输出:int(8)
$exponentiation = 2 ** -3;
var_dump($exponentiation);// 输出:float(0.125) 即八分之一(二分之一的三次幂)

赋值运算符

赋值运算符我们也是很熟悉了,在之前所有的示例代码中,定义一个变量时需要给它赋值,形如$a = 1,其中等于号=就是最基本的赋值运算符,它并不表示“相等”的意思,而是表示把右边表达式的值,赋给左边的变量

除了最基本的赋值运算符=,还有一些组合运算符,如下代码:

<?php

// 赋值运算符

$a = 5;
$b = ($a = 6) + 4;// 此时 $b 的值为 10:赋值表达式 = 所赋的值

// 组合运算符
$str = '一个字符串';
$str .= '被添加的部分';// 该表达式相当于 $str = $str . '被添加的部分';
var_dump($str);// 输出:string(33) "一个字符串被添加的部分"

$addition = 7;
$addition += 8;// 该表达式等同于 $addition = $addition + 8;
var_dump($addition);// 输出:int(15)

$subtraction = 98;
$subtraction -= 67;// 该表达式等同于 $subtraction = $subtraction - 67;
var_dump($subtraction);// 输出:int(31)

$multiplication = 9;
$multiplication *= 9;// 该表达式等同于 $multiplication = $multiplication * 9;
var_dump($multiplication);// 输出:int(81)

$division = 81;
$division /= 9;// 该表达式等同于 $division = $division / 9;
var_dump($division);// 输出:int(9)

$modulo = 67;
$modulo %= 6;// 该表达式等同于 $modulo = $modulo % 6;
var_dump($modulo);// 输出:int(1)

$exponentiation = 3;
$exponentiation **= 3;
var_dump($exponentiation);// 输出:int(27)

以上代码可以看出,组合赋值运算符简化了表达式。

需要注意的是,赋值运算是将原变量的值拷贝至新变量(值传递),改变其中一个并不影响另一个,除非被拷贝值为对象(object,以引用赋值,改变其中一个,另一个也会随之改变)。

那么如何进行引用赋值呢?

引用赋值仅发生在变量之间(因为常量不可改变),形同$a = &$b,在被引用变量前加上&符号。

引用赋值将两个变量指向同一个数据,对该数据来说,这两个变量仅仅相当于别名。请看下列示例代码:

<?php

// 引用赋值

$assignment = 78;
$assignment2 = &$assignment;// 引用传递:变量前添加 & 符号
var_dump($assignment2);// 输出:int(78)
var_dump($assignment);// 输出:int(78)

$assignment2 = '我引用了一个变量后又改变了它';
var_dump($assignment);// 输出:string(42) "我引用了一个变量后又改变了它"

// 对比:传值赋值
$value = 78;
$value2 = $value;// 传值传递,没有 & 符号
var_dump($value2);// 输出:int(78)
$value2 = '我试图改变了$value2的值';
var_dump($value);// 输出:int(78)

比较运算符

比较运算符允许对两个值进行比较。下表展示了各比较运算符的使用:

例子 名称 结果
a == b 等于 TRUE,如果类型转换后 a 等于 b。
a === b 全等 TRUE,如果 a 等于 b,并且它们的类型也相同。
a != b 不等 TRUE,如果类型转换后 a 不等于 b。
a <> b 不等 TRUE,如果类型转换后 a 不等于 b。
a !== b 不全等 TRUE,如果 a 不等于 b,或者它们的类型不同。
a < b 小与 TRUE,如果 a 严格小于 b。
a > b 大于 TRUE,如果 a 严格大于 b。
a <= b 小于等于 TRUE,如果 a 小于或者等于 b。
a >= b 大于等于 TRUE,如果 a 大于或者等于 b。
a <=> b 太空船运算符(组合比较符) 当a小于、等于、大于b时 分别返回一个小于、等于、大于0的integer 值。 PHP7开始提供.
a ?? b ?? c NULL 合并操作符 从左往右第一个存在且不为 NULL 的操作数。如果都没有定义且不为 NULL,则返回 NULL。PHP7开始提供。

具体示例如下:

<?php

// 比较运算符
// 等于与不等于:只比较值,比较前进行类型转换
$left = 'hello';
$right = 'hello ';
var_dump($left == $right);// bool(false)
var_dump($left != $right);// bool(true)
var_dump($left <> $right);// bool(true)

// 全等与不全等:不进行类型转换,类型与值都要比较
$left = '90';
$right = 90;
var_dump($left === $right);// bool(false)
var_dump($left !== $right);// bool(true)

// 太空船运算符:比较两个值是否相等,比较前进行类型转换,并返回一个整型值(-1,0,1)
$left = 89;
$right = '90';
$middle = 89;
var_dump($left <=> $right);// int(-1)
var_dump($right <=> $left);// int(1)
var_dump($left <=> $middle);// int(0)

// NULL合并操作符:查找并返回三个操作数中第一个不为NULL的操作数,否则返回NULL
$left = NULL;
$right = '90';
$middle = 87;
var_dump($left ?? $middle ?? $right);// int(87)
var_dump($left2 ?? $middle2 ?? $right);// string(2) "90"

错误控制运算符

错误控制运算符使用符号@表示,在一个PHP表达式之前添加@符号,则表明你想让该表达式可能产生的错误信息都被忽略掉。有时我们想不向用户显示错误提示信息(避免暴露文件地址等),可以选择使用错误控制运算符。例如下方代码:

<?php

// 错误控制运算符
// include './jisuapi.php';// 包含一个不存在的文件将输出警告信息:Warning: include(./jisuapi.php): failed to open stream: No such file or directory
@include './jisuapi.php';// 不再输出错误信息

function test()
{
    // $value = $cache[$key];// 输出错误信息:Notice: Undefined variable: cache
    $value = @$cache[$key];// 不再输出错误信息
}
test();

但请不要滥用它!一旦脚本发生错误,却没有任何提示,这对于调试是很不好的体验。建议在清楚会造成什么错误并有相关措施后,再进行错误控制。

提示:错误控制运算符@仅对表达式有效,不能把它用于函数或类定义,也不能用于条件结构(if或者foreach等)。

执行运算符

PHP支持直接执行shell命令,可使用一对反引号(非单引号,Windows 系统下 Tab 键上方的符号键)包裹 shell 命令,其效果与函数shell_exec()相同。如下所示:

<?php

// 执行运算符
$shell = `mkdir shell`;// shell命令:创建一个名为 shell 的空目录
echo $shell;// 已创建一个名为shell的空目录

$shell = `rmdir shell`;// 删除 shell 目录
echo $shell;

递增/递减运算符

笔者在初学 C++ 时,看到递增递减运算符简直一头懵。递增递减运算很容易理解,就是对一个变量进行加一或者减一的操作。

但是递增递减的位置决定了其返回值。请看下表:

例子 名称 效果
++a 前自增 a 的值加一,然后返回 a。
a++ 后自增 返回 a,然后将 a 的值加一。
--a 前自减 a 的值减一, 然后返回 a。
a-- 后自减 返回 a,然后将 a 的值减一。

前自增与前自减是先将变量进行加一/减一的操作,然后返回变量现在的值; 而后自增与后自减是先返回变量原来的值,再对变量进行加一/减一的操作。

具体请看下方示例代码:

<?php

// 递增/递减运算符

// 后自增与后自减
$back = 78;
echo $back++;// 输出:78。即表示:后自增与后自减返回的值是变量原先的值
echo PHP_EOL;
echo $back;// 输出:79。即表示:返回值后,自增自减才开始,此时变量变化
echo PHP_EOL;

$back = 78;
echo $back--;// 输出:78。即表示:后自增与后自减返回的值是变量原先的值
echo PHP_EOL;
echo $back;// 输出:77。即表示:返回值后,自增自减才开始,此时变量变化
echo PHP_EOL;

// 前自增与前自减
$front = 78;
echo ++$front;// 输出:79。即表示:前自增与前自减返回的值是变量经过自增自减操作后的值
echo PHP_EOL;
echo $front;// 输出:79。即表示:自增自减后返回变量
echo PHP_EOL;

$front = 78;
echo --$front;// 输出:77。即表示:前自增与前自减返回的值是变量经过自增自减操作后的值
echo PHP_EOL;
echo $front;// 输出:77。即表示:自增自减后返回变量
echo PHP_EOL;

// 如果直接比较返回值,前自增与后自增的返回值是一样的,不同的只是返回值与加减的操作顺序
$front = $back = 80;
var_dump($front == $back);// 输出:bool(true)
var_dump(++$front == $back++);// 输出:bool(false)
$front = $back = 80;
++$front;
$back++;
var_dump($front == $back);// 输出:bool(true)

// NULL值递增结果为1(继续递增结果为2,以此类推),而递减时NULL值不变
$null = NULL;
$null++;
var_dump($null);// 输出:int(1)
$null = NULL;
$null--;
var_dump($null);// 输出:NULL

在进行条件判断时,条件表达式会自动转为布尔值,而递增/递减运算符不影响布尔值。关于这点有个很有趣的案例:

<?php

// 一个关于运算符优先级的案例:
$left = 6;
$right = 9;
if ($left = 5 || $right = 9) {
    $left++;
    $right++;
}
echo $left , ' ' , $right;// 输出:1 10

发生了什么???如果没有仔细看代码结构,我们可能会以为输出结果是69,毕竟if条件并不成立。 但运行结果打脸了。为什么?

我们将上面的代码拆开,提取其判断式:$left = 5 || $right = 9,按照运算符的优先级顺序,可以将之初步解析为:

$left = (5 || $right = 9)

此时if将判断变量$left的值。我们继续分析括号内的表达式,同样按照运算符的优先级顺序,括号内先进行5$right的逻辑或运算,很明显此时结果为真,等号之后的代码不再执行。也就是说,此时$left的值为trueif条件成立。

解说到这一步,也许有人会问,就算运行了if代码块的内容,输出结果也应该是710啊。但结果告诉我们,这么想是错的。刚才说了,在if的判断式中,$left的值变成了布尔值true。像之前强调的,递增/递减运算符不会影响布尔值。所以$left仍旧是true,在使用echo输出时,显示为1(如果使用var_dump()检查其数据类型可以发现它仍旧是布尔值true)。

逻辑运算符

逻辑运算符返回两个布尔值以某种规则比较时的结果。如下表所示:

例子 名称 结果
!a Not(逻辑非) TRUE,如果 a 不为 TRUE。
a && b And(逻辑与) TRUE,如果 a 和 b 都为 TRUE。
a || b Or(逻辑或) TRUE,如果 a 或 b 任一为 TRUE。
a and b And(逻辑与) TRUE,如果 a 和 b 都为 TRUE。
a or b Or(逻辑或) TRUE,如果 a 或 b 任一为 TRUE。
a xor b Xor(逻辑异或) TRUE,如果 a 或 b 任一为 TRUE,但不同时是。

其中,||的运算优先级高于or&&的运算优先级高于and

示例代码如下:

<?php

// 逻辑运算符

$left = '一个字符串,在布尔类型中表示true';
$right = 89;
$false = '';
$null = NULL;

// 逻辑与:二者都为true时返回true,否则返回false
var_dump($left && $right);// 输出:bool(true)
var_dump($left && $false);// 输出:bool(false)
var_dump($false && $null);// 输出:bool(false)
var_dump($left and $false);// 输出:bool(false)

// 逻辑或:只要有其中一个为true就返回true,二者都为false则返回false
var_dump($right || $false);// 输出:bool(true)

// 逻辑非,返回与原来相反的真假值
var_dump(!$left);// 输出:bool(false)

// 逻辑异或:只有二者为一真一假时返回true,否则返回false
var_dump($left xor $false);// 输出:bool(true)
var_dump($left xor $right);// 输出:bool(false)
var_dump($false xor $null);// 输出:bool(false)

字符串运算符

字符串运算符只有两个:..=。其中.点连接符连接左右参数,返回连接后的字符串;.=将右边参数附加到左边参数之后。例如:

<?php

// 字符串运算符

$str = '极速数据' . 'https://www.jisuapi.com/';
var_dump($str);// 输出:string(36) "极速数据https://www.jisuapi.com/"

$str2 = 'PHP';
$str2 .= '是世界上最好的语言';
var_dump($str2);// 输出:string(30) "PHP是世界上最好的语言"

数组运算符

数组运算符如下表所示:

例子 名称 结果
a + b 联合 a 和 b 的联合。
a == b 相等 如果 a 和 b 具有相同的键/值对则为 TRUE。
a === b 全等 如果 a 和 b 具有相同的键/值对并且顺序和类型都相同则为 TRUE。
a != b 不等 如果 a 不等于 b 则为 TRUE。
a <> b 不等 如果 a 不等于 b 则为 TRUE。
a !== b 不全等 如果 a 不全等于 b 则为 TRUE。

数组联合运算符+将右边的数组元素添加至左边的数组末尾,由于数组元素的键名唯一,如果左边数组与右边数组有相同键名,则联合时只保留左边数组的,右边的被忽略。如下:

<?php

// 数组运算符

$arr1 = [
    'first'=>'first',
    'second'=>'second',
    'duplicate'=>'duplicate',
];
$arr2 = [
    'third'=>'third',
    'duplicate'=>'2',
];
$arr3 = [
    'duplicate'=>'2',
    'third'=>'third',
];
$arr = $arr1 + $arr2;
var_dump($arr);
/* 输出:
array(4) {
  ["first"]=>
  string(5) "first"
  ["second"]=>
  string(6) "second"
  ["duplicate"]=>
  string(9) "duplicate"
  ["third"]=>
  string(5) "third"
}
*/

var_dump($arr2 == $arr3);// 输出:bool(true)
var_dump($arr2 === $arr3);// 输出:bool(false)

类型运算符

类型运算符instanceof用来确定某个变量是不是属于某一个类的实例。除此之外它还可以做更多事情:

<?php

// 类型运算符

class tClass
{
    // 类 tClass
}

interface tInterface
{
    // 接口 tInterface
}

class cClass extends tClass implements tInterface
{
    // 实现了接口 tInterface 的子类 cClass
}

// 判断变量是否属于某一class的实例
$ins = new tClass();
var_dump($ins instanceof tClass);// 输出:bool(true)
$ins = '一个很随便的字符串';
var_dump($ins instanceof tClass);// 输出:bool(false)

// 判断变量是否是继承某一父类的子类的实例
$ins = new cClass();
var_dump($ins instanceof tClass);// 输出:bool(true)
var_dump($ins instanceof cClass);// 输出:bool(true)

// 判断变量是否是实现了某接口的对象的实例
$ins = new cClass();
var_dump($ins instanceof cClass);// 输出:bool(true)
var_dump($ins instanceof tInterface);// 输出:bool(true)

$left = new tClass();
$right = '一个普普通通的字符串';
$ins = 'tClass';// 一个有点特殊的字符串:与类名相同
var_dump($left instanceof $right);// 输出:bool(false)
var_dump($left instanceof $ins);// 输出:bool(true)
var_dump($ins instanceof $left);// 输出:bool(false)

$left = '一个普普通通的字符串';
$right = '另一个普普通通的字符串';
var_dump($left instanceof $right);// 输出:bool(false)

位运算符

位运算一向很少被使用,PHP 的位运算符是对整型值中指定的位进行求值和操作。比如对十进制、八进制、二进制等数字的一些操作。下表是位运算符的说明:

例子 名称 结果
a & b And(按位与) 将把 a 和 b 中都为 1 的位设为 1,其余为 0
a | b Or(按位或) 将把 a 和 b 中任何一个为 1 的位设为 1,其余为 0
a ^ b Xor(按位异或) 将把 a 和 b 中一个为 1 另一个为 0 的位设为 1,其余为 0
~ a Not(按位取反) 将 a 中为 0 的位设为 1,其余不变,并添加取反符号
a << b Shift left(左移) 将 a 中的位向左移动 b 次(每一次移动都表示“乘以 2”)。
a >> b Shift right(右移) 将 a 中的位向右移动 b 次(每一次移动都表示“除以 2”)。

位移在PHP中属于数学运算,向任何方向移出去的位都被丢弃。左移时右侧以零填充,符号位被移走意味着正负号不被保留;右移时左侧以符号位填充,意味着正负号被保留。

示例代码如下:

<?php

$a = 0b10;// 相当于十进制的 2
$b = 0b11;// 相当于十进制的 3

// 将把 $a 和 $b 中都为 1 的位设为 1,其余为 0
var_dump($a & $b);// int(2)

// 将把 $a 和 $b 中任何一个为 1 的位设为 1,其余为 0
var_dump($a | $b);// int(3)

// 将把 $a 和 $b 中一个为 1 另一个为 0 的位设为 1,其余为 0
var_dump($a ^ $b);// int(1)

// 将 $a 中为 0 的位设为 1,其余不变,并添加取反符号
var_dump(~$a);// int(-3)

// 将 $a 中的位向右移动 $b 次(每一次移动都表示“除以 2”)
var_dump($a >> $b);// int(0)

// 将 $a 中的位向左移动 $b 次(每一次移动都表示“乘以 2”)
var_dump($a << $b);// int(16)

实际项目中为了代码可读性很少使用位运算符,但在某些场景下位运算符可使一些操作变得简单,具体情况具体分析。


在不清楚运算符的优先级时写代码可能会造成许多奇奇怪怪的不符合预期的现象,但这都是正常的,我们需要检查运算是否正确按照想要的顺序去执行了,必要时请使用圆括号调整运算顺序!

现在我们已完成运算符的学习,接下来进入流程控制语句的学习!