Flutter基础篇(2)-- 老司机用一篇博客带你快速熟悉Dart语法

96
AWeiLoveAndroid 595a1b60 08f6 4beb 998f 2bf55e230555
15.2 2018.05.16 21:04* 字数 5756

版权声明:本文为博主原创文章,未经博主允许不得转载。https://www.jianshu.com/p/3d927a7bf020

转载请标明出处:
https://www.jianshu.com/p/3d927a7bf020
本文出自 AWeiLoveAndroid的博客


Flutter系列博文链接 ↓:

工具安装:

Flutter基础篇:

Flutter进阶篇:

Dart语法系列博文链接 ↓:

【前言】Dart语言是使用flutter框架开发时候必备的语言,flutter是一个跨平台的框架,一套代码就可以完美实现安卓和ios两个平台,适配也很不错,Dart语言很友好,和java很类似,学习成本也是很低的。所以这也是我推荐学习Dart语言的一个原因。

从本篇文章开始讲解Dart语言的基本使用,我将会连续推出好几篇文章详解,希望帮助大家快速掌握Dart语言。


本文目录:

一、注释
二、关键字(56个)
三、变量和常量
四、特殊数据类型
五、运算符
六、控制流程语句
七、异常


本文代码同步发布在Github: https://github.com/AweiLoveAndroid/Flutter-learning/tree/master/projects/dart_demo

怎么运行代码?

如果你使用IDEA或者Android Studio:

打开IDEA或者Android Studio,新建一个Flutter项目,然后在test目录运行我的代码;或者里面去写你自己的dart文件,然后右键run就可以运行了。(注意:需要连接手机或者模拟器)。

如果你使用vscode:

打开vscode,执行菜单栏运行,就可以了(确保只有一个dart文件,免得运行的文件不是你想要的,就很尴尬了,vscode也可以设置默认运行的文件是哪个,但是新手不建议去设置,很麻烦。因为你们想最快的运行效果,所有建议只有一个dart文件是最好的)。


一、注释

Dart的注释分为3种:单行注释、多行注释、文档注释。

1、单行注释以//开头。Dart编译器会忽略//和行尾之间的所有内容。

例如:// todo:待完成

2、多行注释以/*开头,以*/结尾。介于/**/两者之间的内容会被编译器忽略(除非该注释是一个文档注释)。多行注释可以嵌套。

例如:/* todo:待完成 */

3、文档注释以///或者/**开头。可以通过dartdoc命令导出文档。

文档注释的使用,例如:/// todo:待完成

文档的导出如图所示:


文档的导出

导出的结果在我的工程根路径的/doc/api/文件夹里面,如图所示:

导出的结果

然后浏览器打开index.html就可以看到文档了。如图所示:

本地的文档

二、关键字(56个)

33个保留字(不能使用保留字作为标识符)

关键字 - - -
if superdo switch assert
else in this enum
is throw true break
new try case extends
null typedef catch var
class false void const
final rethrow while continue
finally return with for
default - - -

其中内置标志符有:(17个)

关键字 - - -
abstract deferred as dynamic
covariant export external factory
get implements import library
operator part set static
typedef

其中Dart2相对于Dart1新增的,支持异步功能的关键字有:(6个)

关键字 - - -
async async* await sync*
yield yield*

跟java相比,Dart特有的关键字有:(25个)

关键字 - - -
deferred as assert dynamic
sync* async async* in
is await export library
external typedef factory operator
var part const rethrow
covariant set yield get
yield*

三、变量和常量

(一)变量的声明,可以使用 var、Object 或 dynamic 关键字。

创建变量并初始化变量实例:

    var name = '张三' ;

变量存储引用。

    1. 使用Object或dynamic关键字
    dynamic name = '张三';

调用的变量name包含对String值为“张三” 的对象的引用。
name推断变量的类型是String,但可以通过指定它来更改该类型。
如果对象不限于单一类型(没有明确的类型),请使用Object或dynamic关键字

    Object name = '张三';
    dynamic name = '李四';
    1. 显式声明将被推断的类型

比如String,int等。

    //可以使用String显示声明字符串类型
    String name = '张三' ; //代替var name = '张三';

这个类型有很多,具体在下文有介绍。

(二)默认值

未初始化的变量的初始值为null(包括数字),因此数字、字符串都可以调用各种方法。

    //测试 数字类型的初始值是什么?
    int intDefaultValue;
    // assert 是语言内置的断言函数,仅在检查模式下有效
    // 在开发过程中, 除非条件为真,否则会引发异常。(断言失败则程序立刻终止)
    assert(intDefaultValue == null);
    print(intDefaultValue);//打印结果为null,证明数字类型初始化值是null

