Dart 开发语言概览
目录
Dart 开发语言概览
重要概念
以下均假设开启了 空安全
- 所有变量引用的都是 对象,每个对象都是一个 类 的实例
- 数字、函数以及 null 都是对象
- 除去
null
以外, 所有的类都继承于Object
类
- Dart 是强类型语言,但是在声明变量时可不显示指定类型,因为 Dart 可以进行类型推断
- 变量在未声明为可空类型时不能为
null
- 可以通过在类型后加上问号
?
将类型声明为可空 - 可以在表达式后添加
!
来断言表达式不为空(为空时将抛出异常)
- 可以通过在类型后加上问号
- Dart 中可使用
Object?
或者dynamic
表示任意类型- 在编译时,任何成员对 dynamic 类型值访问都是允许的,但在运行时可能会引发异常
- 避免使用 dynamic,除非你希望禁用静态检查 (Indicates that you want to disable static checking)
- Dart 支持泛型,比如
List<int>
- Dart 支持顶级函数,支持定义属于类或对象的函数(即 静态 和 实例方法),支持 函数嵌套 或 局部函数
- Dart 支持顶级变量,支持定义属于类或对象的变量(即 静态 和 实例变量),实例变量有时称之为
域或属性
- Dart 没有类似于 Java 那样的 public、protected 和 private 成员访问限定符
- 标识符可以以字母或者下划线
_
开头 - 以下划线
_
开头时表示该标识符在库内是私有的
- 标识符可以以字母或者下划线
- Dart 中表达式有值而语句没有值
- Dart 工具可以显示 警告 和 错误 两种类型的问题
- 警告表明代码可能有问题但不会阻止其运行
- 错误分为编译时错误和运行时错误;编译时错误代码无法运行;运行时错误会在代码运行时导致异常
变量
- 变量仅存储对象的引用,而非对象的值
- 在 Dart 中一切皆为对象,数字也不例外
- 建议通过
var
声明局部变量,而非使用指定的类型- 因为函数往往很短,省略局部变量类型会将读者的注意力集中在变量的 名称 及初始化值上
- 并不需要在声明变量时初始化,只需在第一次用到这个变量前初始化即可
late
- 可以在声明变量时用
late
修饰,用来延迟初始化一个变量 - 若
late
修饰的变量在使用前没有初始化,会抛出运行时异常 -- 即使变量是可空类型 - 若
late
修饰的变量在声明时指定了初始化方法,那么初始化过程会且仅会
发生在第一次被使用的时候
String? s1;
late String? s2;
void main() {
assert(s1 == null);
print(s1); // null
print(s2); // LateInitializationError: Field 's2' has not been initialized.
}
- 在生产环境,assert() 的调用将会被忽略掉
- 在开发过程,
assert(condition)
将会在 condition 为 false 时抛出一个异常
final 和 const
- 关键字
final
或const
可以替代var
关键字,也可以加在一个具体的类型前 final
变量只可以被赋值一次const
变量是一个编译时常量,const
变量同时也是final
的- 如果使用
const
修饰类中的变量,则必须加上static
关键字,即static const
, 顺序不能颠倒
final s1 = null;
final String s2 = "s2";
late final s3;
const name = "bqt";
const double pi = 3.14;
- const 也可以用来创建 常量值,该常量值可以赋予给任何变量
- const 也可以用来修饰 构造函数,这种类型的构造函数创建的对象是不可改变的
var c1 = const [1, 2]; // 普通变量,可以被修改
final c2 = const [1, 2]; // final 变量,不可修改
const c3 = [1, 2]; // const 变量,不可修改
const c4 = const [1, 2]; // const 变量,不可修改
void main() {
c1 = [666]; // [666]
print(c1);
}
注释
- 单行注释:
//
- 多行注释: 以
/*
开始,以*/
结尾 - 文档注释: 以
///
或者/**
开始- 文档注释可以是多行注释,也可以是单行注释
- 可以使用中括号引用类、方法、字段、顶级变量、函数和参数
- 在生成的文档中,
[xxx]
会成为一个链接,指向 xxx 方法/类/字段的文档
内置类型
数字 int double
- 整数 int 是不带小数点的数字
- 如果包含小数点,那么它就是浮点型 double
- 整型字面量可自动转换成浮点数字面量
- 数字字面量为编译时常量
- 很多算术表达式,只要其操作数是常量,则表达式结果也是编译时常量
var x = 1;
var hex = 0xDEADBEEF; // 16^8 = 2^(4*8) 即 32 位
var y = 1.1;
var exponents = 1.42e5;
double z = 1; // Equivalent to double z = 1.0.
var one = int.parse('1'); // String -> int
var onePointOne = double.parse('1.1'); // String -> double
字符串 String
- 可以使用单引号或者双引号来创建字符串
- 使用单引号创建字符串时,可以使用
斜杠
来转义那些与单引号冲突的字符串 - 如果
${表达式}
的结果为一个对象,则 Dart 会调用该对象的 toString 方法来获取一个字符串 - 可以使用
+
运算符或并列放置
多个字符串(即便它们不在同一行)来连接字符串 - 使用三个单引号或者三个双引号也能创建多行字符串
- 在字符串前加上
r
作为前缀创建 raw 字符串, 即不会被做任何处理(比如转义)的字符串
var s1 = '可以拼接'
'字符串'
"即便它们不在同一行。";
assert(s1 == '可以拼接字符串即便它们不在同一行。');
var s2 = '''
You can create
multi-line strings like this one.
''';
print(s2);
var s3 = r'在 raw 字符串中,转义字符串 \n 会直接输出 "\n" 而不是转义为换行。';
print(s3);
const cList = [1, 2];
const cString = '$cList'; // 仅 null、num、String、bool 可以作为字符串字面量的插值表达式
布尔 bool
- Dart 使用
bool
关键字表示布尔类型 - 布尔类型只有两个对象
true
和false
,两者都是编译时常量
数组 List
- 可以在 Dart 的集合类型的最后一个项目后添加逗号
- 在 List 字面量前添加
const
关键字会创建一个编译时常量 - 扩展操作符
...
: 将一个 List 中的所有元素插入到另一个 List 中 - 如果扩展操作符右边可能为
null
,可以使用...?
来避免产生异常
var list = [1, 2, 3];
assert(list.length == 3);
assert(list[1] == 2);
var constantList = const [1, 2, 3];
var list2 = [0, ...list]; // 扩展操作符
assert(list2.length == 4);
var list3 = [0, ...?list]; // 空感知扩展操作符
在构建集合时,可以使用条件判断 if
和循环 for
var nav = ['Home', 'Furniture', 'Plants', if (promoActive) 'Outlet'];
var listOfInts = [1, 2, 3];
var listOfStrings = ['#0', for (var i in listOfInts) '#$i'];
assert(listOfStrings[1] == '#1');
无序集合 Set
- Set 是一组特定元素的无序集合
- 可以使用在
{}
前加上类型参数的方式创建一个空的 Set,或者将{}
赋值给一个 Set 类型的变量 - 可以在 Set 变量前添加
const
关键字创建一个 Set 编译时常量 - 可以像 List 一样支持使用扩展操作符以及 Collection if 和 for 操作
var set = const {'a', 'c', '1', 'b'};
var names1 = <String>{}; // 创建一个空的 Set
Set<String> names2 = {}; // 创建一个空的 Set
var names3 = {}; // 类型为 Map<dynamic, dynamic>
names1.add("bqt"); // 添加项目
names1.addAll(names2); // 添加所有项目
print(names1.elementAt(0)); // 获取项目
print(names1.length); // 获取元素数量
键值对 Map
- Map 是用来关联 keys 和 values 的对象,其中键和值都可以是任何类型的对象
- 每个 键 只能出现一次,但是 值 可以重复出现多次
- 在一个 Map 字面量前添加 const 关键字可以创建一个 Map 编译时常量
- 可以像 List 一样支持使用扩展操作符以及 Collection if 和 for 操作
var map1 = Map<String, String>();
map1['first'] = 'bqt';
var map2 = { 2: 'bqt'}; // 类型推断为 Map<int, String>
函数
Dart 是一种真正面向对象的语言,所以函数也是对象并且类型为 Function,这意味着函数可以被赋值给变量或者作为其它函数的参数。
- 建议在公开的 API 上定义返回类型,不过也可以不定义
- 如果函数体内只包含一个表达式,可以使用 箭头 表达式
=> _表达式_
代替{ return _表达式_; }
- 只能是 表达式 而不能是 语句
参数
函数可以有两种形式的参数:必要参数 和 可选参数。必要参数定义在参数列表前面,可选参数则定义在必要参数后面。
其中,可选参数可以是 命名参数 或 位置参数。
- 向函数传入参数或者定义函数参数时,可以使用 尾逗号
- 可以将 命名参数 放在参数列表的任意位置,但通常将 位置参数 放在最前面比较合理
- 定义函数时,使用
{_参数1_, _参数2_, …}
来指定命名参数,使用[]
来指定位置参数
命名参数
- 命名参数默认为可选参数,除非他们被特别标记为
required
。 - 如果没有为命名参数提供默认值,也没有使用
required
标记,那么它就要定义为可空的类型,默认值是null
- 当调用函数时,可以使用
_参数名_: _参数值_
指定一个命名参数的值 - 标记为
required
的命名参数仍然可以是可空的类型
注意: 某些 API(特别是 Flutter 控件的构造器)只使用命名参数
main 函数
每个 Dart 程序都必须有一个 main()
顶级函数作为程序的入口, main()
函数返回值为 void
并且有一个 List<String>
类型的可选参数。
使用 命令行 访问带参数的 main()
函数示例:
// dart TestDart.dart 20094 bqt
void main(List<String> arguments) {
print(arguments); // [20094, bqt]
}
可以通过 参数库 定义和解析命令行参数。
匿名函数
没有名字的函数称之为 匿名函数、 Lambda 表达式 或 Closure 闭包。
如果函数体内只有一行返回语句,你可以使用胖箭头缩写法。粘贴下面代码到 DartPad 中并点击运行按钮,验证两个函数是否一致。
list
.map((item) => item.toUpperCase())
.forEach((item) => print('$item: ${item.length}'));
词法闭包
闭包 即一个函数对象,即使函数对象的调用在它原始作用域之外,依然能够访问在它词法作用域内的变量。
Function makeAdder(int addBy) => (int i, int j) => addBy + i + j;
void main() {
var add2 = makeAdder(2);
var add4 = makeAdder(4);
assert(add2(1, 0) == 3);
assert(add4(1, 1) == 6);
}
函数相等
void foo() {} // A top-level function
class A {
static void bar() {} // A static method
void baz() {} // An instance method
}
void main() {
Function x;
x = foo;
assert(foo == x); // top-level functions
x = A.bar;
assert(A.bar == x); // static methods
x = A().baz;
assert(A().baz != x); // refer to different instances, so they're unequal
}
函数返回值
所有的函数都有返回值。没有显式返回语句的函数, 最后一行默认为执行 return null;
。
foo() {}
assert(foo() == null);
运算符
- 一旦使用了运算符,就创建了表达式
- 对于有两个操作数的运算符,左边的操作数决定了运算符的功能
- 比如表达式
aVector + aPoint
中所使用的是 Vector 对象中定义的相加运算符 (+) - 表达式
x == y
返回对 x 调用==
方法的结果,参数为 y
- 比如表达式
?[]
: 左侧调用者不为空时,访问 List 中特定位置的元素
assert(5 / 2 == 2.5); // 除,Result is a double
assert(5 ~/ 2 == 2); // 除并取整,Result is an int
assert(5 % 2 == 1); // 取模,Remainder
类型判断
as
类型转换(也用作指定 库前缀)is
如果对象是指定类型则返回 trueis!
如果对象是指定类型则返回 false
二进制位运算
二进制位运算符仅适用于整数
&
按位与|
按位或^
按位异或~表达式
按位取反<<
位左移>>
位右移>>>
无符号右移
条件表达式
??=
: 用来为值为null
的变量赋值表达式 1 ?? 表达式 2
: 如果表达式 1 为非 null 则返回其值,否则执行表达式 2 并返回其值foo?.bar
: 条件访问成员,如果 foo 为null
则返回null
,否则返回 bar
var a = null;
a ??= "bqt";
print(a); // bqt
var b = null;
var name1 = b ?? "bqt1"; // 条件表达式
print("b=$b name1=$name1"); // b=null name1=bqt1
var name2 = b ??= "bqt2"; // 赋值语句
print("b=$b name2=$name2"); // b=bqt2 name2=bqt2
级联运算符
- 可以在同一个对象上连续调用多个变量或方法
- 级联运算符可以嵌套
- 忽略任何可能返回的值
- 如果级联操作的对象可能为
null
,则对第一个操作使用空短路级联?..
。从?..
开始保证不会对空对象尝试任何级联操作
var paint = getPaint()
?..color = Colors.black
..strokeCap = StrokeCap.round
..strokeWidth = 5.0;
流程控制语句
For 循环
可迭代对象可以使用 forEach()
方法
var list = [1, 2, 3];
var addInt = (e) => print(e + 10);
list.forEach(addInt); // 11 12 13
for 循环中的闭包会自动捕获循环的 索引值 以避免 JavaScript 中一些常见的陷阱
var callbacks = [];
for (var i = 0; i < 2; i++) {
callbacks.add(() => print(i));
}
for (final c in callbacks) {
c(); // 代表调用一个方法
}
上述代码执行后会输出 0 和 1,但是如果在 JavaScript 中执行同样的代码则会输出两个 2
Switch 和 Case
- Switch 语句中使用
==
来比较两个对象 - 比较的两个对象必须是同一类型且不能是子类并且没有重写 == 操作符
- 支持空的 case 语句,允许以 f
all-through
的形式执行 - 每一个非空的 case 子句都必须有一个
break
语句- 也可以通过
throw
或者return
来结束非空 case 语句 - 也支持使用
continue label
来结束
- 也可以通过
- 每个 case 子句都可以有局部变量, 且仅在该 case 语句内可见
var command = 'CLOSED';
switch (command) {
case 'CLOSED':
var a = 1;
break;
case 'NOW_CLOSED':
var a = 2;
break;
default:
var a = 3;
}
assert
- assert 的第一个参数可以是值为布尔值的任何表达式
- 如果表达式的值为 true,则断言成功,继续执行
- 如果表达式的值为 false,则断言失败,抛出一个
AssertionError
异常
- assert 的第二个参数可以为其添加一个字符串消息
断言是否生效依赖开发工具和使用的框架:
- Flutter 在 调试模式 时生效
- 其他一些工具,比如
dart run
以及dart compile js
, 通过在运行时添加命令行参数--enable-asserts
时生效 - 在生产环境代码中,断言会被忽略,此时传入 assert 的参数不被判断。
异常
与 Java 不同的是,Dart 的所有异常都是非必检异常,方法不必声明会抛出哪些异常,并且也不必捕获任何异常。
抛出异常
- 可以将任何非 null 对象作为异常抛出
- 但优秀的代码通常会抛出 Error 或 Exception 类型的异常
- 抛出异常是一个表达式,可以在
=>
语句中使用
void main() {
try {
getName();
} catch (e) {
print(e);
}
}
void getName() => throw Exception("抛出异常");
捕获异常
- 可以使用
on
或catch
来捕获异常,使用on
来指定异常类型,使用catch
来捕获异常对象,两者可同时使用 - 可以为
catch
方法指定两个参数,第一个参数为抛出的异常对象,第二个参数为栈信息StackTrace
对象 - 可以使用
rethrow
将捕获的异常再次抛出
try {
// ···
} on NoSuchMethodError {
print('NoSuchMethodError');
} on Exception catch (e) {
print('Exception details:\n $e');
} catch (e, s) {
print('Stack trace:\n $s');
rethrow;
}
Finally
- 无论是否抛出异常,finally 语句始终执行
- 如果没有指定 catch 语句来捕获异常,则异常会在执行完 finally 语句后抛出
2023-06-24
本文来自博客园,作者:白乾涛,转载请注明原文链接:https://www.cnblogs.com/baiqiantao/p/17501884.html