学习 Flutter,必须得掌握 Dart 语言,这篇文章就来整理一下 Dart 的语法,由于内容较多,所以分成上下两篇。
目录:
- Dart 简介
- Dart 开发环境
- 注释
- 关键字
- 变量和常量
- 特殊数据类型
- 运算符
- 流程控制语句
- 异常
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。
$ 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 会自动重启。
New Project,选择 Dart,可以看到它需要 Dart SDK 的路径:
然后下一步,填写项目名词,存放路径,最后确定,创建完后的项目结构如下:
然后再新建一个存放代码的文件夹,可以随意命名,我这边命名为 src,再创建一个 Dart 文件:
接下来我们就来开始学习 Dart 的语法。
3. 注释
Dart 的注释分为 3 种:单行注释、多行注释、文档注释。
- 1. 单行注释以
// 开头,Dart 编译器会忽略 // 和行尾之间的所有内容。
- 2. 多行注释以
/* 开头,以*/ 结尾。介于/* 和 */ 两者之间的内容会被编译器忽略 (除非该注释是一个文档注释)。多行注释可以嵌套。
- 3. 文档注释以
/// 或者 /** 开头。可以通过 dartdoc 命令导出文档。
4. 关键字
Dart 的关键字一共有 56 个。
关键字 |
- |
- |
- |
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 |
- |
- |
- |
关键字 |
- |
- |
- |
abstract |
deferred |
as |
dynamic |
covariant |
export |
external |
factory |
get |
implements |
import |
library |
operator |
part |
set |
static |
typedef |
|
|
- 4.3 Dart2 相对于 Dart1 新增的,支持异步功能的关键字 (6 个)
关键字 |
- |
- |
- |
async |
async* |
await |
sync* |
yield |
yield* |
- 4.4 和 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* |
|
|
5. 变量和常量
- 5.1 变量的声明,可以使用 var、Object 或 dynamic 关键字
main() {
// 创建变量并初始化变量实例
var name = "kuang";
// 如果对象不限于单一类型 (没有明确的类型),请使用 Object 或 dynamic 关键字
Object name1 = "zhong";
dynamic name2 = "wen";
// 显式声明将被推断的类型
String name3 = "ww";
}
未初始化的变量的初始值为 null (包括数字),因此数字、字符串都可以调用各种方法。
// 测试数字类型的初始值是什么?
int intDefaultValue;
// assert 是语言内置的断言函数,仅在检查模式下有效
// 在开发过程中,除非条件为真,否则会引发异常。(断言失败则程序立刻终止)
assert(intDefaultValue == null);
// 打印结果为null,证明数字类型初始化值是 null
print(intDefaultValue);
运行,最后打印输出 null。
如果你从未打算更改一个变量,那么使用 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 字符)。
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
}
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,继续进行,否则抛出异常,中断操作。
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 模式下有效 。在开发过程中, 除非条件为真,否则会引发异常 (断言失败则程序立刻终止)。
在 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中是否包含数字5 |
true |
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 里面比较有代表性的以及有特点的一些运算符相关用法。
比如: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
}
级联符号.. 允许你在同一个对象上进行一系列操作。 除了函数调用之外,还可以访问同一对象上的字段。其实相当于 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}'); // 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
-
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 语言差不多,有这些语句:
if(条件语句){
内容体
}else{
内容体
}
(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 是比较独特的,所以这一部分需要一点时间去阅读和熟悉。
标签最常用作 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 内部。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(判断条件);
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());
比较 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();
}
如果布尔条件为 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 对象)。
以下是抛出或引发异常的示例:
throw FormatException('Expected at least 1 section');
你也可以抛出任意对象,例如:throw '格式不正确!', 通常在开发中会抛出 Error 或者 Exception 类型。因为抛出异常是一个表达式,所以可以在=>语句中以及允许表达式的任何其他地方抛出异常:
void distanceTo(int i) => throw UnimplementedError();
捕获或捕获异常会阻止异常传递 (除非你重新抛出异常),捕获异常使你有机会处理它:
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 子句。如果没有 catch 子句匹配该异常,则在 finally 子句运行后传递异常。示例:
try {
breedMoreLlamas();
} finally {
// 即使抛出异常 也会执行这句代码.
cleanLlamaStalls();
}
该finally子句在任何匹配的catch子句之后运行:
try {
breedMoreLlamas();
} catch (e) {
// 首先会处理异常
print('Error: $e');
} finally {
// 然后执行这句语句
cleanLlamaStalls();
}
|
请发表评论