【Dart】运算符的类型与函数的使用

146 阅读10分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第28天,点击查看活动详情

前言

昨天我们介绍了 Dart 语言中的变量类型,今天我们来介绍一下 Dart 中的运算符和函数类型。

运算符

dart 定义了下表中显示的操作符。您可以重写其中的许多操作符,如可重写操作符中所述:

描述操作符
一元后置操作符expr++    expr--    ()    []    .    ?.
一元前置操作符-expr    !expr    ~expr    ++expr    --expr
乘除运算*    /    %    ~/
加减运算+    -
移位运算<<    >>
按位与&
按位异或^
按位或|
关系和类型测试>=    >    <=    <    as    is    is!
相等==    !=
逻辑与&&
逻辑或||
是否为null??
天健判断(三元运算)expr1 ? expr2 : expr3
级联..
赋值=    *=    /=    ~/=    %=    +=    -=    <<=    >>=    &=    ^=

算术运算符

dart 中的算术运算符与其他语言大体是类似的,不过有一点不一致的是,dart 中的除法是符合我们生活中的正常运算的,即使是int类型的值也会有小数的结果出现:

 print(3 / 2); // 1.5 not 1

如果想要像其他语言一直取整,需要使用~/运算符:

 print(3 ~/ 2);// 1

类型测试操作符

asisis!操作符可以方便地在运行时检查类型。

操作符说明
as形态转换
is如果对象具有指定的类型,则为True
is!如果对象具有指定的类型,则为False
  • 对于 is 操作符,如果 obj 实现了 T 指定的接口,则obj is T为0真。例如,obj is Object总是为真(也就是说在对任何父类使用 is 操作符都会返回真)。

  • 使用 as 操作符将对象转换为特定类型。一般来说,应该将其作为 is 测试的简写形式,以使用该对象的表达式对对象进行测试:

     if (emp is Person) {
         // Type check
         emp.firstName = 'Bob';
     }
    

    使用as操作符可以使代码更简短:

     (emp as Person).firstName = 'Bob';
    

注意: 上面两个例子的代码不等效。如果 emp 是 null 或不是 Person,那么第一个示例(使用 is)么都不做;第二个(带有 as)抛出异常。

赋值操作符

dart 的赋值操作符和复合赋值操作符也和其他语言的类似,也有一点要注意,若要仅仅为空的变量赋值可以使用??=操作符。

 // Assign value to a
 a = value;
 // Assign value to b if b is null; otherwise, b stays the same
 // /仅仅在b为空的情况下b被赋值value否则b的值不变
 b ??= value;

逻辑运算符

!&&||,同其它类型语言,略。

位和移位运算

可以操纵 dart 中的单个数字位。通常,会使用这些位和移位运算符。

运算符说明
&按位与
|按位或
^按位异或
~expr按位取反
<<左移
>>右移

下例是使用位运算符和唯一运算符的示例:

 final value = 0x22;
 final bitmask = 0x0f;

 assert((value & bitmask) == 0x02); // AND
 assert((value & ~bitmask) == 0x20); // AND NOT
 assert((value | bitmask) == 0x2f); // OR
 assert((value ^ bitmask) == 0x2d); // XOR
 assert((value << 4) == 0x220); // Shift left
 assert((value >> 4) == 0x02); // Shift right

条件表达式

dart 中有两个运算符,可以精确地计算那些可能需要if-else语句的表达式:

  • condition ? expr1 : expr2(三目表达式): 如果条件为真,则计算 expr1(并返回其值);否则,计算并返回 expr2 的值。
  • expr1 ?? expr2: 如果 expr1 是非空的,则返回其值;否则,计算并返回 expr2 的值。

当需要基于布尔表达式的结果选择赋值,请考虑使用 **?: **。

 var visibility = isPublic ? 'public' : 'private';

如果布尔表达式只想判断值是否为 null,请考虑使用 **?? **。

 String playerName(String name) => name ?? 'Guest';

级联表示法 (..)

级联(..)允许在同一个对象上创建一个操作序列。除了函数调用之外,还可以访问同一对象上的字段。这通常可以省去创建临时变量的步骤,能更为流畅的写代码。

看下面这段代码:

 querySelector('#confirm') // Get an object.
   ..text = 'Confirm' // Use its members.
   ..classes.add('important')
   ..onClick.listen((e) => window.alert('Confirmed!'));

首先调用querySelector()方法返回一个selector对象。跟随级联表示法的代码对这个选择器对象进行操作,忽略可能返回的任何后续值。

注意: 在级联的时候,获取的对象的第一个属性或方法开始就需要使用..,全程都不能使用单独的.

