本文收录于dart入门潜修系列教程。
创作不易,转载还请备注。
概览
dart同其他语言一样,提供了丰富的操作符,什么是操作符?其实,我们常见的 +、-、*、/ 等都是操作符,这些操作符很多都对应于数学知识中的概念。使用操作符能大大简化我们的代码,并使之具有较高的可读性。本篇文章将对dart中的操作符进行阐述。
算术操作符
dart中,支持的算术操作符如下所示:
操作符 | 含义 |
---|---|
+ | 加法操作 |
- | 减法操作 |
* | 乘法操作 |
/ | 除法操作,返回浮点数 |
~/ | 除法操作,但返回整数 |
% | 求余数操作 |
结合上面的运算符及其含义,我们来看下示例:
void main() {
print(1 + 2); //打印 3
print(1 - 2); //打印 -1
print(1 * 2); //打印 2
print(1 / 2); //打印 0.5
print(1 % 2); //打印 1
print(1 ~/ 2); //打印 0
}
需要注意的是,除法操作符 / 和其他很多语言的意义不一样,这里不再简单取整,而是返回实际的浮点数。取整的功能则有另一个操作符 ~/ 来完成,操作符 ~/ 相当于取整操作,会舍弃小数。
自增自减操作符
dart中的自增操作符同样分为两种,一种是前缀自增,一种是后缀自增,如下所示:
操作符 | 含义 |
---|---|
++a | 前缀自增操作符,相当于a = a + 1,表达式的返回值是a+1 |
a++ | 后缀自增操作符,相当于a = a + 1,表达式的返回值是a |
--a | 前缀自减操作符,相当于a = a-1,表达式的返回值为a - 1 |
a-- | 后缀自减操作符,相当于a = a -1,表达式的返回值是a |
来看个自增自减操作符的示例:
void main() {
var a = 0;
var b = 0;
b = a++;
print("a = $a; b = $b"); //打印 a = 1; b = 0
a = 0;
b = ++a;
print("a = $a; b = $b"); //打印 a = 1; b = 1
a = 0;
b = a--;
print("a = $a; b = $b"); //打印 a = -1; b = 0
a = 0;
b = --a;
print("a = $a; b = $b"); //打印 a = -1; b = -1
}
由此可见,前缀表达式和后缀表达式最终的区别就是:前缀表达式除了自身改变外,其表达式的返回值也随之改变;而后缀表达式只有自身的值会改变,但其表达式的返回值依然是原来的旧值。
比较操作符
dart提供的比较操作符罗列如下:
操作符 | 含义 |
---|---|
== | 等于操作符 |
!= | 不等于操作符 |
> | 大于操作符 |
< | 小于操作符 |
>= | 大于等于操作符 |
<= | 小于等于操作符 |
这些比较操作符主要需要关注的是 == 操作符,我们知道在很多编程语言中,== 操作符并不像表面那样,看着内容相等就相等,这里还涉及到对象的比较以及对象内容的比较。先来看个例子:
//简单定义一个Person类,这里涉及到了面向对象的
//知识,会在后续的文章中进行阐述。
class Person {
String id;
Person(String id) {
this.id = id;
}
}
//测试方法main
void main() {
Person p = Person("1234");
Person p1 = p;
print(p == p1);//打印 true
Person p2 = Person("1234");
print(p == p2);//打印 false
}
在上面代码中,我们定义了一个Person类,然后生成了id=1234的实例p和p2,最后通过比较,我们发现p竟然不等于p2,按照常理,id相同的人显然他们应该是同一个人!但是== 操作符却不这么认为,默认的 == 操作符比较的实际上是两个对象的地址,如果两个对象地址一致则相等,否则不相等,所以p==p1为true,而p==p2为false。
那么如果我们觉得两个id相等的人就应该是同一个人,该如何进行比较呢?这就涉及到了操作符重载。
操作符重载是指,我们改变操作符默认行为的操作,上面除了 !=操作符不能被重载(!=操作符,实际上对应的是 !(obj1 == obj2) ),其他的都可以进行重载,比如我们现在想要得到id相等的人就是同一个人的期望,那么我们就可以重载==操作符,如下所示:
class Person {
String id;
Person(String id) {
this.id = id;
}
//重点在这里,我们重载了操作符==
bool operator ==(Person p) => this.id == p.id;
}
//测试
void main() {
Person p = Person("1234");//打印 true
Person p1 = p;
print(p == p1);
Person p2 = Person("1234");//打印 true
print(p == p2);
}
这样两个id相等的人就是同一个人了!
然而这也带来一个问题,如果我们操作失误,确实把两个不同的人写成了同一个id,但是他们确实是不同的人,而Person类本身又已经复写了==操作符,此时我们该怎么办?
这个就需要dart为我们提供的另一个方法了:identical,identical方法会精确比较两个对象的地址,不相等就是不相等,如下所示:
class Person {
String id;
Person(String id) {
this.id = id;
}
//我们同样复写了==操作符
bool operator ==(Person p) => this.id == p.id;
}
//测试
void main() {
Person p = Person("1234");
Person p1 = p;
Person p2 = Person("1234");
print(identical(p, p1));//打印 true
print(identical(p, p2));//注意这里,打印为 false
}
由此可知,identical方法不会关心你有没有重载==操作符,它会进行精确比较。
当然对于整型的比较我们无需关注这些,只要它们的内容相等即为相等,但是对于浮点数的比较,我还是有责任再引入一个众多语言都存在的一个经典的比较问题,来看个例子:
void main() {
var k = 0.1 + 0.2;
print(k);//打印 0.30000000000000004
print(k == 0.3);//打印 false !
}
是的,这就是浮点数比较的陷阱,dart同样无法避免,这是浮点数在计算机内部存储机制造成的,可自行查找相关资料。这里只是给出比较建议,类似于这样的场景,完全可以根据自己的需要进行取舍,我们可以结合两个浮点数的差值大小来进行判断,比如当两个浮点数之差小于10-6的时候就认为这两个数相等就可以了。
类型相关操作符
类型相关操作符,主要用于在运行时检查实例的类型是否匹配或者进行类型转换等,dart中提供了三个类型相关的操作符,如下所示:
操作符 | 含义 |
---|---|
as | 类型转换 |
is | 如果对象类型是匹配的类型则返回true |
is! | 如果对象类型匹配的类型则返回false |
这三个操作符都比较简单,以上述Person类为例,来看下他们的效果:
//定义了一个Person类
class Person {
String id;
String name;
Person(String id) {
this.id = id;
}
//复写toString的方法,目的是打印数据
String toString() {
return this.name;
}
}
//测试
void main() {
Object p = Person("1234");//注意这里的p是Object类型,Object是dart中所有类的基类
//检测p是否是Person类型,这里p的实际类型确实是Person,所以为true
if (p is Person) {
p.name = "张三";
}
print(p);//打印"张三"
//注意这里的str同样是Object类型,但是其实际类型是字符串
Object str = "test";
if (str is Person) {//str不是Person类型,此处为false
str.name = "李四";
}
print(str);//打印 test
}
上面代码展示了is的用法,is! 操作符刚好与其相反,这里不再演示。有一点需要注意,我们无法使用精确的类型去检测,比如上面我们将str定义为String str = "test",这个时候代码就会编译不通过,因为编译器已经知道了str的类型为String,所以在编译str.name这句语句的时候,就会报没有name这个字段的异常。
上面的代码显得有点麻烦,其实我们可以结合as操作符简化上述代码,如下所示:
void main() {
Object p = Person("1234");
(p as Person).name = "张三";//这里使用as操作符对p进行了转换
print(p);//打印 张三
}
上面代码演示了as操作符的用法,但是需要注意,如果p为null或者不符合匹配的类型的时候,as操作符将会抛出异常。
赋值操作符
dart中最直接的操作符就是单等于号(=),这个其实在上面例子中已经使用了很多次,= 操作符就是为变量赋值,而不用关心该变量是不是为null。实际上dart还为我们提供了 ??= 操作符,该操作符只有在变量为null的时候才会对变量进行赋值,如下所示:
void main() {
String str = "first str";
str ??= "second str";
print(str);//打印 first str
str = null;
str ??= "third str";//打印 third str
print(str);
}
除此之外,dart还支持很多扩展的赋值操作符,这里罗列如下:
操作符 | 含义 |
---|---|
-= | a -= b,相当于a = a -b |
/= | a /=b,相当于a = a / b |
%= | a %=b,相当于a = a % b |
>>= | a >>= b,相当于a = a >> b |
^= | a ^=b,相当于a = a ^ b |
+= | a +=b,相当于a = a + b |
*= | a *= b,相当于a = a * b |
~/= | a ~/= b,相当于a = a ~/ b |
<<= | a <<= b,相当于a = a << b |
&= | a &= b,相当于a = a & b |
|= | a |=b,相当于a = a | b |
这些操作符的含义还是比较容易理解的,这里不再展开阐述。
逻辑操作符
逻辑操作符主要用于布尔表达式判断,dart中的逻辑操作符主要三种,如下所示:
操作符 | 含义 |
---|---|
!expression | 非运算符,expression为false时返回true,否则返回false |
|| | 或运算操作符,其连接的表达式都为false时返回false,否则返回true |
&& | 并运算操作符,其连接的表达式都为true时返回true,否则返回false |
在dart中,同样对逻辑操作符进行了优化,即会执行“截断操作”,如下所示:
//这里有个判断是否为空的方法,方法体在返回之前
//打印了一条语句,用于判断该方法是否执行
bool isEmpty() {
print("is empty...");
return false;
}
//测试
void main() {
bool isNull = false;
if (isNull && isEmpty()) {//这个if代码块执行完成后,没有任何信息
print("is not blank");
}
isNull = true;
if (isNull && isEmpty()) {//这个执行完成后打印 is empty...
print("is blank");
}
}
上面代码是以 && 操作符为例,从中可以看出,当第一个表达式(即isNull)为false的时候,后面表达式(即isEmpty())就不会再执行。其他操作符同样也有对应的优化,这里不再阐述。
位操作符
dart支持多种位运算,罗列如下:
操作符 | 含义 |
---|---|
& | 位与操作符 |
| | 位或操作符 |
^ | 位异或操作符 |
~expression | 取反操作符 |
<< | 左移操作符 |
>> | 右移操作符 |
单纯的演示位操作符意义不大,因为无法直观的看出表达式和其结果的对应关系,还需要手动的计算才可以验证,不过计算机的输出结果是不用怀疑的,所以主要简单关注下其语法即可,如下所示:
void main() {
var val = 0x01;
var mask = 0xff;
print(val & mask);
print(val & ~mask);
print(val | mask);
print(val ^ mask);
print(val << 4);
print(val >> 4);
}
条件表达式
提起“条件”两个字,很自然想到的就是if else语句,确实if else能够解决各种条件判断问题,但是dart提供了两种更简洁的条件表达式,其语法如下所示:
//第一种
//当条件condition为true时执行expr1并返回其值
//当条件condition为false时执行expr2并返回其值
condition ? expr1 : expr2
//第二种
//当expr1不为null时返回其自身值,否则执行expr2并返回其值
expr1 ?? expr2
来看个例子:
void main() {
bool isOk = false;
print(isOk ? "is ok " : "is not ok");//打印 is not ok
String str;
print(str ?? "test");//打印test,因为此时str为null,所以返回test
str = "test";
print(str ?? "test2");//打印test,因为此时str不为null,所以返回其自身
}
展开符
首先明确一点,在dart官方文档中,将展开符定义为了dart的一种语法,而不是定义为一个操作符,其修饰关键字是两个点(..) 展开操作符示例如下:
class Person {
String name;
int age;
//复写toString方法,方便打印
String toString() {
return "name : $name ; age :$age";
}
}
//测试
void main() {
Person p = Person()//使用展开符完成赋值
..name = "张三"
..age = 20;
print(p);//打印 name : 张三 ; age :20
}
其他操作符
除了上面的操作符,dart还提供了一些其他的操作符,如下所示:
操作符 | 含义 |
---|---|
( ) | 方法调用操作符 |
[ ] | 数组访问操作符 |
. | 成员访问操作符 |
?. | 有条件的成员访问操作符 |
这主要来看一下 ?. 操作符,这个操作符也是成员访问操作符,只不过它是有条件的访问,来看个例子:
void main() {
Person p = Person()
..name = "张三";
print(p.name);//打印 张三
print(p?.name);//打印 张三
p = null;//此时我们将p设置为null
print(p?.name);//打印null
print(p.name);//!!!运行时错误,会crash
}
由代码可知,操作符 . 和 ?. 唯一的区别就是在当对象为null的时候,操作符 . 会直接crash,而 ?. 操作符则直接返回null。
至此,本篇文章阐述完毕。