(三)Final 和 Const的用法

如果您从未打算更改一个变量,那么使用 final 或 const,不是var,也不是一个类型。
一个 final 变量只能被设置一次;const 变量是一个编译时常量。(Const变量是隐式的final。)
final的顶级或类变量在第一次使用时被初始化。

  • 1、被final或者const修饰的变量,变量类型可以省略。
//可以省略String这个类型声明
final name1 = "张三";
//final String name1  = "张三";
    
const name2 = "李四";
//const String name2 = "李四";
  • 2、被 final 或 const 修饰的变量无法再去修改其值。
  final name1 = "张三";
    // 这样写,编译器提示:a final variable, can only be set once
    // 一个final变量,只能被设置一次。
    //name1 = "zhangsan";
    
    const name2 = "李四";

    // 这样写,编译器提示:Constant variables can't be assigned a value
    // const常量不能赋值
    // name2 = "lisi";
  • 3、注意:flnal 或者 const 不能和 var 同时使用,
    //这样写都会报错
    //final var name1 = "张三";
    //const var name2 = "李四";
  • 4、常量如果是类级别的,请使用 static const
    static const speed = 100;
  • 5、常量的运算
    const speed = 100; //速度(km/h)
    const double distance = 2.5 * speed; // 距离 = 速度 * 时间

    final speed2 = 100; //速度(km/h)
    final double distance2 = 2.5 * speed2; // 距离 = 速度 * 时间
  • 6、const关键字不只是声明常数变量。您也可以使用它来创建常量值,以及声明创建常量值的构造函数。 任何变量都可以有一个常量值。
  // 注意: [] 创建的是一个空的list集合
  // const []创建一个空的、不可变的列表(EIL)。
  var varList = const []; // varList 当前是一个EIL
  final finalList = const []; // finalList一直是EIL
  const constList = const []; // constList 是一个编译时常量的EIL

  // 可以更改非final,非const变量的值
  // 即使它曾经具有const值
  varList = ["haha"];

  // 不能更改final变量或const变量的值
  // 这样写,编译器提示:a final variable, can only be set once
  // finalList = ["haha"];
  // 这样写,编译器提示:Constant variables can't be assigned a value  
  // constList = ["haha"];
  • 7、只要任何插值表达式是一个计算结果为null或数字,字符串或布尔值的编译时常量,那么文字字符串就是编译时常量。(关于$表达式和不同的数据类型后面会讲解。)
// 这些是常量字符串
const aConstNum = 0;
const aConstBool = true;
const aConstString = 'a constant string';

// 这些不是常量字符串

var aNum = 0;
var aBool = true;
var aString = 'a string';
const aConstList = const [1, 2, 3];

const validConstString = '$aConstNum $aConstBool $aConstString';

//这样用就会报错:Const variables must be initialized with a constant value
// const常量必须用conat类型的值初始化。
// const invalidConstString = '$aNum $aBool $aString $aConstList';

四、特殊数据类型

Dart支持以下特殊类型:

numbers 数字
strings 字符串
booleans 布尔
lists list集合(也称为数组)
maps map集合
runes 字符(用于在字符串中表示Unicode字符)

(一)num数字类型

num是数字类型的父类,有两个子类 intdouble
num类型包括基本的运算符,如+,-,/和*,位运算符,如>>,在int类中定义。如果num和它的子类没有你要找的东西,math库可能会找到。比如你会发现abs(),ceil()和floor()等方法。

(1)int类型

int表示整数,int默认是64位二进制补码整数,int的取值不大于64位,具体取决于平台。编译为JavaScript时,整数仅限于valus,可以用双精度浮点值精确表示。可用的整数值包括-253和253之间的所有整数,以及一些幅度较大的整数。这包括一些大于2^63的整数。 因此,在编译为JavaScript的Dart VM和Dart代码之间,int类中的运算符和方法的行为有时会有所不同。例如,当编译为JavaScript时,按位运算符将其操作数截断为32位整数。
示例如下:

  int intNum1 = 10 ;
  print(intNum1);//结果是10
  int intNum2 = 0xDEADBEEF ;
  print(intNum2);//结果是3735928559

判断一个int值需要多少bit(位),可以使用bitLength,例如:

  // bitLength 返回存储此int整数所需的最小位数
  int a1 = 1; // 占了1个bit     相当于二进制数字 00000000 00000001
  int a2 = 12; // 占了4个bit    相当于二进制数字 00000000 00001100
  int a3 = 123; // 占了7个bit   相当于二进制数字 00000000 01111011
  int a4 = 1234; // 占了11个bit 相当于二进制数字 00000100 11010010
    print('${a1.bitLength}'); //  1
  print('${a2.bitLength}');  // 4
    print('${a3.bitLength}'); // 7
    print('${a4.bitLength}'); // 11
