控制语句是任何编程语言都必须掌握的,也就是我们写代码逻辑的时候,都是通过这些控制语句来完成我们的业务逻辑编写。比如循环、条件判断等之类。所以这里也主要是通过两个方面来学习Dart的控制语句吧。
- 循环控制
- 条件控制
循环控制
如果有别的语言基础的话,那应该都知道我们常用的循环控制有for,foreach,while,do...while这些。Dart中也是差不多的。
For
这个算是比较熟悉且最常用的一个循环了。
dart
var callbacks = [];
for (var i = 0; i < 2; i++) {
callbacks.add(() => print(i)); //通过循环的方式往callbacks这个数组里添加匿名函数()=>print(i)
}
这是一个标准的for循环语句。但是学过Java以及kotlin或者JS的人应该都清楚,有时候其实挺烦这种标准方式的,尤其是循环输出一些内容且不需要用到indx下标的时候。那其实Dart也有简化的方式。
dart
for (final c in callbacks) {
c();
}
还有更高级的用法,比如当我们在for循环里循环的元素是一个对象,而我们需要用到的只是其中一个或者其中的部分参数的时候。如:
dart
var people = [Person("Mr.Li", 32, "ShangHai")];//这是一个Person对象
for (final Person(:name, :age) in people) {//当我只需要用到其中的2个属性name和age的时候这种写法更方便很多
print('$name is $age years old'); // 省去了person.name, person.age的繁琐。
}
尤其是在逻辑复杂且这些字段使用频繁的时候,这种方式更能体现出它的优势。 到这里可能你会疑问,Dart中是否支持Foreach。答案是肯定的。如:
dart
var collection = [1, 2, 3];
//标准写法是:
colledtion.forEach((it){
print(it.name);
})
//简单写法,这是之前方法那部分有了解过的。
collection.forEach(print); // 输出1 2 3
While 和do...while
这个其实没什么特别的内容了,和Java语言差不多。
dart
while (!isDone()) {
doSomething();
}
dart
do {
printLine();
} while (!atEndOfPage())
这2个循环的区别就是while是先执行判断条件,满足再进行循环;而do...while则是先执行循环,然后再判断是否要进行下一次循环。
break 和continue
前面我们了解了几个循环的控制语句,和其它的大多数语言都差不多,相信大家也会疑问,如果在这些循环里使用break和continue是不是也一样的?答案是Yes。来几个官方的例子:
dart
while (true) {
if (shutDownRequested()) break;
processIncomingRequests();
}
// do...while
do {
if (shutDownRequested()) break;
processIncomingRequests();
} while (true)
dart
for (int i = 0; i < candidates.length; i++) {
var candidate = candidates[i];
if (candidate.yearsExperience < 5) {
continue;
}
candidate.interview();
}
针对这个for循环的效果,官方还有另一种实现方式:
dart
candidates
.where((c) => c.yearsExperience >= 5)// 过滤出满足这个条件的所有数据。
.forEach((c) => c.interview()); //拿到想要的数据之后再通过forEach的循环去执行。
这里其实是涉及到了集合的操作符的内容了。如果我没理解错的话,第二种方式相对来说性能会比第一种差一点。当然,数据量不大且操作次数不多的话,肯定是差异不明显,毕竟现如今的手机内存、CPU什么的都是强的飞起。但是如果是在一个数据量比较大的且处理逻辑比较复杂,运算量比较大的情况下,我觉得性能应该影响相对会大一点。因为第一种for循环只需要执行1次循环,而第二种方式,首先需要执行第一次循环去筛选搜有满足条件的数据,第二次则是循环把所有满足条件的数据再执行一次。
虽然我们在评估算法的性能的时候,通常都是用O(n)来表示,忽略了O前面的系数。但是真实开发过程中我们很多时候比不能忽略这个问题。尤其是在一些操作复杂且耗时的逻辑中。举个例子:假设O(n) = 0.5ms。按照这个例子中的通过where和forEach的方式则可能需要1ms。因为where需要0.5ms。forEach在最坏可能的情况下也可能需要0.5ms。这里的0.5ms的差距看似短,但如果出现UI切屏或者变化的时候,肉眼是可见的。
这是我对这个官方例子的一些个人看法。
标签 Labels
标签是通过变量名后面加冒号(labelName:)的方式来定义的。需要配合break和continue来使用。
Break labelName; 跳出某个循环至这个label定义的地方
Continue labelName:跳过后续代码执行,直接进入labelName所在的循环的下一轮执行。 早些年有接触过go语言的应该会很熟悉这个。近些年很多其它语言慢慢的其实也开始新增了这个特性。它主要的应用场景有2种。一种是多个嵌套循环;另一种是使用switch的时候。
break labelName
先来看看break labelName几个实用场景:
For
dart
outerLoop:
for (var i = 1; i <= 3; i++) { //外循环
for (var j = 1; j <= 3; j++) { // 内循环
print('i = $i, j = $j');
if (i == 2 && j == 2) {
break outerLoop; //之间跳出外循环,整个这部分循环逻辑就彻底结束了。
}
}
}
print('outerLoop exited');
while
dart
var i = 1;
outerLoop:
while (i <= 3) { //外循环
var j = 1;
while (j <= 3) { //内训还
print('i = $i, j = $j');
if (i == 2 && j == 2) {
break outerLoop;
}
j++;
}
i++;
}
print('outerLoop exited');
do...while
dart
var i = 1;
outerLoop:
do {
var j = 1;
do {
print('i = $i, j = $j');
if (i == 2 && j == 2) {
break outerLoop;
}
j++;
} while (j <= 3);
i++;
} while (i <= 3);
print('outerLoop exited');
以上三种情况的逻辑是一样的,目的也是一样的。输出的结果都是:
i = 1, j = 1 i = 1, j = 2 i = 1, j = 3 i = 2, j = 1 i = 2, j = 2 outerLoop exited
continue labelName
再来看看continue labelName的例子:
For
dart
outerLoop:
for (var i = 1; i <= 3; i++) { //外循环
for (var j = 1; j <= 3; j++) { //内循环
if (i == 2 && j == 2) {
continue outerLoop; //当i为2且j也是2的时候,直接跳过内循环,开始外循环的下一轮。
}
print('i = $i, j = $j');
}
}
while
dart
var i = 1;
outerLoop:
while (i <= 3) { //外循环
var j = 1;
while (j <= 3) { //内循环
if (i == 2 && j == 2) {
i++;
continue outerLoop; //当i为2且j也是2的时候,直接跳过内循环,开始外循环的下一轮。
}
print('i = $i, j = $j');
j++;
}
i++;
}
do...while
dart
var i = 1;
outerLoop:
do { //外循环
var j = 1;
do { //内循环
if (i == 2 && j == 2) {
i++;
continue outerLoop; //当i为2且j也是2的时候,直接跳过内循环,开始外循环的下一轮。
}
print('i = $i, j = $j');
j++;
} while (j <= 3);
i++;
} while (i <= 3);
数据结果都是:
i = 1, j = 1 i = 1, j = 2 i = 1, j = 3 i = 2, j = 1 i = 3, j = 1 i = 3, j = 2 i = 3, j = 3
dart
switch (command) {
case 'OPEN':
executeOpen();
continue newCase; // 执行完了之后,跳到newCase继续执行
case 'DENIED': // 这个case没有内容,会继续下一个case
case 'CLOSED':
executeClosed(); //当case是denied和closed的时候,这行代码都会执行。可以理解为两个case对应一种情况,
newCase: // 跳过来之后可以直接执行
case 'PENDING':
executeNowClosed(); // 当command是'open'的时候,open和pending都会执行。continue label则是一种情况可以执行两种case的代码
}
关于swith这部分,可以先了解一下就行,后面还会重点去讲这个关键字的用法。
以上就是关于循环部分的的一些基础知识。
条件控制语句
这部分主要是了解一下条件控制语句在Dart中的使用。主要是以下三种:
if 这个应该很熟悉,就是通过if判断条件
If - case 这个对我来说有点陌生,不知道你们了解不了解。
Switch,这个其实就是用来应对多个if的场景的情况
if
这个很熟悉,看一下这个官方例子就好了,和其它语言应该是一样的,尤其是Java语言。if(一个bool值或者可以返回bool值的表达式或者方法)。
dart
if (isRaining()) {
you.bringRainCoat();
} else if (isSnowing()) {
you.wearJacket();
} else {
car.putTopDown();
}
if-case
这个对我来说是一种新知识点,后面也会单独去讲解这个东西。可以简单的先了解一下if中也支持这种模式就行。
dart
void main() {
var pair = [1, 2]; // 数组[1,2]
printList(pair);
var pair2 = {2, 3}; // 这里这个pair2就不满足,是一个对象,
printList(pair2);
}
void printList(obj) {// 这是一个没有确定类型的参数
if (obj case [int x, int y]) { //这里的意思就是pair如果是一个包含了2个数字的列表,这该条件就为true,输出这2个数字。
print('$x and $y');
}
}
同样,这个模式一样适用于Switch,一会儿又例子可以展示一下。
Switch
- 这个其实就是if和if-case的多情况下使用的一个方式。
dart
var command = 'OPEN';
switch (command) {
case 'CLOSED':
executeClosed();
case 'PENDING':
executePending();
case 'APPROVED':
executeApproved();
case 'DENIED':
executeDenied();
case 'OPEN':
executeOpen();
default:
executeUnknown();
}
- 在switch中,可以不需要使用。如果你有空的case,但是没有内容需要执行的时候,就需要使用break了。
dart
switch (command) {
case 'OPEN':
executeOpen();
continue newCase;
case 'DENIED':
case 'CLOSED':
executeClosed();
newCase:
case 'PENDING':
executeNowClosed();
}
比如如上这种情况(这个之前的contine labelName中也提到过的例子),当case 'DENIED' 命中的时候,默认会继续向下执行,执行case 'CLOSED'的代码表达式片段。如果你不想这样,那就得在里面case中添加break
dart
case 'DENIED': // Empty case falls through.
break;
case 'CLOSED':
executeClosed(); // Runs for both DENIED and CLOSED,
- Switch中还支持生成内容的表达式
dart
var obj = 1;
var x = switch(obj){
1 => "String",
2 => "Int",
_ => "Default" //这里不能用default
}
print(switch(obj){
1 => "String",
2 => "Int",
_ => "Default" //这里不能用default
});
return switch(obj){
1 => "String",
2 => "Int",
_ => "Default" //这里不能用default
}
这里要注意的是"_"这个符号,它的作用和default其实是一样的,但是这个地方不能用default去替代。
- Switch还支持其它更复杂的表达式:
dart
switch (charCode) {
case slash || star || plus || minus:
token = operator(charCode);
case comma || semicolon:
token = punctuation(charCode);
case >= digit0 && <= digit9:
token = number();
default:
throw FormatException('Invalid');
}
这个其实还可以换种写法
dart
token = switch (charCode) { // 直接让switch返回一个值,然后将这个值赋给token,这样代码是不是有简化了一些。
slash || star || plus || minus => operator(charCode),
comma || semicolon => punctuation(charCode),
>= digit0 && <= digit9 => number(),
_ => throw FormatException('Invalid'),
};
总结一下Switch表达式和switch语句的区别:
- 表达式中是不需要case这个关键字的;但是语句是需要的。
- 表达式只能执行单个表达式;但是语句是可以执行一个代码块的。
- 表达式每一个都必须返回值,不允许空表达式;但是语句是允许空的case。
- 表达式是通过=>来分割条件和表达式;而语句是通过:符号来分割。
- 表达式之间是通过,符号来分割的。
- 表达式的最后的默认只能是""符号。而语句中既可以使用default,也可以使用""符号。
穷尽检查
穷尽检查是一个编译时检查,这个主要是体现在使用switch的时候,编译器能检测到使用的case是一个可以穷举的情况,然后检测到你的case没有满足所有情况,则提示的一个错误信息。如:
dart
var nullableBool = true;
switch (nullableBool) { //nullbaleBool 是一个bool值,要么true,要么false
case true:
print('yes');
}
这段代码是编译不通过的,编译器会提示:The type 'bool' is not exhaustively matched by the switch cases since it doesn't match 'false'。大概意思是这个bool类型没有完全匹配switch的所有case,因为没有匹配到false的情况。
这个其实在使用enums和sealed类型的时候,表现的更明显:
dart
sealed class Shape {}
class Square implements Shape {
final double length;
Square(this.length);
}
class Circle implements Shape {
final double radius;
Circle(this.radius);
}
double calculateArea(Shape shape) => switch (shape) {
Square(length: var l) => l * l,
Circle(radius: var r) => math.pi * r * r,
};
如果候选在基于Shape扩展了一个Triangle类时,这个calculateArea就会报错了。但是,如果你这个Triangle又不想出现在这个方法里,因为不需要计算它的面积大话。那你就可以添加"_"这个符号如:
dart
double calculateArea(Shape shape) => switch (shape) {
Square(length: var l) => l * l,
Circle(radius: var r) => math.pi * r * r,
_ => 0
};
但是在真实开发过程中,不是很推荐使用"_"这个默认情况,因为这样容易导致这个Shape类被扩展了,但是calculateArea方法忘记补充了,从而导致bug之类的。当然这个还是编码规范问题,看具体要求。
保护子句(Guard clasue)
我也确定这个翻译是不是准确的,以前也没接触过这个特性和关键字。先看例子吧:
dart
int number = 5;
switch (number) {
case 5 when number > 0:
print('Positive five');
break;
case 5 when number < 0:
print('Negative five'); // 不会执行,因为 number 是正数
break;
case int n when n.isEven:
print('$n is even');
break;
case int n when n.isOdd:
print('$n is odd');
break;
default:
print('Unknown number');
}
官方给的模版是这样的:
dart
// Switch statement:
switch (something) {
case somePattern when some || boolean || expression:
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Guard clause.
body;
}
// Switch expression:
var value = switch (something) {
somePattern when some || boolean || expression => body,
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Guard clause.
}
// If-case statement:
if (something case somePattern when some || boolean || expression) {
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Guard clause.
body;
}
从这个模板来看,这个在switch语句和switch表达式中都是支持的,而且在if中也是支持的。我感觉这个在权限使用的情况下应该很方便使用,可以完全将业务逻辑和权限分开。case后面是业务逻辑,when后面就是放权限的判断。
关于switch的用法其实还有很多丰富的内容以及情况,这个作为初学者不一定理解,而且这个需要深挖才能了解清楚,所以作为初学Flutter的人来说,可以不用了解这么深,不理解就可以直接跳过就好了。知道一下有这些高级用法,后续可以进阶。