跨平台技术篇 - Dart 语法全解析 (上)

学习 Flutter,必须得掌握 Dart 语言,这篇文章就来整理一下 Dart 的语法,由于内容较多,所以分成上下两篇。

 

目录:

  1. Dart 简介
  2. Dart 开发环境
  3. 注释
  4. 关键字
  5. 变量和常量
  6. 特殊数据类型
  7. 运算符
  8. 流程控制语句
  9. 异常

 

 

1. Dart 简介

Dart 是面向对象的、类定义的、单继承的语言。它的语法类似 C 语言,可以转译为 JavaScript,支持接口 (interfaces)、混入 (mixins)、抽象类 (abstract classes)、具体化泛型 (reified generics)、可选类型 (optional typing) 和 sound type system 。

Dart 语言是使用 Flutter 框架开发时候必备的语言,Flutter 是一个跨平台的框架,一套代码就可以完美实现 Android 和 iOS 两个平台,适配也很不错,Dart 语言很友好,和 Java 很类似,学习成本也是很低的。

学习 Dart 语言,必须将以下的概念熟记于心:

  • 在 Dart 语言中,一切皆为对象。所有的对象都是一个类的实例。甚至整数、函数、null 也看做是对象。所有的对象都继承于Object 类。
  • 尽管 Dart 是强类型语言,但是变量的类型指定不一定要标明,因为 Dart 可以推断出它的类型。比如说变量 number 就可以被推测出是 int 类型。如果你想明确表示这个变量不想被任何一个类型指定,那就使用特殊类型 dynamic 来表示。
  • Dart 语言支持通用类型,比如 List<int> 表示整数集列表,List<dynamic> 表示元素集为任意类型对象的列表。
  • Dart 支持顶级函数 (如 main()),以及绑定到类或对象的函数(分别是静态方法和实例方法)。你还可以在函数中创建函数(嵌套函数或本地函数)。
  • 类似的,Dart 支持顶级变量,以及绑定到类或对象 (静态和实例变量) 的变量。实例变量有时称为字段或属性。
  • 有别于 Java 语言,Dart 语言中没有 public、protected 和 private 这些关键字。在 Dart 里面,如果标识符以下划线 (_) 开头,那么它对其库是私有的。
  • Dart 里面的标志符号由 _、字母和数字组成,只能以 或者字母开头。
  • Dart 既有表达式(具有运行时值),也有语句(不具有运行时值)。比如传统的表达式 condition ? expr1 : expr2 会有一个值,expr1 或者 expr2。相比较于 if-else 语句,则没有值。一个语句通常包括一个或者多个表达式,但是一个表达式不能直接包含一个语句。
  • Dart 工具会向你发出两种类型问题:警告和错误。警告则是指出你的代码存在问题,但是不会阻止你正在执行的程序。错误则会发生在编译时和运行时。编译时的错误会完全阻止你代码的运行。运行时错误导致代码执行时引发异常。

 

 

2. Dart 开发环境

我这边的 OS 是 Mac,IDE 是 Intellij Idea。

 

  • 2.1 Dart SDK
$ brew tap dart-lang/dart
$ brew install dart

安装成功后:

Please note the path to the Dart SDK:
  /usr/local/opt/dart/libexec
==> Summary
?  /usr/local/Cellar/dart/2.2.0: 340 files, 281.4MB, built in 16 seconds

可以看到 Dart SDK 被安装到了 /usr/local/opt/dart/libexec 目录。

 

  • 2.2 Intellij Idea dart 插件

教程:https://blog.csdn.net/cekiasoo/article/details/82735312,安装完成后,IDE 会自动重启。

 

  • 2.3 新建 Dart 项目

New Project,选择 Dart,可以看到它需要 Dart SDK 的路径:

然后下一步,填写项目名词,存放路径,最后确定,创建完后的项目结构如下:

然后再新建一个存放代码的文件夹,可以随意命名,我这边命名为 src,再创建一个 Dart 文件:

接下来我们就来开始学习 Dart 的语法。

 

 

3. 注释

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

  • 1. 单行注释以//开头,Dart 编译器会忽略 // 和行尾之间的所有内容。
  • 2. 多行注释以/*开头,以*/结尾。介于/*和 */两者之间的内容会被编译器忽略 (除非该注释是一个文档注释)。多行注释可以嵌套。
  • 3. 文档注释以 /// 或者 /** 开头。可以通过 dartdoc 命令导出文档。

 

 

4. 关键字

Dart 的关键字一共有 56 个。

 

  • 4.1 保留字 (33个)
关键字---
ifsuperdoswitchassert
elseinthisenum
isthrowtruebreak
newtrycaseextends
nulltypedefcatchvar
classfalsevoidconst
finalrethrowwhilecontinue
finallyreturnwithfor
default---

 

  • 4.2 内置标志符 (17 个)
关键字---
abstractdeferredasdynamic
covariantexportexternalfactory
getimplementsimportlibrary
operatorpartsetstatic
typedef  

 

  • 4.3 Dart2 相对于 Dart1 新增的,支持异步功能的关键字 (6 个)
关键字---
asyncasync*awaitsync*
yieldyield*

 

  • 4.4 和 java 相比,Dart 特有的关键字 (25 个)
关键字---
deferredasassertdynamic
sync*asyncasync*in
isawaitexportlibrary
externaltypedeffactoryoperator
varpartconstrethrow
covariantsetyieldget
yield*  

 

 

5. 变量和常量

 

  • 5.1 变量的声明,可以使用 var、Object 或 dynamic 关键字
main() {
  // 创建变量并初始化变量实例
  var name = "kuang";
  // 如果对象不限于单一类型 (没有明确的类型),请使用 Object 或 dynamic 关键字
  Object name1 = "zhong";
  dynamic name2 = "wen";
  // 显式声明将被推断的类型
  String name3 = "ww";
}

 

  • 5.2 默认值

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

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

运行,最后打印输出 null。

 

  • 5.3 final 和 const 的用法

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

// 可以省略 String 这个类型声明
  final name1 = "kzw";
//final String name1  = "张三";

  const name2 = "zx";
//const String name2 = "zx";

被 final 或 const 修饰的变量无法再去修改其值:

  final name1 = "kzw";
  // 这样写,编译器提示:a final variable, can only be set once
  // 一个 final 变量,只能被设置一次。
//  name1 = "kzw_1";

  const name2 = "zx";
  // 这样写,编译器提示:Constant variables can't be assigned a value
  // const 常量不能赋值
//  name2 = "zx_1";

flnal 或者 const 不能和 var 同时使用:

 // 这样写都会报错
  final var name1 = "kzw";
  const var name2 = "zx";

常量如果是类级别的,请使用 static const:

class A {

  static const speed = 100;
}

常量的运算:

  // 速度(km/h)
  const speed = 100;
  // 距离 = 速度 * 时间
  const double distance = 2.5 * speed; 

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"];

只要任何插值表达式是一个计算结果为 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';

const 和 final 它们的区别在于,const 比 final 更加严格。final 只是要求变量在初始化后值不变,但通过 final,我们无法在编译时 (运行之前) 知道这个变量的值;而 const 所修饰的是编译时常量,我们在编译时就已经知道了它的值,显然,它的值也是不可改变的。

int Func() {
  // 代码
}

final int m1 = 60;
final int m2 = Func(); // 正确
const int n1 = 42;
const int n2 = Func(); // 错误

 

 

 

6. 特殊数据类型

Dart 支持以下特殊类型:

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

 

  • 6.1 num 数字类型

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

(1) int 类型

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

示例:

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

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