(2)double类型

Dart的double是IEEE 754标准中规定的64位浮点数。double的最大值是:1.7976931348623157e+308,double类里面有一个常量maxFinite,我们通过语句print(double. maxFinite)可以得到double的最大值
如果一个数字包含一个小数,那么它就是一个double类型。示例如下:

double doubleNum1 = 1.1;
  print(doubleNum1); //结果是1.1
double doubleNum2 = 1.42e5;
print(doubleNum2); //结果是142000.0
(3)Dart2.1里面新增特性,当double的值为int值时,int自动转成double。

例如:double test = 12;//打印结果是12.0

(4)Dart2.1,int也有api转成double。

例如:

   int test = 10;
   print(test.toDouble()); // 结果是: 10.0
(5)Dart2.1,double也有api转成int,会把小数点后面的全部去掉。

例如:

   double test2 = 15.1;
   double test3 = 15.1234;
   print(test2.toInt());// 结果是15
print(test3.toInt());// 结果是15

(二)String字符串类型

Dart里面的String是一系列 UTF-16代码单元。

(1)您可以使用单引号或双引号来创建一个字符串。
String str1 = '单引号基本使用demo.';
String str2 = "双引号基本使用demo.";
(2)单引号或者双引号里面嵌套使用引号。

单引号里面嵌套单引号,或者//双引号里面嵌套双引号,必须在前面加反斜杠。

// 单引号里面有单引号,必须在前面加反斜杠
String str3 = '单引号里面有单引号it\'s,必须在前面加反斜杠.';
//双引号里面嵌套单引号(正常使用)
String str4 = "双引号里面有单引号it's.";
//单引号里面嵌套双引号(正常使用)
String str5 = '单引号里面有双引号,"hello world"';
//双引号里面嵌套双引号,必须在前面加反斜杠
String str6 = "双引号里面有双引号,\"hello world\"";

print(str3);// 双引号里面有单引号it's,必须在前面加反斜杠
print(str4);// 双引号里面有单引号it's.
print(str5);// 单引号里面有双引号,hello world"
print(str6);//双引号里面有双引号,"hello world"
(3)多个字符串相邻中间的空格问题:

除了单引号嵌套单引号或者双引号嵌套双引号不允许出现空串之外,其余的几种情况都是可以运行的。
示例如下:

这个会报错
//String blankStr1 = 'hello''''world';
//这两个运行正常
String blankStr2 = 'hello'' ''world'; //结果: hello world
String blankStr3 = 'hello''_''world'; //结果: hello_world


// 这个会报错
//String blankStr4 = "hello""""world";
这两个运行正常
String blankStr5 = "hello"" ""world"; //结果: hello world
String blankStr6 = "hello""_""world"; //结果: hello_world

单引号里面有双引号,混合使用运行正常

String blankStr7 = 'hello""""world'; //结果: hello""""world
String blankStr8 = 'hello"" ""world'; //结果: hello"" ""world
String blankStr9 = 'hello""_""world'; //结果: hello""_""world

双引号里面有单引号,混合使用运行正常

String blankStr10 = "hello''''world"; //结果: hello''''world
String blankStr11 = "hello'' ''world"; //结果: hello'' ''world
String blankStr12 = "hello''_''world"; //结果: hello''_''world
(4)您可以使用相邻字符串文字或+ 运算符连接字符串:
  1. 直接把相邻字符串写在一起,就可以连接字符串了。
  String connectionStr1 =  '字符串连接''甚至可以在''换行的时候进行。';
  print(connectionStr1);// 字符串连接甚至可以在换行的时候进行。
  1. 用+把相邻字符串连接起来。
  String connectionStr2 =  '字符串连接'+ '甚至可以在'+ '换行的时候进行。';
  print(connectionStr2);// 字符串连接甚至可以在换行的时候进行。
  1. 使用单引号或双引号的三引号:
String connectionStr3 = ''' 
  这是用单引号创建的
  多行字符串。
  ''' ;
print(connectionStr3);
String connectionStr4 = """这是用双引号创建的
  多行字符串。""";
print(connectionStr4);

print(connectionStr3)输出结果如下:

  这是用单引号创建的
  多行字符串。

print(connectionStr4)的输出结果如下:

这是用双引号创建的
  多行字符串。
(5)关于转义符号的使用

声明raw字符串(前缀为r),在字符串前加字符r,或者在\前面再加一个\
可以避免“\”的转义作用,在正则表达式里特别有用。