等价于:

 var button = querySelector('#confirm');
 button.text = 'Confirm';
 button.classes.add('important');
 button.onClick.listen((e) => window.alert('Confirmed!'));

也可以嵌套级联操作。例如:

 final addressBook = (AddressBookBuilder()
       ..name = 'jenny'
       ..email = 'jenny@example.com'
       ..phone = (PhoneNumberBuilder()
             ..number = '415-555-0100'
             ..label = 'home')
           .build())
     .build();

不过,在返回实际对象的函数上构造级联要小心,可能我们需要级联的表达式根本就没有返回给我们正确的对象。例如,以下代码会出错:

 var sb = StringBuffer();
 sb.write('foo')
   ..write('bar'); // Error: method 'write' isn't defined for 'void'.
 // sb.write()返回结果为void,所以不能再一个void结果上继续构建级联操作。

注意: 严格地说,级联(..)表示法不是运算符。这只是 dart 语法的一部分。

其它运算符(主要是 ?.)

运算符名称说明
()功能函数表示一个函数调用
[]访问列表引用列表中指定索引处的值
.访问成员表示表达式的属性;例如:foo.bar从表达式foo中选择属性bar
?.根据条件访问成员和(.)相似,但是左边的操作数可以为空;例如:foo?.bar从foo的表达式中选择bar属性,如果foo为空则返回null

这里说明一下.?.

 class A {
     var b;
 }
 
 void main() {
     /// aobj为A的一个实例
     var aobj = A();
     ///z为空值的变量
     var z=null;
     print(aobj.b);
     print(z?.b); // null
     // 如果是一个方法,则说明都不会发生
     
     print(z.b); // exception
 }

函数

dart 是一种真正的面向对象的语言,所以即使是函数也是对象,并且有一个类型 Function。这意味着函数可以赋值给变量或作为参数传递给其他函数,这是函数式编程的典型特征。

函数声明

 bool isNoble(int atomicNumber) {
   return _nobleGases[atomicNumber] != null;
 }

对于只包含一个表达式的函数,可以使用简写语法(类似 JavaScript 中的箭头函数):

 bool isNoble (int atomicNumber)=> _nobleGases [ atomicNumber ] != null ;

注意:箭头(=>)分号(;)之间只能出现一个表达式(不是语句)。例如,不能在那里放一个if语句,但是可以使用一个条件表达式。

函数赋值

  • 函数作为变量

     var say = (str){
       print(str);
     };
     say("hi world");
    
  • 函数作为参数传递

     void execute(var callback) {
         callback();
     }
     execute(() => print("xxx"))
    

main 函数

每个应用程序都必须有一个顶级的main()函数,它作为应用程序的入口点。main()函数返回void,并有一个可选的列表参数作为参数。

 void main() {
   querySelector('#sample_text_id')
     ..text = 'Click me!'
     ..onClick.listen(reverseText);
 }

下面是一个命令行应用程序的main()函数示例,它接受参数:

 // Run the app like this: dart args.dart 1 test
 void main(List<String> arguments) {
   print(arguments);
 
   assert(arguments.length == 2);
   assert(int.parse(arguments[0]) == 1);
   assert(arguments[1] == 'test');
 }

注: 可以使用args库来定义和解析命令行参数。

函数参数

不能同时使用可选的位置参数和可选的命名参数

可选的位置参数和可选的命名参数都能和普通的函数参数一起使用。

可选的位置参数

包装一组函数参数,用[]标记为可选的位置参数,并放在参数列表的最后面

 String say(String from, String msg, [String device]) {
   var result = '$from says $msg';
   if (device != null) {
     result = '$result with a $device';
   }
   return result;
 }

下面是一个不带可选参数调用这个函数的例子:

 say('Bob', 'Howdy'); //结果是: Bob says Howdy

下面是用第三个参数调用这个函数的例子:

 say('Bob', 'Howdy', 'smoke signal'); //结果是:Bob says Howdy with a smoke signal

可选的命名参数

定义函数时,使用{param1, param2, …},放在参数列表的最后面,用于指定命名参数。例如:

 //设置[bold]和[hidden]标志
 void enableFlags({bool bold, bool hidden}) {
     // ... 
 }

调用函数时,可以使用paramName: value来指定命名参数。例如:

 enableFlags(bold: true, hidden: false);

可选命名参数在 flutter 中使用非常多,可以在任何 dart 代码(不仅仅是 flutter)中注释一个已命名的参数,并使用@required说明它是一个必传的参数。例如:

 const Scrollbar({Key key, @required Widget child})

