2.4 Dart中的运算符
运算符是编程语言中重要的组成部分,程序都是由一行一行的语句组成的,表达式用来描述语句的逻辑,变量和运算符组成了表达式。
Dart支持的运算符非常丰富,包括算数运算符、比较运算符、类型运算符、复合运算符、逻辑运算符、位运算符和条件运算符等。本节我们一起来学习Dart中运算符的使用。
2.4.1 算数运算符
算数运算符通常用来进行简单的数据运算,例如加、减、乘、除等。
四则运算是数学中基础的运算类型。“+”用来进行相加运算。在Dart中,并非只有数值才支持加运算,字符串、列表、Map等数据类型也支持进行相加运算,例如:
print(1+2);//3 print("1"+"2");//12
“-”用来进行减法运算,例如:
print(3-1);//2
当符号“-”只有一个操作数时,实际上是负号运算符,即将正数变成负数,负数变成正数,例如:
print(-(-1));//1
乘法运算使用运算符“*”来描述,例如:
print(2*4);//8
除法运算使用运算符“/”描述,例如:
print(10/2);//5.0
可以看到,除法运算符无论其操作数是整数还是小数,其都会返回浮点数,如果要进行整除运算,就可以使用“~/”运算符,例如:
print(9~/2);//4
除了上面常用的四则运算外,Dart中还提供了取模运算符,例如:
print(9%2);//1
和C语言类似,Dart中也支持自增与自减运算符。这两个运算符让很多编程初学者头疼不已,其实掌握了其中的规律,理解它们很简单。无论是自增运算符“++”还是自减运算符“--”,其都只有一个操作数,它们可以出现在操作数的前面,也可以出现在操作数的后面,前后的差异将使计算结果完全不同。
自增运算符的作用是在当前值的基础上自加1,即可改变当前变量的值,例如:
var a = 3; a++; print(a);//4 ++a; print(a);//5
从上面的代码来看,无论是++a还是a++,其执行完成后,都使原变量a的值增加了1。a++的作用其实除了将变量a加1外,还会将a增加前的原始值返回,而++a的作用除了将变量a加1外,会将计算后变量a最新的值返回,从下面的打印就可以清楚地看到前置与后置的区别:
print(a++);//打印3,这时a变成了4 print(++a);//打印5,这时a变成了5
“--”运算符和“++”运算符的逻辑基本一致,只是其是将变量的值进行减1操作。
2.4.2 比较运算符
比较运算符的作用是进行两个值的比较。虽然在Dart中,比较运算符的操作数可以是任意类型的值,但是对于List、Map或String对象,一般会使用函数来进行比较,比较运算符更多用于数值之间的比较。可用的比较运算符如表2-1所示。
表2-1 比较运算符
比较运算都会返回一个布尔值的结果:如果比较成立,就会返回布尔值true;如果比较不成立,就会返回布尔值false。
示例代码如下:
2.4.3 类型运算符
Dart中的类型运算符有3种:as、is和is!。其中,as运算符用来进行类型的“转换”,需要注意,这里的类型转换并不是真正意义上的转换,其并不会真正修改数据的类型,而是告诉Dart将当前数据当成某个类型的数据来进行处理。在面向对象开发中,这个运算符非常有用,后面我们会介绍。is运算符用来判断数据是否属于某个类型:如果属于,就返回布尔值true;如果不属于,就返回布尔值false。is!运算符的作用则与is刚好相反,它用来判断数据是否不属于某个类型,示例如下:
var a = 1; var b = "2"; print(a is int);//true print(b is! String);//false
2.4.4 复合运算符
简单理解,复合运算符是多种简单运算符的复合。复合运算符通常也叫作赋值复合运算符,因为其总是一种运算符与赋值运算符的组合。
首先,我们需要了解赋值运算符。前面也一直在使用赋值运算符,在定义变量时,需要使用“=”将某个值赋给变量,例如:
var a = 1;
同样,对变量进行修改时,也需要使用赋值运算符,例如:
var a = 1; a = a+1;
上面代码中的a=a+1语句中使用了两个运算符,分别为赋值运算符和加法运算符,可以将这两个运算符进行复合,例如:
a+=1;
a+=1与a=a+1的作用完全一样。表2-2列出了常用的复合运算符。
表2-2 常用的复合运算符
表2-2所列举的运算符中也包含复合的逻辑运算与位运算符。关于逻辑运算和位运算符,我们会在后面介绍。复合运算符只是将它们的运算结果又赋值给了所运算的变量。
2.4.5 逻辑运算符
逻辑运算符是针对布尔值进行运算的运算符。我们知道,布尔值只有两种:true和false。逻辑运算符所适用的操作数也只有true或者false。
“!”被称为逻辑非运算符,进行逻辑非运算,它是一个前置运算符,且只有一个操作数,当操作数为布尔值true时,运算结果为布尔值false,当操作数为布尔值false时,运算结果为布尔值true。例如:
print(!false);//true print(!true);//false
“||”被称为逻辑或运算符,进行逻辑或运算。逻辑或运算遵守下面的运算规则:
(1)当两个操作数中至少有一个操作数为true时,运算结果为true。
(2)当两个操作数都为false时,运算结果才为false。
示例代码如下:
print(false||false);//false print(false||true);//true print(true||false);//true print(true||true);//true
“&&”被称为逻辑与运算符,进行逻辑与运算。逻辑与运算遵守下面的运算规则:
(1)当两个操作数中至少有一个操作数为false时,运算结果为false。
(2)当两个操作数都为true时,运算结果为true。
示例代码如下:
print(false&&false);//false print(true&&false);//false print(false&&true);//false print(true&&true);//true
2.4.6 位运算符
位运算符是对二进制位进行操作的运算符。在计算机中,所有的数据存储实际上采用的都是二进制。计算机元器件的高低电位对支持二进制计数有着先天的优势,对于二进制,每一个数位只有0或1两种情况,逢二进一。例如,十进制数10使用二进制表示为1010。
“&”用来进行按位与运算。所谓按位与运算,是指将两个运算符的每一个二进制位分别进行与运算,即若两个对应二进制位都为1,则运算结果为1,否则为0。例如,将10与3进行按位与运算,结果为2:
var a = 10; //二进制 1010 var b = 3; //二进制 0010 print(a&b);//2 即二进制0010
“|”用来进行按位或运算。与按位与运算一样,按位或运算将两个运算数的每个二进制位分别进行或运算,若两个对应二进制位有一个为1,则运算结果为1,否则运算结果为0。例如,将10与4进行按位或运算,结果为14:
var c = 10; //二进制 1010 var d = 4; //二进制 0100 print(c|d);//14 即二进制1110
“~”用来进行按位非运算。按位非运算是一个前置的单元运算符,其只有一个操作数,对操作数的每一个二进制位进行取反,即为0的位运算后结果为1,为1的位运算后结果为0。例如,将4进行按位取反运算,结果为-5:
var e = 4; //00000100 print(~e); //11111011 以补码表示 原码为00000101 且为负数 即-5
理解按位非运算之前,首先需要了解在计算机中负数的表示方法。正数是用其二进制的原码来表示,而负数则是使用补码的方式,即首先将负数的绝对值转换成二进制,然后进行按位取反操作,最后加1。例如-5的绝对值为5,二进制表示为00000101,按位取反后为11111010,加1后为11111011,这个数就是计算机内存中-5的表达形式,当需要取数时,会将首位作为符号位,首位为1,则表示为负数,首位为0,则表示为正数,然后根据正数和负数的不同规则将二进制码转换成真正的数值。
“^”用来进行按位异或运算。关于按位异或运算,只需要牢记进行运算的两个数位相同时,运算结果为0,否则运算结果为1即可,即两个二进制位都为0或者都为1时,运算结果为0,否则运算结果为1。例如:
var f = 3; // 0011 var g = 5; // 0101 print(f^g);// 0110 十进制6
“<<”用来进行按位左移运算,即将每一个二进制位向左移动指定位数。对于二进制数据,一个很重要的特点是每左移一位,会使原数值进行乘2操作,例如:
var h = 3;//0011 print(h<<1);//0110 十进制6
同样,“>>”用来进行按位右移操作,例如:
var i = 4;//0100 print(i>>1);// 0010 十进制2
2.4.7 条件运算符
条件运算符与流程控制语句中的条件语句作用很像。这是一种更简洁的实现条件逻辑的方式。首先来看如下代码片段:
运行代码,控制台将输出字符串“a<=b”。我们来逐步分析上面的逻辑,首先定义两个变量a与b,a的值为3,b的值为5,之后使用条件运算符进行条件逻辑运算,条件运算符“?:”是一个三元的运算符,其有3个操作数,第一个操作数可以是一个布尔值或者运算结果为布尔值的表达式,当这个操作数为true时,条件运算的结果为第二个操作数的值,当第一个操作数为false时,条件运算的结果为第三个操作数的值。
在实际开发中,很多时候我们需要判断某个变量的值是否为null,不为null的时候再来做某些操作。使用条件运算符的代码如下:
var c = null; print(c==null?"无作为":"额外操作a:$c");
针对这种判空逻辑,可以使用Dart中的空条件运算符进行再次简化,示例代码如下:
var c = 3; print(c??"无作为");
“??”是Dart中的空条件运算符,其有两个操作数,若第一个操作数为null,则运算后的值为第二个操作数的值,若第一个操作数为非null值,则运算后的值为第一个操作数的值。这个运算符最大的作用是保证运算的结果不为null值,通常用来进行安全保证。
空条件运算符也可以和赋值运算符结合组成复合运算符,示例如下:
var c = null; c ??= 0;//与c = c??0;意义完全一样
2.4.8 级联运算符
级联运算符是Dart中比较高级的一种运算符,它可以让开发者对某个对象连续地进行一系列操作。这样的好处是可以减少中间变量的生成,并且让开发者更畅快地体验Dart编码的乐趣。
级联运算符使用“..”表示,在学习之前,我们需要先初步了解一些类的知识。关于类,后面会有专门的章节进行讲解,这里你只需要了解类中属性的相关概念即可。首先,通过前面的学习,我们知道在Dart中,整数、浮点数以及字符串都是对象,这些对象是由类构造出来的,类中定义了对象的属性和方法。我们也可以定义自己的类,例如下面定义了一个People类:
People类中分别定义了两个属性:姓名与年龄。在构造类对象后,可以使用点语法来对属性进行赋值,例如:
var p = People(); p.name = "珲少"; p.age = 26; print("name:${p.name},age:${p.age}");//name:珲少,age:26
如上面的代码所示,从对象的创建到属性赋值完成使用了3行代码,使用级联运算符可以一行代码搞定同样的事情,例如:
var p =People()..name="珲少"..age=26; print("name:${p.name},age:${p.age}");
简单来说,级联运算符的作用是对对象连续地执行一组操作,操作可以是对对象赋值,也可以是对对象方法的调用,这里不再演示。
2.4.9 点运算符
点运算符用来对对象的属性和方法进行操作。例如对象属性的赋值和获取、对象方法的调用等都使用点运算符来完成,例如:
如果使用点运算符获取了一个对象中不存在的属性或调用了对象中不存在的方法,就会抛出异常。同样,如果对null值使用点运算符也会产生错误,在实际开发中,一个变量是否为null往往不能完全确定,在Dart中有一种更加完全的对象获取属性或调用方法的运算符:条件成员访问运算符“?.”。这个运算符的作用是,如果所调用的对象是非null值,就会正常进行访问,否则返回null,但是不会产生错误,例如:
var c = null; print(c?.a);//null