举例如下:

    print(r"换行符:\n"); //这个结果是 换行符:\n
    print("换行符:\\n"); //这个结果是 换行符:\n
    print("换行符:\n");  //这个结果是 换行符:
(6)使用$可以获得字符串中的内容,使用${表达式}也可以将表达式的值放入字符串中。使用${表达式}时可以使用字符串拼接,也可以使用String类或者Object里面的某些方法获得相关字符串属性。

1、使用$+字符串

var height = 48.0;
print('当前标题的高度是$height'); //当前标题的高度是48.0

2、使用$+字符串,以及字符串拼接

String name = "张三";
print("$name"+"是我们的部门经理"); // 张三是我们的部门经理

3、这里使用了字符串的拼接,以及使用了String类里面的toUpperCase()函数,把字母全部变成大写。

String replaceStr = 'Android Studio';
assert('你知道' +
        '${replaceStr.toUpperCase()}'
          + '最新版本是多少吗?' ==
          '你知道ANDROID STUDIO最新版本是多少吗?'); 

注:==操作符测试两个对象是否相等。assert是断言,如果条件为true,继续进行,否则抛出异常,中端操作。

(三)bool布尔类型

Dart表示布尔值的类型叫做bool,它有两个值,分别是:truefalse,它们都是编译时常量。
Dart使用的是显式的检查值,检查值的类型,如下所示:

  // 检查是否为空字符串
  var emptyStr = '';
  assert(emptyStr.isEmpty);

  // 检查是否小于等于0
  var numberStr = 0;
  assert(numberStr <= 0);  

  // 检查是否为null
  var nullStr;
  assert(nullStr == null);

  // 检查是否为NaN
  var value = 0 / 0;
  assert(value.isNaN);

assert 是Dart语言里的的断言函数,仅在Debug模式下有效
在开发过程中, 除非条件为真,否则会引发异常。(断言失败则程序立刻终止)。

(四)list集合,也成为数组

在Dart中,数组是List对象,因此大多数人只是将它们称为List。
以下是一个简单的Dart的List:

创建一个int类型的list

List list = [10, 7, 23];
print(list);// 输出结果  [10, 7, 23]

要创建一个编译时常量const的list,示例如下:

List constantList = const[10,3,15];
print(constantList);// 输出结果  [10, 3, 15]

注意事项:

1.可以直接打印list包括list的元素,list也是一个对象。但是java必须遍历才能打印list,java若直接打印list,结果是地址值。
2.和java一样list里面的元素必须保持类型一致,不一致就会报错。
3.和java一样list的角标从0开始。

Dart的list集合给我们提供了很多api,示例如下,api太多就不逐个展示了:

操作 代码 含义 输出结果
新增 list.add(1);print(list); 把数字1添加到list中,默认是添加到末尾 [10, 7, 23, 1]
移除 list.remove(1);print(list); 移除数字1 [10, 7, 23]
插入 list.insert(0, 5);print(list); 在索引为0的地方插入数字5 [5, 10, 7, 23]
查找某个索引的值 int value = list.indexOf(10);print(value); 查找10在list中的索引 1
判断元素是否包含 bool result = list.contains(5);print(result); 查找list中是否包含数字5 true

(五)map集合

Dart中的map是将键和值相关联的对象。键和值都可以是任何类型的对象。每个键只出现一次,但您可以多次使用相同的值。

(1)创建方式:
    1. 直接声明,用{}表示,里面写key和value,每组键值对中间用逗号隔开。
Map companys = {'first': '阿里巴巴', 'second': '腾讯', 'fifth': '百度'};
print(companys);//打印结果 {first: 阿里巴巴, second: 腾讯, fifth: 百度}
    1. 先声明,再去赋值。
  Map companys1 = new Map();
  companys1['first'] = '阿里巴巴';
  companys1['second'] = '腾讯';
  companys1['fifth'] = '百度';
  print(companys1);
  //打印结果 {first: 阿里巴巴, second: 腾讯, fifth: 百度}
    1. 要创建一个编译时常量const的map,请在map文字之前添加const:
final fruitConstantMap = const {2: 'apple',10: 'orange',18: 'banana'};
// 打印结果{second: 腾讯, fifth: 百度, 5: 华为}
(2)添加元素。格式: 变量名[key] = value,其中key可以是不同类型。
//添加一个新的元素,key为“5”,value为“华为”
  companys[5] = '华为';
  print(companys);//打印结果 {first: 阿里巴巴, second: 腾讯, fifth: 百度, 5: 华为}
(3)修改元素。格式:变量名[key] = value

例如:把key为first的元素对应的value改成 alibaba

  companys['first'] = 'alibaba';
  print(companys);//打印结果 {first: alibaba, second: 腾讯, fifth: 百度, 5: 华为}