当构造Scrollbar时,分析器在没有子参数(child)时报错。

注: Required在元包中被定义。或者直接使用import package:meta/meta.dart导入或者导入其他包含 meta 导出的包,例如 flutter 的包:Flutter /material.dart

默然参数值

可以使用 = 来定义命名和位置参数的默认值。默认值必须是编译时常量。如果没有提供默认值,则默认值为 null。

注意: 函数的默认参数值只能写在可选的位置参数({})和可选的命名参数([])内。

下面是为命名参数设置默认值的示例:

 /// Sets the [bold] and [hidden] flags ...
 void enableFlags({bool bold = false, bool hidden = false}) {...}
 
 // bold will be true; hidden will be false.
 enableFlags(bold: true);

下一个示例展示了如何设置位置参数的默认值:

 String say(String from, String msg,
     [String device = 'carrier pigeon', String mood]) {
   var result = '$from says $msg';
   if (device != null) {
     result = '$result with a $device';
   }
   if (mood != null) {
     result = '$result (in a $mood mood)';
   }
   return result;
 }
 
 assert(say('Bob', 'Howdy') ==
     'Bob says Howdy with a carrier pigeon');
     

还可以将 list 或 map 集合作为默认值。下面的示例定义一个函数doStuff(),该函数指定列表参数的默认列表和礼品参数的默认map集合:

 void doStuff(
     {List<int> list = const [1, 2, 3],
     Map<String, String> gifts = const {
       'first': 'paper',
       'second': 'cotton',
       'third': 'leather'
     }}) {
   print('list:  $list');
   print('gifts: $gifts');
 }

匿名函数

大多数函数都被命名,如main()printElement()。也可以创建一个没有函数名称的函数,这种函数称为匿名函数,或者 lambda 函数或者闭包函数。

不过可以为变量分配一个匿名函数。例如,可以从集合中添加或删除它。

匿名函数看起来类似于命名函数:有0个或者多个参数,在括号之间用逗号和可选类型标注分隔,后面的代码块包含函数的主体。

 ([[Type] param1[, …]]) { 
   codeBlock; 
 }; 

下面的示例定义了一个带有无类型参数 item 的匿名函数。该函数为列表中的每个 item 调用,打印一个字符串,该字符串包含指定索引的值:

 var list = ['apples', 'bananas', 'oranges'];
 list.forEach((item) {
   print('${list.indexOf(item)}: $item');
 });

如果函数只包含一条语句,可以使用箭头函数:

 var list = ['apples', 'bananas', 'oranges'];
 list.forEach((item) => print('${list.indexOf(item)}: $item'));

箭头函数

在 dart 中的箭头函数的用法和 JavaScript 中箭头函数只写一行直接返回值是类似的效果,不过有所区别的是 dart 中只有简便写法这个功能,无法修改this的指向问题。

同时 dart 中的箭头函数只能写一行表达式,没有{}函数体,虽然能在最外层包裹一层{},但这不代表函数体,而是一个 Set 类型的数据。 {}不仅在定义函数的时候用到,在构建 Set、Map集合字面量 的时候也用到了。

注意: 如果是单独声明的箭头函数,最后都必须要加上分号;作为结尾。

 // 下面的test返回值是{null},因为 if (true) {print("success")} 这个语句的返回值是null
 test() => {
     if (true) {print("success")}
 };
 main() {
   print(test().runtimeType);
 }
 
 // 输出
 /*
 _CompactLinkedHashSet<Set<void>>
 */

函数闭包

dart 中的函数闭包和函数作用域的问题和 JavaScript 中的一致,略。

返回值

所有函数都返回一个值。如果没有指定返回值,则语句返回 null。

 foo() {}
 assert(foo() == null);

dart 中的函数声明如果是写在全局没有显式声明返回值类型时会默认当做dynamic处理。

注意:

  • 写在全局的和类中的函数返回值没有类型推断。
  • 写在函数内部(包括 main 函数)的函数有类型推断,不写return返回值是 Null 类型,如果返回条件有很多种会返回 Object 类型。
 typedef bool CALLBACK();
 // 不指定返回类型,此时默认为dynamic,不是bool
 isNoble(int atomicNumber) {
   return _nobleGases[atomicNumber] != null;
 }
 void test(CALLBACK cb) {
   print(cb());
 }
 
 main() {
   /*
   // 返回类型能推断出是boll,写在里面下面的代码不会报错
     isNoble(int atomicNumber) {
       return _nobleGases[atomicNumber] != null;
     }
   */
   // 报错,isNoble不是bool类型
   test(isNoble);
 }