[Flutter学习之Dart基础] - 控制语句

控制语句是任何编程语言都必须掌握的,也就是我们写代码逻辑的时候,都是通过这些控制语句来完成我们的业务逻辑编写。比如循环、条件判断等之类。所以这里也主要是通过两个方面来学习Dart的控制语句吧。

  1. 循环控制
  2. 条件控制
循环控制

如果有别的语言基础的话,那应该都知道我们常用的循环控制有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
  1. 这个其实就是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();
}
  1. 在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,
  1. 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去替代。

  1. 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语句的区别:

  1. 表达式中是不需要case这个关键字的;但是语句是需要的。
  2. 表达式只能执行单个表达式;但是语句是可以执行一个代码块的。
  3. 表达式每一个都必须返回值,不允许空表达式;但是语句是允许空的case。
  4. 表达式是通过=>来分割条件和表达式;而语句是通过:符号来分割。
  5. 表达式之间是通过,符号来分割的。
  6. 表达式的最后的默认只能是""符号。而语句中既可以使用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的人来说,可以不用了解这么深,不理解就可以直接跳过就好了。知道一下有这些高级用法,后续可以进阶。

相关推荐
QING61840 分钟前
详解:Kotlin 类的继承与方法重载
android·kotlin·app
QING61841 分钟前
Kotlin 伴生对象(Companion Object)详解 —— 使用指南
android·kotlin·app
一一Null1 小时前
Android studio 动态布局
android·java·android studio
AD钙奶-lalala8 小时前
某车企面试备忘
android
我爱拉臭臭9 小时前
kotlin音乐app之自定义点击缩放组件Shrink Layout
android·java·kotlin
匹马夕阳10 小时前
(二十五)安卓开发一个完整的登录页面-支持密码登录和手机验证码登录
android·智能手机
吃饭了呀呀呀10 小时前
🐳 深度解析:Android 下拉选择控件优化方案——NiceSpinner 实践指南
android·java
吃饭了呀呀呀11 小时前
🐳 《Android》 安卓开发教程 - 三级地区联动
android·java·后端
_祝你今天愉快12 小时前
深入剖析Java中ThreadLocal原理
android
张力尹13 小时前
谈谈 kotlin 和 java 中的锁!你是不是在协程中使用 synchronized?
android