(4)查询元素
  bool mapKey = companys.containsKey('second');
  bool mapValue = companys.containsValue('百度');
  print(mapKey); //结果为:true
  print(mapValue); //结果为:true
(5)删除元素.可以使用map的remove或者clear方法。
  companys.remove('first');// 移除key为“first”的元素。
  print(companys);// 打印结果{second: 腾讯, fifth: 百度, 5: 华为}

  companys.clear();// 清空map集合的数据。
  print(companys);// 打印结果{}
(6)关于map集合的小结:
1.创建map有两种方式。
2.map的key类型不一致也不会报错。
3.添加元素的时候,会按照你添加元素的顺序逐个加入到map里面,哪怕你的key不连续。
比如key分别是 1,2,4,看起来有间隔,事实上添加到map的时候{1:value,2:value,4:value} 这种形式。
4.添加的元素的key如果是map里面某个key的英文,照样可以添加到map里面,
比如可以为3和key为three可以同时存在。
5.map里面的key不能相同,但是value可以相同,value可以为空字符串或者为null。

(六)runes 字符(用于在字符串中表示Unicode字符)

Unicode为世界上所有的书写系统中使用的每个字母,数字和符号定义了唯一的数值。
Dart字符串是UTF-16代码单元的序列,所以在字符串中表达32位Unicode值需要特殊的语法。
Unicode代码点的常用方法是\uXXXX,其中XXXX是一个4位十六进制值。

例如,心形字符()是\u2665。要指定多于或少于4个十六进制数字,请将该值放在大括号中。 例如,笑的表情符号是\u{1f600}

String类有几个属性可以用来提取符文信息。 codeUnitAt和codeUnit属性返回16位代码单元。
以下示例说明了符文,16位代码单元和32位代码点之间的关系。

var clapping = '\u{1f44f}';
print(clapping);
print(clapping.codeUnits);
print(clapping.runes.toList());

//使用String. fromCharCodes显示字符图形 
Runes input = new Runes(
        '\u2665  \u{1f605}  \u{1f60e}  \u{1f47b}  \u{1f596}  \u{1f44d}');
print(new String.fromCharCodes(input));

五、运算符

运算符在每一种语言中都很常见,Dart的运算符如下表所示:

我这里不详细去讲解每个运算符的用法,我这里主要讲一下Dart里面比较有代表性的以及有特点的一些运算符相关用法。

(一)?..一样,但最左边的操作数可以为空。

比如:Test?.funs从表达式Test中选择属性funs,除非Test为空(当Test为空时,Test?.funs的值为空)。

class Test {
  static int funs = 5;

  Test() {
    print('构造函数 Test');
  }
  static fun() {
    print('Test fun函数');
  }
}
void main(){
  print(Test?.funs); // 打印5
}

(二)..级联符号..

级联符号..允许您在同一个对象上进行一系列操作。 除了函数调用之外,还可以访问同一对象上的字段。其实相当于java的链式调用。
例如:

String s = (new StringBuffer()
        ..write('test1 ')
        ..write('test2 ')
        ..write('test3 ')
        ..write('test4 ')
        ..write('test5'))
      .toString();
print(s); // test1 test2 test3 test4 test5

(三)?? 三目运算符的一种形式。

expr1 ?? expr2 表示如果expr1非空,则返回其值; 否则返回expr2的值。

//普通三元运算符
int a = 10;
var values = a > 5 ? a : 0;
//??操作符
print('a ??=3 : ${a ??= 3}');  // 3

(四)~/ 除法,返回一个整数结果,其实就是取商。

小学都学过:被除数 ÷ 除数 = 商 ... 余数,在Dart里面A ~/ B = C,这个C就是商,这个语句相当于Java里面的A / B = C。Dart与java不同的是,Dart里面如果使用A / B = D语句,这个结果计算出来的是真实的结果。示例如下:

  var result1 = 15/7;
  print(result1); // 结果是:2.142857...
  var result2 = 15~/7;
  print(result2); // 结果是:2

顺便提一下取模操作,在Dart里面A % B = E,这个E就是余数,%符号表示取模,例如:

 var result3 = 15%7;
  print(result3); // 结果是:1

(五)as、is与is!

as 判断属于某种类型
is 如果对象具有指定的类型,则为true
is! 如果对象具有指定的类型,则为false

例如:

class Test {
  static int funs = 5;

  Test() {
    print('构造函数 Test');
  }
  static fun() {
    print('Test fun函数');
  }
}

class Test2 extends Test {
  Test2() {
    print('构造函数 Test2');
  }
  void fun() {
    print('Test2 fun函数');
  }
}