main() {
  // 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 类型。示例如下:

main() {
  double doubleNum1 = 1.1;
  print(doubleNum1); // 结果是1.1
  double doubleNum2 = 1.42e5;
  print(doubleNum2); // 结果是142000.0
}

(3) Dart2.1 里面新增特性,当 double 的值为 int 值时,int 自动转成 double

main() {
  double test = 12; // 打印结果是12.0
  print(test);
}

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

main() {
  int test = 10;
  print(test.toDouble()); // 结果是:10.0
}

(5) Dart2.1,double 也有 api 转成 int,会把小数点后面的全部去掉

main() {
  double test2 = 15.1;
  double test3 = 15.6234;
  print(test2.toInt());// 结果是15
  print(test3.toInt());// 结果是15
}

 

  • 6.2 String 字符串类型

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

(1) 你可以使用单引号或双引号来创建一个字符串

main() {
  String str1 = '单引号基本使用demo.';
  String str2 = "双引号基本使用demo.";
  print(str1);
  print(str2);
}

(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) 你可以使用相邻字符串文字或+ 运算符连接字符串

直接把相邻字符串写在一起,就可以连接字符串了:

  String connectionStr1 =  '字符串连接''甚至可以在''换行的时候进行。';
  print(connectionStr1);// 字符串连接甚至可以在换行的时候进行

用+把相邻字符串连接起来:

  String connectionStr2 =  '字符串连接'+ '甚至可以在'+ '换行的时候进行。';
  print(connectionStr2);// 字符串连接甚至可以在换行的时候进行

使用单引号或双引号的三引号:

  String connectionStr3 = ''' 
  这是用单引号创建的
  多行字符串。
  ''' ;
  print(connectionStr3);
  String connectionStr4 = """这是用双引号创建的
  多行字符串。""";
  print(connectionStr4);

打印出:

字符串连接甚至可以在换行的时候进行。
字符串连接甚至可以在换行的时候进行。
  这是用单引号创建的
  多行字符串。
  
这是用双引号创建的
  多行字符串。

(5) 关于转义符号的使用

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

  print(r"换行符:\n"); //这个结果是 换行符:\n
  print("换行符:\\n"); //这个结果是 换行符:\n
  print("换行符:\n");  //这个结果是 换行符:

(6) 使用 $ 可以获得字符串中的内容,使用 ${表达式} 也可以将表达式的值放入字符串中。使用 ${表达式} 时可以使用字符串拼接,也可以使用 String 类或者 Object 里面的某些方法获得相关字符串属性

使用 $+ 字符串:

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

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

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

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

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

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

 

  • 6.3 bool 布尔类型

Dart 表示布尔值的类型叫做 bool,它有两个值,分别是:true 和 false,它们都是编译时常量。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 模式下有效。在开发过程中, 除非条件为真,否则会引发异常 (断言失败则程序立刻终止)。

 

  • 6.4 list 集合,也成为数组

在 Dart 中,数组是 List 对象,因此大多数人只是将它们称为 List。

创建一个 int 类型的 list:

main() {
  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中是否包含数字5true

 

  • 6.5 map 集合

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

(1) 创建方式

直接声明,用 {} 表示,里面写 key 和 value,每组键值对中间用逗号隔开:

main() {
  Map companys = {'first': '阿里巴巴', 'second': '腾讯', 'fifth': '百度'};
  print(companys);//打印结果 {first: 阿里巴巴, second: 腾讯, fifth: 百度}
}

先声明,再去赋值:

  Map companys1 = new Map();
  companys1['first'] = '阿里巴巴';
  companys1['second'] = '腾讯';
  companys1['fifth'] = '百度';
  print(companys1);
  //打印结果 {first: 阿里巴巴, second: 腾讯, fifth: 百度}

要创建一个编译时常量 const 的 map,请在 map 文字之前添加 const:

  final fruitConstantMap = const {2: 'apple', 10: 'orange', 18: 'banana'};
  print(fruitConstantMap);
// 打印结果{second: 腾讯, fifth: 百度, 5: 华为}

(2) 添加元素。格式: 变量名 [key] = value,其中 key 可以是不同类型

  Map companys1 = new Map();
  companys1['first'] = '阿里巴巴';
  companys1['second'] = '腾讯';
  companys1['fifth'] = '百度';
  print(companys1);
  //打印结果 {first: 阿里巴巴, second: 腾讯, fifth: 百度}
  // 添加一个新的元素,key为“5”,value为“华为”
  companys[5] = '华为';
  // 打印结果 {first: 阿里巴巴, second: 腾讯, fifth: 百度, 5: 华为}
  print(companys); 

(3) 修改元素。格式:变量名 [key] = value

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

  companys['first'] = 'alibaba';
  // 打印结果 {first: alibaba, second: 腾讯, fifth: 百度, 5: 华为}
  print(companys);

(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。

 

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

Unicode 为世界上所有的书写系统中使用的每个字母,数字和符号定义了唯一的数值。Dart 字符串是 UTF-16 代码单元的序列,所以在字符串中表达32位 Unicode 值需要特殊的语法。Unicode 代码点的常用方法是 \uXXXX,其中 XXXX 是一个4位十六进制值。例如,心形字符(♥)是\u2665。要指定多于或少于4个十六进制数字,请将该值放在大括号中。 例如,笑的表情符号是\u{1f600}。String 类有几个属性可以用来提取符文信息。 codeUnitAt 和 codeUnit 属性返回16位代码单元。以下示例说明了符文,16位代码单元和32位代码点之间的关系。

main() {
  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));
}

执行输出:

?
[55357, 56399]
[128079]
♥  ?  ?  ?  ?  ?

 

 

7. 运算符

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

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

 

  • 7.1 ?.像.一样,但最左边的操作数可以为空

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

class Test {
  static int funs = 5;

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

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

main() {
  print(Test?.funs); // 打印5
}

 

  • 7.2 ..级联符号..

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

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

 

  • 7.3 ?? 三目运算符的一种形式

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

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

 

  • 7.4 ~/ 除法,返回一个整数结果,其实就是取商

小学都学过:被除数 ÷ 除数 = 商 ... 余数,在 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

 

  • 7.5 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函数');
  }
}

