dart入门潜修

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

2019-02-18  本文已影响0人  寒潇2018

本文收录于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还可以接收第二个参数:即错误描述信息,用于在断言不符合需求的情况下,提示给用户的描述性文案。这样可以很明了的知道发生了什么错误。

上一篇下一篇

猜你喜欢

热点阅读