void main(){
  print(test2 is Test);  // true
  print(test is! Test2);  // true

  (test2 as Test2).fun();  // Test2 fun函数
  // 相当于
  // if (test2 is Test) {
  //   test2.fun();
  // }

六、控制流程语句

控制流程语句和Java语言差不多,有这些语句:

(一)if else

if(条件语句){
    内容体
}else{
内容体
}

(二)for循环

1.简单for循环

for(初始值;判断条件;循环后的语句){
    内容体
}

例如:

for(int i=0;i<10;i++){
    print(i);
}

也可以通过for循环内部的闭包获取索引的值。

var array = [];
for(var i=0;i<10;i++){
    array.add(()=> print(i));
}

2.使用foreach循环,一般List和Set都可以使用foreach遍历元素。

如果要迭代的对象是Iterable,或者你不想知道当前的迭代次数,可以使用foreach()方法。

var numbers = [1,2,3,4,5,6,7,8,9];
numbers.foreach((number)=> print(number));

3.使用for in循环,一般List和Set使用for-in遍历元素。

var list = [1,2,3];
for(var data in list){
    print(data);
}

4.Dart的for循环里面可以使用标记:(比较有特色的地方)

Dart的标记:标记是后面跟着冒号的标识符。带标记的陈述是以标记 L为前缀的陈述。带标签的case子句是标签L前缀的switch语句中的case子句。标签的唯一作用是为“break”和“continue”声明提供对象。
大多数此功能与其他语言类似,因此以下大部分内容可能对读者来说都很熟悉。Dart的switch声明中处理continue是比较独特的,所以这一部分需要一点时间去阅读和熟悉。

  • 1.循环(Loops)

标签最常用作breakcontinue内部循环。假设你有嵌套的循环,并要跳转到breakcontinue到外部循环。如果没有标记,这不可能(轻松)实现。

以下示例使用continue 标记名称从内部循环直接跳转到外部循环的下一轮循环:

// 返回具有最小总和的内部列表(正整数)。
/// Returns the inner list (of positive integers) with the smallest sum.
List<int> smallestSumList(List<List<int>> lists) {
  var smallestSum = 0xFFFFFFFF; //已知list的总和较小。
  var smallestList = null;
  outer: // 这就是标记
  for (var innerList in lists) {
    var sum = 0;
    for (var element in innerList) {
      assert(element >= 0);
      sum += element;
      // 无需继续迭代内部列表。它的总和已经太高了。
      if (sum > smallestSum) continue outer; // continue 跳出到标记处(outer)
    }
    smallestSum = sum;
    smallestList = innerList;
  }
  return smallestList;
}

此函数在所有list中运行,但只要总和过高,就会停止累加变量。

同理,可以使用break跳出到外部循环:

// 计算第一个非空list
List<int> firstListWithNullValueList(List<List<int>> lists) {
  var firstListWithNullValues = null;
  outer:
  for (var innerList in lists) {
    for (var element in innerList) {
      if (element == null) {
        firstListWithNullValues = innerList;
        break outer;  // break 返回到标记处
      }
    }
  }
  // 现在继续正常的工作流程
  if (firstListWithNullValues != null) {
    // ...
  }
  return firstListWithNullValues;
}
  • 2.跳出代码块

标记也可以用于跳出代码块。假设我们想要统一处理错误条件,但有多个条件(可能是深度嵌套)来揭示(reveal)错误。标签可以帮助构建此代码。

void doSomethingWithA(A a) {
  errorChecks: {
    if (a.hasEntries) {
      for (var entry in a.entries) {
        if (entry is Bad) break errorChecks;   // 跳出代码块
      }
    }
    if (a.hasB) {
      var b = new A.b();
      if (b.inSomeBadState()) break errorChecks;  // 跳出代码块
    }
    // 一些看起来都正常
    use(a);
    return;
  }
  // 错误的情况,执行这里的代码:
  print("something bad happened");
}

class A{
  bool hasEntries = false;
  bool hasB = true;
  List<Bad> entries = [new Bad2(),new Bad2()];
  A.b(){

  }

  bool inSomeBadState(){
    return false;
  }
  
}

void use(A a){}

abstract class Bad{}
class Bad1 extends Bad{}
class Bad2 extends Bad{}

对代码块的使用break指令,使得Dart继续执行块之后的语句。从某个角度来看,它是一个结构化的goto,它只允许跳转到当前指令之后的嵌套较少的位置。

虽然声明标签在代码块中最有用,但它们可以用在在每个语句中。
例如,foo: break foo;是一个有效的声明。

请注意:continue上面的循环可以通过将循环体包装到带标记的代码块中并使用break来实现。
也就是说,以下两个循环是等效的:

//以下两种描述是等价的:

// 使用 continue
for (int i = 0; i < 10; i++) {
  if (i.isEven) continue;
  print(i);
}

// 使用 break.
for (int i = 0; i < 10; i++) {
  labels: {
    // isEven 当且仅当该整数为偶数时才返回true
    if (i.isEven) break labels;
    print(i);
  }
}
  • 3.Switch中的标记(label)

标记也可以用于switch内部。
Switch中的标记允许continue 使用其它的case 子句。在最简单的形式中,这可以作为一种方式来实现下一个子句:

void switchExample(int foo) {
  switch (foo) {
    case 0:
      print("foo is 0");
      break;
    case 1:
      print("foo is 1");
      continue shared; // Continue使用在被标记为shared的子句中
    shared:
    case 2:
      print("foo is either 1 or 2");
      break;
  }
}

有趣的是, Dart没有要求continue的目标子句是当前case子句之后的子句。
带标记的任何case子句都是有效的目标。这意味着,Dart的switch语句实际上是状态机(state machines)。

以下示例演示了这种滥用,其中整个switch实际上只是用作状态机(state machines)。

void main() {
  runDog();
}

void runDog() {
  int age = 0;
  int hungry = 0;
  int tired = 0;

  bool seesSquirrel() => new Random().nextDouble() < 0.1;
  bool seesMailman() => new Random().nextDouble() < 0.1;

  switch (1) {
    start:
    case 0:
      print("dog 方法已经开始");
      print('case 0 ==> age: $age');
      print('case 0 ==> hungry: $hungry');
      print('case 0 ==> tired: $tired');
      continue doDogThings;

    sleep:
    case 1:
      print("sleeping");
      tired = 0;
      age++;
      if (age > 20) break;
      print('case 1 ==> age: $age');
      print('case 1 ==> hungry: $hungry');
      print('case 1 ==> tired: $tired');
      continue doDogThings;

    doDogThings:
    case 2:  
      if (hungry > 2) continue eat;
      if (tired > 3) continue sleep;
      if (seesSquirrel()) continue chase;
      if (seesMailman()) continue bark;
      print('case 2 ==> age: $age');
      print('case 2 ==> hungry: $hungry');
      print('case 2 ==> tired: $tired');
      continue play;

    chase:
    case 3:  
      print("chasing");
      hungry++;
      tired++;
      print('case 3 ==> age: $age');
      print('case 3 ==> hungry: $hungry');
      print('case 3 ==> tired: $tired');
      continue doDogThings;

    eat:
    case 4:  
      print("eating");
      hungry = 0;
      print('case 4 ==> age: $age');
      print('case 4 ==> hungry: $hungry');
      print('case 4 ==> tired: $tired');
      continue doDogThings;

    bark:
    case 5: 
      print("barking");
      tired++;
      print('case 5 ==> age: $age');
      print('case 5 ==> hungry: $hungry');
      print('case 5 ==> tired: $tired');
      continue doDogThings;

    play:
    case 6: 
      print("playing");
      tired++;
      hungry++;
      print('case 6 ==> age: $age');
      print('case 6 ==> hungry: $hungry');
      print('case 6 ==> tired: $tired');
      continue doDogThings;
  }
}

这个函数从一个switch子句跳到另一个子句,模拟了狗的生命。
在Dart中,标签只允许在case子句中使用,因此我必须添加一些case永远不会到达的行。

这个功能很酷,但很少使用。由于我们的编译器增加了复杂性,我们经常讨论它的删除。到目前为止,它已经在我们的审查中幸存下来,但我们最终可能会简化我们的规范并让用户自己添加一个while(true)循环(带有标记)。这个dog的示例可以重写如下:

var state = 0;
loop:
while (true)
  switch (state) {
    case 0:
      print("dog has started");
      state = 2; continue;

    case 1:  // sleep.
      print("sleeping");
      tired = 0;
      age++;
      // The inevitable... :(
      if (age > 20) break loop;  // 跳出循环
      // Wake up and do dog things.
      state = 2; continue;
    
    case 2:  // doDogThings.
      if (hungry > 2) { state = 4; continue; }
      if (tired > 3) { state = 1; continue; }
      if (seesSquirrel()) { state = 3; continue; }
      ...

如果状态值被命名为常量,那么它将与原始版本一样具有可读性,但不需要switch语句来支持状态机。

(三)while 和do while

while(判断条件){
    内容体
}
do{
内容体
} while(判断条件);
while(a>5){
    print(a);
}
do{
print(a);
} while(a>5);

(四)break continue

break 停止循环

while(a>5){
  if(b>5){
  print(a);
    break;
  }
}

continue 跳到下一个循环

while(a>5){
  if(b<10){
  print(b);
    continue;
  }
}

如果使用Iterable(list或者set),则可以使用下面这种方式:

// 第一个是满足条件就进入  第二个是foreach遍历
arrays
  .when((c)=>c.counts >=5)
  .foreach((c)=>c.next());

(五)switch case

比较integer, string,编译时常量 使用==。比较对象必须是同一个类的实例(不是其子类的实例),并且该类不能重写==。枚举类型在switch也可以运行。
每一条非空case字子句以break结束,也可以使用其他的方式结束:continue,throw或者return

var command = 'OPEN';
switch (command) {
  case 'CLOSED':
    executeClosed();
    break;
  case 'PENDING':
    executePending();
    break;
  case 'APPROVED':
    executeApproved();
    break;
  case 'DENIED':
    executeDenied();
    break;
  case 'OPEN':
    executeOpen();
    break;
  default:
    executeUnknown();
}

(六)assert

如果布尔条件为false,则使用assert语句来中断正常执行。例如:

// 确保变量具有非空值 
assert(text != null);
// 确保值小于100
assert(number < 100);
// 确保这是一个 https 网址
assert(urlString.startsWith('https'));

要将消息附加到断言,请添加一个字符串作为第二个参数。

assert(urlString.startsWith('https'),'URL ($urlString) should start with "https".');

上例中assert的第一个参数可以是任何解析为布尔值的表达式。如果表达式的值为true,则断言成功并继续执行。如果为false,则断言失败并抛出异常


七、异常

Dart代码可以抛出并捕获异常。Exception是指示发生意外事件的错误。如果未捕获异常,则会暂停引发异常的isolate ,并且通常会终止isolate及其程序。

与Java相比,Dart的所有异常都是未经检查的异常。方法不会声明它们可能引发的异常,并且您不需要捕获任何异常。

Dart提供了ExceptionError 类型,以及许多预定义的子类型。当然,您可以定义自己的Exception。但是,Dart程序可以抛出任何非null对象,作为Exception(不仅仅是Exception和Error对象)。

(一)throw

以下是抛出或引发异常的示例:

throw FormatException('Expected at least 1 section');

你也可以抛出任意对象,例如:throw '格式不正确!';
通常在开发中会抛出Error或者Exception类型。

因为抛出异常是一个表达式,所以可以在=>语句中以及允许表达式的任何其他地方抛出异常:

void distanceTo(Point other) => throw UnimplementedError();   

(二)try catch

捕获或捕获异常会阻止异常传递(除非您重新抛出异常)。捕获异常使您有机会处理它:

try {
    breedMoreLlamas();
} on OutOfLlamasException {
    buyMoreLlamas();
}

要处理可能抛出多种类型异常的代码,可以指定多个catch子句。与抛出对象的类型匹配的第一个catch子句处理异常。如果catch子句未指定类型,则该子句可以处理任何类型的抛出对象。
您可以使用on或catch两者兼而有之。使用on时需要指定异常类型。使用catch时,你的异常处理程序需要异常对象。
示例:

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // A specific exception
  buyMoreLlamas();
} on Exception catch (e) {
  // Anything else that is an exception
  print('Unknown exception: $e');
} catch (e) {
  // No specified type, handles all
  print('Something really unknown: $e');
}

您可以指定一个或两个参数catch()。第一个是抛出的异常,第二个是堆栈跟踪(StackTrace对象)。
示例:

try {
  // ···
} on Exception catch (e) {
  print('Exception details:\n $e');
} catch (e, s) {
  print('Exception details:\n $e');
  print('Stack trace:\n $s');
}

要部分处理异常,同时允许它传递,请使用rethrow关键字。
示例:

void misbehave() {
  try {
    dynamic foo = true;
    print(foo++); // 运行时异常
  } catch (e) {
    print('misbehave() partially handled ${e.runtimeType}.');
    rethrow; // 允许调用者查看exception.
  }
}

void main() {
  try {
    misbehave();
  } catch (e) {
    print('main() finished handling ${e.runtimeType}.');
  }
}

(三)finally

无论是否抛出异常,要确保某些代码运行,请使用finally子句。如果没有catch子句匹配该异常,则在finally子句运行后传递异常。
示例:

try {
  breedMoreLlamas();
} finally {
  // 即使抛出异常  也会执行这句代码.
  cleanLlamaStalls();
}
该finally子句在任何匹配的catch子句之后运行:
try {
  breedMoreLlamas();
} catch (e) {
    // 首先会处理异常
  print('Error: $e'); 
} finally {
  // 然后执行这句语句
  cleanLlamaStalls(); 
}
Flutter&Dart
Web note ad 1