本文收录于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中迭代器中用法。对于上述代码需要注意以下几点点:
-
迭代类必须要提供一个迭代器(本例中即PersonIterator),这个迭代器同时需要实现两个方法,一个是表示当前元素current的getter方法(即get current => lists[i];),另一个则是寻找下一个元素并确认是否还有剩余元素的moveNext方法。
-
对于迭代器的实现须遵循以下步骤:首先,迭代器的默认指向位置位于第一个元素之前,所以我们定义了var index = -1,表示初始化索引为第一个元素的前一个元素(其实就是不存在)。其次,moveNext方法的含义是,首先寻找下一个元素,并返回是否还有下一个元素的判断,如果有则返回true,否则返回false。所以,这里我们先对moveNext进行了index++运算,然后返回是否还存在该元素的判断。最后,current的getter方法返回的就是当前元素值,所以我们只需简单返回即可。
-
最后,我们需要实现抽象类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还可以接收第二个参数:即错误描述信息,用于在断言不符合需求的情况下,提示给用户的描述性文案。这样可以很明了的知道发生了什么错误。
网友评论