美文网首页dart入门潜修
dart入门潜修基础篇之控制流语句

dart入门潜修基础篇之控制流语句

作者: 寒潇2018 | 来源:发表于2019-02-18 19:52 被阅读0次

    本文收录于dart入门潜修系列教程

    创作不易,转载还请备注。

    控制流语句

    所谓控制流语句就是能够改变程序执行流程的语句,比如if else、switch、for、assert语句等等,dart为我们提供了和其他语言一样丰富的控制流语法,罗列如下:
    —if else: 条件判断语句
    —for循环:循环遍历语句
    —while 和 do-while语句: 循环遍历语句
    —break:跳出当前循环体
    —continue:跳过循环中的本次执行
    —switch 和 case: 条件匹配语句
    —assert:断言
    下面一一来看一下上面的语句的用法。

    if-else

    dart中的if-else写法同其他语言一致,但是需要注意的是,只有boolean值才能作为if-else中的条件!if-else示例如下:

    void main() {
      int i = 1;
      if (i > 0) {
        print("i > 0");
      } else if (i < 0) {
        print("i < 0");
      } else {
        print("i == 0");
      }
    }
    

    我们无法像下面这样使用if-else语句:

    void main() {
      int i = 0;
      if (!i) {//这里i不是boolean值,仅仅是个对象,所以不能这么使用
        print(i);
      }
    }
    

    for

    for是用于迭代遍历的语句,典型的场景是用于遍历lists。示例如下:

      var lists = [1, 2, 3];
      for (int i = 0; i < lists.length; i++) {
        print(i);//打印'1 2 3'
      }
    

    注意,for循环中的变量i的作用域就只在for循环体内部可以访问,无法像javascript那样可以在外部访问,也就是说dart严格限制了作用域,这有助于避免一些未知错误。

    dart还提供了forEach方法,该方法可以用于遍历具有迭代属性的对象,来看个例子:

    class Person {//声明了一个Person类
      String name;
    }
    //测试方法
    void main() {
    //生成3个Person对象
      Person p1 = Person();
      Person p2 = Person();
      Person p3 = Person();
    
      p1.name="张三";
      p2.name="李四";
      p3.name="王五";
    //列表显然具备迭代属性
      var lists = [p1, p2, p3];
    //这里我们采用forEach方法遍历lists中的元素,并打印name值
      lists.forEach((person) => print(person.name));//打印 '张三 李四 王五'
    }
    

    此外,对于可迭代的对象,dart还提供了“增加for循环”for-in,如下所示:

      var lists = [1, 2, 3];//lists是一个可迭代的对象
      for(var item in lists){//可以用for in循环进行遍历
        print(item);//打印'1 2 3'
      }
    

    上面多次提到可迭代的对象,那么什么是可迭代的对象?在dart中,可迭代对象就是指实现了Iterable类,并提供Iterator迭代器的对象。下面我们来阐述下如何定义自己的可迭代对象。由于这些涉及到面向对象部分的知识,如果看不太明白,可以暂时略过。

    import 'dart:collection';
    //定义了一个Person类,这个是我们要遍历的元素类型
    class Person {
      String name;
      Person(String name){
        this.name = name;
      }
    }
    //我们需要提供一个迭代器,用于遍历Person,我们必须实现该接口中定义的“抽象元素”
    class PersonIterator implements Iterator<Person> {
    //简单起见,这里并没有提供对外设置入口,暂时写死
      var lists = [Person("张三"), Person("李四")];
    //元素其实索引
      var index = -1;
    //必须实现Iterator接口中的current成员的getter方法
    //用于返回当前遍历的元素对象
      get current => lists[index];
    //必须实现moveNext方法
      bool moveNext() {
        index++;
        return index < lists.length;
      }
    }
    //实现IterableBase,IterableBase实现了Iterable抽象类
    class Persons extends IterableBase<Person> {
    //这里我们需要提供一个迭代器,for-in遍历的时候会获取该迭代器
      @override
      final Iterator<Person> iterator = PersonIterator();
    }
    //测试方法
    void main() {
    //我们可以使用for-in来遍历Persons类型的对象。
      for(var person in Persons()){
        print(person.name);//打印'张三 李四'
      }
    }
    

    上面简单演示了如何自定义一个具有迭代属性的对象,同时也演示了dart中迭代器中用法。对于上述代码需要注意以下几点点:

    1. 迭代类必须要提供一个迭代器(本例中即PersonIterator),这个迭代器同时需要实现两个方法,一个是表示当前元素current的getter方法(即get current => lists[i];),另一个则是寻找下一个元素并确认是否还有剩余元素的moveNext方法。

    2. 对于迭代器的实现须遵循以下步骤:首先,迭代器的默认指向位置位于第一个元素之前,所以我们定义了var index = -1,表示初始化索引为第一个元素的前一个元素(其实就是不存在)。其次,moveNext方法的含义是,首先寻找下一个元素,并返回是否还有下一个元素的判断,如果有则返回true,否则返回false。所以,这里我们先对moveNext进行了index++运算,然后返回是否还存在该元素的判断。最后,current的getter方法返回的就是当前元素值,所以我们只需简单返回即可。

    3. 最后,我们需要实现抽象类Iterable,这个类包含了forEach方法,可用于forEach遍历。这里我们采用的是继承IterableBase的方式,因为IterableBase本身继承了自Iterable类。

    当然,这个迭代器只是演示案例,没有任何扩展性,如果想实现类似于系统库list那样具有高扩展性、健壮性的迭代机制,还需要做一些工作。这里只需明白其原理即可。

    while和do-while

    这两条也是存在于众多语言中的遍历语句,while是先判断条件再决定是否执行循环体,而do-while则是先执行一次循环体,然后结合条件判断决定是否继续执行下一次循环体,二者示例如下:

    //定义了一个整型变量i,其值为2
      int i = 2;
    //while循环遍历,这里显然i不小于2,所以while
    //循环体根本不会执行
      while (i < 2) {
        print(i++);
      }
    //do-while循环,因为先执行一次while循环体,
    //然后再判断i是否小于2,所以会打印 2 
      do {
        print(i++);
      } while (i < 2);
    

    break和continue

    break和continue都是用于改变代码执行流程的语句,break用于跳出当前循环体,而continue则是跳过循环体的当前执行,继续执行下一次循环,示例如下:

      var lists = [1, 3, 2, 3];//定义了一个lists
    //break示例,这里当遇到lists中的偶数元素即停止打印
      for (int i = 0; i < lists.length; i++) {
        if (lists[i] % 2 == 0) {
          break;
        }
        print(lists[i]);//打印'1 3'
      }
    //continue示例,这里的意思是跳过lists中的偶数元素
      for (int i = 0; i < lists.length; i++) {
        if (lists[i] % 2 == 0) {
          continue;
        }
        print(lists[i]);//打印'1 3 3'
    

    当然对于可迭代对象,我们还可以利用其提供的便利方法来达到上面代码的目的,如下所示:

    //定义一个Person类
    class Person {
      int age;
      String name;
      Person(String name, int age) {
        this.name = name;
        this.age = age;
      }
    }
    //测试方法main
    void main() {
    //包含有年龄不同的三个对象
      var lists = [Person("张三", 20), Person("李四", 21), Person("王五", 22)];
    //我们可以使用where方法,进行条件判断,
    //这里我们过滤掉 age<= 21岁的Person
      lists.where((person) => person.age > 21)
          .forEach((person) => print(person.name));//打印'王五'
    }
    

    上面代码用到了where方法,where的方法定义如下所示;

    //where方法的定义
      Iterable<E> where(bool test(E element)) => new WhereIterable<E>(this, test);
    //WhereIterable的定义
      WhereIterable(this._iterable, this._f);
    

    由此可见,where实际上是结合了外部的判断条件以及内部的迭代器完成了条件过滤。

    在dart内置的list中,where是以mixin的方式提供的(后面会有文章阐述mixin机制),位于ListMixin中,而ListMixin最终实现了可迭代的抽象类Iterable,在该类中定义了where方法,所以任何实现迭代功能的对象,都可以使用该方法进行条件过滤。比如我们同样可以使用where方法来顾虑我们自己定义的可迭代对象,如下所示:

    //Person类
      class Person {
      int age;
      String name;
      Person(String name, int age) {
        this.name = name;
        this.age = age;
      }
    }
    //我们自己实现的迭代器,用于迭代Persons对象
    class PersonIterator implements Iterator<Person> {
      var lists = [Person("张三", 22), Person("李四", 20), Person("王五", 22)];
      var index = -1;
      get current => lists[index];
    
      bool moveNext() {
        index++;
        return index < lists.length;
      }
    }
    //可迭代对象类Persons,
    class Persons extends IterableBase<Person> {
      @override
      final Iterator<Person> iterator = PersonIterator();
    }
    //测试方法
    void main() {
      var persons = Persons();
      persons.where((person) => (person.age > 21))
          .forEach((person) => print(person.name));//打印' 张三 王五'
    }
    

    switch和case

    dart中的switch-case机制同其他语言一样,都是作为条件匹配存在的语句,不过dart对switch-case做了一些健壮性控制,先来看一个最基本的switch-case使用案例:

    void main() {
    //假设我们默认有三种颜色,red、yellow、green
      String color = "red";//可以通过改变此值来观察不同的打印结果
    //现在我们想根据不同的颜色做不同的事情
    //就可以使用switch-case语句,
      switch (color) {
        case "red":
          print("red");
          break;
        case "yellow":
          print("yellow");
          break;
        default:
          print("green");
      }
    }
    

    在使用switch-case的时候,必须要使用break及时终止剩余代码的执行,在有些语言中,如果不指定break,会继续执行下一个case语句,直到遇到break或者default为止,而dart对其进行了控制,如下所示:

    void main() {
      String color = "red";
      switch (color) {
        case "red":
          print("red");//!!!注意这里,我们没有显示使用break,dart编译器会报错!!!
        case "yellow":
          print("yellow");
          break;
        default:
          print("green");
      }
    }
    

    但是,如果多个case确实对应了同样的执行逻辑,该怎么办?

    很简单,dart很人性化的给我们考虑到了这种情况,只要case分支中,没有具体的执行逻辑,那么dart是允许你共用同一个case逻辑 的,比如下面的代码:

    void main() {
      String color = "red";
      switch (color) {
        case "red"://注意这里,我们没有任何实现
        case "yellow":
          print("red or yellow");
          break;
        default:
          print("green");
      }
    }
    

    上面代码执行过后,会打印red or yellow,这就是dart对switch-case所做的健壮处理。

    但是,可能又有朋友说了,我现在的需求确实是要执行两个不同case下的不同逻辑!那么,此时该怎么做?

    其实这个需求很怪异,因为switch-case的本意表达更多的是“单条件”匹配,而现在这种情况意思就是要多条件匹配,然而dart针对这种情况还是提供了一种机制,即结合continue来做,示例如下:

    void main() {
      String color = "red";//注意这里的匹配条件是red
      switch (color) {
        case "red"://匹配,按道理可以结束了
          print("red");
          continue anotherColor;//但是这里却有个continue语句,指向了anotherColor标签
        anotherColor://anotherColor标签实际上执行了case "yellow"这个匹配case
        case "yellow":
          print("yellow");
          break;
        default:
          print("green");
      }
    }
    

    上面代码执行完后,打印如下:

    red
    yellow
    

    这个也是dart语言中关于switch-case的一个特性。

    上面阐述了dart中switch-case的各种判断场景,那么switch的入参类型都可以是哪些?

    dart中的swith支持多种常见的入参类型,比如整型、字符串、编译时常量、枚举等等。switch语句在进行匹配的时候,会调用了相应的==操作符来进行匹配比较,当然,前提是比较的两个对象必须具备同一个类型(连子类型都不行)。

    assert

    assert即断言,是很多语言都提供的一种检测机制,它是用来判断是否符合执行逻辑的语句,示例如下:

    void main() {
      String str = null;
      assert(str != null);
      print(str.length);
    }
    

    上面代码会直接中断执行流程,抛出一个断言异常,因为str != null为非真值。如果此时str不为null,则程序会正常执行。

    我们会发现,很多时候在生产的代码中都写有大量的断言,这个在不符合断言条件的时候显然也会抛出异常,那为什么还要这么写?实际上,断言默认只在开发环境中生效,发布到生产环境中的断言是会被忽略的。因此,断言实际上起到了在开发环境调试程序、测试程序的目的。

    可能会有朋友说,上面的代码我不用assert不照样抛出异常吗?确实,不用的话也会抛出空指针这种异常!但是我们还是需要尝试从两个角度来理解断言:1. 空指针异常实际上是被动抛出的,是程序执行过程中报出的错误,更多的是给程序员看的;而断言则更多的是用户侧主动定义的行为,除了程序员,测试开发人员也可以利用断言机制来来测试系统,断言可以清楚的给出我们异常的原因是什么。2. 上面只是一个演示例子,实际上断言处理的更多的是逻辑层面上的测试,这个也是最重要的一点。比如下面代码:

    //这里提供一个极其简单的元转分的实现(注意这里并没有考虑溢出问题)
    //仅仅是为了说明assert的用法
    int yuan2fen(int yuan) {
    //这里的入参yuan显然可能为负数,但这个不符合现实逻辑
    //所以我们进行了一层断言,保证金额>0;
      assert(yuan >= 0, "金额错误!");
      return yuan * 100;
    }
    //测试方法
    void main() {
      print(yuan2fen(100));//正确!打印'10000'
      print(yuan2fen(-1));//错误!抛出异常'Failed assertion: line 15 pos 10: 'yuan >= 0': 金额错误!'
    }
    

    上面代码演示了断言的一种应用场景,如果没有断言,当我们输入的数字为负数时,程序不会抛出任何异常,进而会产生错误的金额值。加上断言后,则在开发、测试期间就能发现类似的错误。

    由上述代码可知,assert还可以接收第二个参数:即错误描述信息,用于在断言不符合需求的情况下,提示给用户的描述性文案。这样可以很明了的知道发生了什么错误。

    相关文章

      网友评论

        本文标题:dart入门潜修基础篇之控制流语句

        本文链接:https://www.haomeiwen.com/subject/sxvtkqtx.html