版权声明:本文为博主原创文章,未经博主允许不得转载。https://www.jianshu.com/p/3d927a7bf020
转载请标明出处:
https://www.jianshu.com/p/3d927a7bf020
本文出自 AWeiLoveAndroid的博客
Flutter系列博文链接 ↓:
工具安装:
Flutter基础篇:
- 谷歌Flutter1.0正式版发布
- Flutter基础篇(1)-- 跨平台开发框架和工具集锦
- Flutter基础篇(2)-- 老司机用一篇博客带你快速熟悉Dart语法
- Flutter基础篇(3)-- Flutter基础全面详解
- Flutter基础篇(4)-- Flutter填坑全面总结
- Flutter基础篇(5)-- Flutter代码模板,解放双手,提高开发效率必备
- Flutter基础篇(6)-- 水平和垂直布局详解
- Flutter基础篇(7)-- Flutter更新错误全面解决方案(图文+视频讲解)
- Flutter基础篇(8)-- Flutter for Web详细介绍
- Flutter基础篇(9)-- 手把手教你用Flutter实现Web页面编写
- Flutter1.9升级体验总结(Flutter Web 1.9最新版本填坑指南)
Flutter进阶篇:
Dart语法系列博文链接 ↓:
Dart语法基础篇:
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
就可以看到文档了。如图所示:
二、关键字(60个)
5个上下文关键字(仅在特定位置具有含义。它们在任何地方都是有效的标识符)
关键字 | - | - | - |
---|---|---|---|
async | hide | on | show |
sync | - | - | - |
其中内置标志符有:(20个)
关键字 | - | - | - |
---|---|---|---|
abstract | as | covariant | defered |
dynamic | export | external | factory |
Function | get | implements | import |
interface | library | mixin | operator |
part | set | static | typedef |
Dart新增的,有限的保留字,支持异步功能的关键字有:(2个)
关键字 | - | - | - |
---|---|---|---|
await | yield |
33个保留字(不能使用保留字作为标识符)
关键字 | - | - | - |
---|---|---|---|
assert | break | case | catch |
class | const | continue | default |
do | else | enum | extends |
false | final | finally | for |
if | in | is | new |
null | rethrow | return | super |
switch | this | throw | true |
try | var | void | while |
with | - | - | - |
跟java相比,Dart特有的关键字有:(27个)
关键字 | - | - | - |
---|---|---|---|
as | async | await | covariant |
deferred | dynamic | export | external |
factory | Function | get | hide |
in | is | library | mixin |
on | operator | part | rethrow |
set | show | sync | typedef |
var | with | yield |
三、变量和常量
(一)变量的声明,可以使用 var、Object 或 dynamic 关键字。
创建变量并初始化变量实例:
var name = '张三' ;
变量存储引用。
- 使用Object或dynamic关键字
dynamic name = '张三';
调用的变量name包含对String值为“张三” 的对象的引用。
name推断变量的类型是String,但可以通过指定它来更改该类型。
如果对象不限于单一类型(没有明确的类型),请使用Object或dynamic关键字
Object name = '张三';
dynamic name = '李四';
- 显式声明将被推断的类型
比如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是数字类型的父类,有两个子类 int
和 double
。
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)您可以使用相邻字符串文字或+ 运算符连接字符串:
- 直接把相邻字符串写在一起,就可以连接字符串了。
String connectionStr1 = '字符串连接''甚至可以在''换行的时候进行。';
print(connectionStr1);// 字符串连接甚至可以在换行的时候进行。
- 用+把相邻字符串连接起来。
String connectionStr2 = '字符串连接'+ '甚至可以在'+ '换行的时候进行。';
print(connectionStr2);// 字符串连接甚至可以在换行的时候进行。
- 使用单引号或双引号的三引号:
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
,它有两个值,分别是: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模式下有效
。
在开发过程中, 除非条件为真,否则会引发异常。(断言失败则程序立刻终止)。
(四)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)创建方式:
- 直接声明,用{}表示,里面写key和value,每组键值对中间用逗号隔开。
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'};
// 打印结果{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)
标签最常用作break
和 continue
内部循环。假设你有嵌套的循环,并要跳转到break
或continue
到外部循环。如果没有标记,这不可能(轻松)实现。
以下示例使用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提供了Exception
和Error
类型,以及许多预定义的子类型。当然,您可以定义自己的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();
}