main() {
  Test test = new Test();
  Test2 test2 = new Test2();

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

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

执行输出:

构造函数 Test
构造函数 Test
构造函数 Test2
true
true
Test2 fun函数

 

 

8. 流程控制语句

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

 

  • 8.1 if else
if(条件语句){
    内容体
}else{
内容体
}
  • 8.2 for 循环

(1) 简单 for 循环

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

例如:

main() {
  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 是比较独特的,所以这一部分需要一点时间去阅读和熟悉。

  • 循环 (Loops):

标签最常用作 break 和 continue 内部循环。假设你有嵌套的循环,并要跳转到 break 或 continue 到外部循环。如果没有标记,这不可能 (轻松) 实现。以下示例使用 continue 标记名称从内部循环直接跳转到外部循环的下一轮循环:

// 返回具有最小总和的内部列表(正整数)。
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;
}
  • 跳出代码块

标记也可以用于跳出代码块。假设我们想要统一处理错误条件,但有多个条件 (可能是深度嵌套) 来揭示 (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);
  }
}
  • 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 语句来支持状态机。

 

  • 8.3 while 和 do while
while(判断条件){
    内容体
}

do{
内容体
} while(判断条件);

 

  • 8.4 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());

 

  • 8.5 switch case​​​​​​​

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

 

  • 8.6 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,则断言失败并抛出异常。

 

 

9. 异常

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

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

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

 

  • 9.1 throw

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

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

你也可以抛出任意对象,例如:throw '格式不正确!',通常在开发中会抛出 Error 或者 Exception 类型。因为抛出异常是一个表达式,所以可以在=>语句中以及允许表达式的任何其他地方抛出异常:

void distanceTo(int i) => throw UnimplementedError();   

 

  • 9.2 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}.');
  }
}

 

  • 9.3 finally

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

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

 

 

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值