Dart编程语言概览
一个简单的Dart程序:
- 注释,单行、多行
- 数据类型、字面量、输出方式
- 字符串插值
- main()函数:特定的顶级函数
- 定义变量var:通过这种方式定义变量不需要指定变量类型
// 定义一个函数 printInteger(int aNumber) {
// 打印 print('The number is $aNumber)'); } // 应用从这里开始 main() {
var number = 42; printInteger(number); }
重要的概念:
- 一切皆对象,所有对象都有对应的一个
类
的实例;无论数字、函数和null
都是对象;所有对象都继承自Object
类; - Dart是强类型语言,但可以推断类型;如果要明确说明不需要任何类型,需要使用特殊类型
dynamic
动态类型; - Dart支持泛型,如
List<int>
整数列表、List<dynamic>
任何类型的对象列表; - Dart对函数的支持:
- 支持顶级函数main()
- 绑定在类上——静态函数
- 绑定在对象上——实例函数
- 支持函数内创建函数(嵌套或 局部函数)
- Dart对变量的支持:
- 支持顶级变量
- 绑定在类上——静态变量
- 绑定在对象上——实例变量(字段/属性)
- Dart没有关键字
public/protected/private
,如果标识符以下划线_
开头,则它相对于库是私有的; - Dart表达式(运行时有值),语句(运行时无值);
condition?expr1:expr2
值可能是二者之一,if-else
语句没有值;语句可以包含表达式,但是表达式不能直接包含语句; - Dart工具提示两种类型问题:警告 和 错误(编译时错误会阻止代码执行 或 运行时错误会导致代码在执行过程中引发异常);
Dart关键字解析:
abstract
:定义 抽象类 — 抽象类不能实例化;抽象类通常用来定义接口,以及部分实现。 如果希望抽象类能够被实例化,那么可以通过定义一个 工厂构造函数 来实现;
// 这个类被定义为抽象类, // 所以不能被实例化。 abstract class AbstractContainer {
// 定义构造行数,字段,方法... void updateChildren(); // 抽象方法。 }
as
、as
、is
、is!
运算符用于在运行时处理类型检查;- 例如,
obj is Object
总是 true。 但是只有 obj 实现了 T 的接口时,obj is T
才是 true。 - 使用 as 运算符将对象强制转换为特定类型;
- 例如,
if (emp is Person) {
(emp as Person).firstName = 'Bob'; }
assert
:如果 assert 语句中的布尔条件为 false , 那么正常的程序执行流程会被中断- 例如,
assert(urlString.startsWith('https'), 'URL ($urlString) should start with "https".');
- assert 语句只在开发环境中有效, 在生产环境是无效的;
- 断言失败,会抛出异常 (
AssertionError
);
- 例如,
async
和await
:- Dart 库中包含许多返回 Future 或 Stream 对象的函数. 这些函数在设置完耗时任务(例如 I/O 曹组)后, 就立即返回了,不会等待耗任务完成。 使用 async 和 await 关键字实现异步编程。 可以让你像编写同步代码一样实现异步操作。
- 要使用 await , 代码必须在 异步函数(使用 async 标记的函数)中;
- 使用 try, catch, 和 finally 来处理代码中使用 await 导致的错误。
- 在一个异步函数中可以多次使用 await
- 在 await 表达式 中, 表达式 的值通常是一个 Future 对象; 如果不是,这是表达式的值会被自动包装成一个 Future 对象。
Future checkVersion() async {
var version = await lookUpVersion(); // Do something with version } try {
version = await lookUpVersion(); } catch (e) {
// React to inability to look up the version } var entrypoint = await findEntrypoint(); var exitCode = await runExecutable(entrypoint, args); await flushThenExit(exitCode);
break
和continue
:- 使用 break 停止程序循环,使用 continue 跳转到下一次迭代;
- 如果对象实现了 Iterable 接口 (例如,list 或者 set)。 那么示例完全可以用另一种方式来实现:
while (true) {
if (shutDownRequested()) break; processIncomingRequests(); } for (int i = 0; i < candidates.length; i++) {
var candidate = candidates[i]; if (candidate.yearsExperience < 5) {
continue; } candidate.interview(); } candidates .where((c) => c.yearsExperience >= 5) .forEach((c) => c.interview());
case
、switch
、default
:- 在Dart中switch语句使用
==
比较比较整数,字符串,或者编译时常量 - 比较的对象必须都是同一个类的实例(并且不可以是子类), 类必须没有对 == 重写。
- 枚举类型 可以用于 switch 语句
- 在 case 语句中,每个非空的 case 语句结尾需要跟一个 break 语句;除 break 以外,还有可以使用 continue, throw,者 return。
- Dart 支持空 case 语句, 允许程序以
fall-through
的形式执行。 - 在非空 case 中实现
fall-through
形式, 可以使用 continue 语句结合 lable 的方式实现 - case 语句可以拥有局部变量, 这些局部变量只能在这个语句的作用域中可见。
- 在Dart中switch语句使用
var command = 'OPEN'; switch (command) {
case 'CLOSED': executeClosed(); // break; // 缺省break会报错 case 'PENDING': // executePending(); //但支持空case语句 // break; case 'APPROVED': executeApproved(); continue open; case 'DENIED': executeDenied(); break; open: case 'OPEN': executeOpen(); break; default: executeUnknown(); }
catch
、finally
、rethrow
:捕获异常可以避免异常继续传递(除非重新抛出(rethrow
)异常)。 可以通过捕获异常的机会来处理该异常- 通过指定多个 catch 语句,可以处理可能抛出多种类型异常的代码。
- catch 语句未指定类型, 则该语句可以处理任何类型的抛出对象
catch()
函数可以指定1到2个参数, 第一个参数为抛出的异常对象, 第二个为堆栈信息 ( 一个StackTrace
对象 )。- 如果仅需要部分处理异常, 那么可以使用关键字 rethrow 将异常重新抛出。
- 不管是否抛出异常, finally 中的代码都会被执行。 如果 没有用catch 匹配异常, 异常会在 finally 执行完成后,再次被抛出;任何匹配的 catch 执行完成后,再执行 finally ;
try {
breedMoreLlamas(); } on OutOfLlamasException {
// 一个特殊的异常 buyMoreLlamas(); } on Exception catch (e) {
// 其他任何异常 print('Unknown exception: $e'); } catch (e, s) {
// 没有指定的类型,处理所有异常 print('Something really unknown: $e'); rethrow; } finally {
// Always clean up, even if an exception is thrown. cleanLlamaStalls(); }
throw
:- 高质量的生产环境代码通常会实现 Error 或 Exception 类型的异常抛出
// 抛出异常 throw FormatException('Expected at least 1 section'); // 抛出任意对象 throw 'Out of llamas!'; // 因为抛出异常是一个表达式, 所以可以在 => 语句中使用,也可以在其他使用表达式的地方抛出异常: void distanceTo(Point other) => throw UnimplementedError();
class
、this
:class 用于声明类;- 所有实例变量都生成隐式 getter 方法。 非 final 的实例变量同样会生成隐式 setter 方法
- 构造函数中,使用
this
关键字引用当前实例;仅当存在命名冲突时,使用this
关键字。 否则,按照 Dart 风格应该省略 this ;(通常模式下,会将构造函数传入的参数的值赋值给对应的实例变量)
class Point {
num x; // 声明示例变量 x,初始值为 null 。 num y; // 声明示例变量 y,初始值为 null 。 num z = 0; // 声明示例变量 z,初始值为 0 。 // 生成构造函数 Point(num x, num y) {
// 还有更好的方式来实现下面代码,敬请关注。 this.x = x; this.y = y; } }
const
和final
:- 使用过程中从来不会被修改的变量, 可以使用 final 或 const, 而不是 var 或者其他类型
- Final 变量的值只能被设置一次;Const 变量在编译时就已经固定 (Const 变量 是隐式 Final 的类型.)
- 实例变量可以是 final 类型但不能是 const 类型。
- 如果 Const 变量是类级别的,需要标记为 static const
- Const 关键字不仅可以用于声明常量变量,还可以用来创建常量值(
const
关键字在声明常量构造函数时
还有应用,参考关键字new
的描述)
// 声明常量变量 const bar = ; // 创建常量值 var foo = const [];
deferred
:- Deferred loading (也称之为 lazy loading) 可以让应用在需要的时候再加载库
- 常用场景:减少 APP 的启动时间。执行 A/B 测试,例如 尝试各种算法的 不同实现。加载很少使用的功能,例如可选的屏幕和对话框。
- 延迟加载库的常量在导入的时候是不可用的,在导入文件的时候也无法使用延迟库中的类型;
// 要延迟加载一个库,需要先使用 deferred as 来导入 import 'package:greetings/hello.dart' deferred as hello; // 当需要使用的时候,使用库标识符调用 loadLibrary() 函数来加载库: Future greet() async {
// 可以多次调用 loadLibrary() 函数。但是该库只是载入一次 await hello.loadLibrary(); hello.printGreeting(); }
do
和do-while
while (!isDone()) {
doSomething(); } do {
printLine(); } while (!atEndOfPage());
dynamic
:动态的数据类型else
if
:和 JavaScript 不同, Dart 的判断条件必须是布尔值,不能是其他类型
if (isRaining()) {
you.bringRainCoat(); } else if (isSnowing()) {
you.wearJacket(); } else {
car.putTopDown(); }
enum
:枚举类型也称为 enumerations 或 enums , 是一种特殊的类,用于表示数量固定的常量值- 枚举中的每个值都有一个 index getter 方法, 该方法返回值所在枚举类型定义中的位置(从 0 开始)
- 使用枚举的 values 常量, 获取所有枚举值列表( list )
- 可以在 switch 语句 中使用枚举, 如果不处理所有枚举值,会收到警告
- 枚举不能被子类化,混合或实现
- 枚举不能被显式实例化
enum Color {
red, green, blue } assert(Color.red.index == 0); List<Color> colors = Color.values; assert(colors[2] == Color.blue);
export
:- 库代码位于lib目录下,对其他包是公开的。您可以根据需要在lib下创建任何层次结构。按照惯例,实现代码放在lib/src下。lib/src下的代码被认为是私有的;其他包永远不需要导入src/…要使lib/src下的api公开,可以从直接位于lib下的文件导出lib/src文件;
// 目录结构 - src - cascade.dart - ... - shelf.dart - shelf_io.dart // shelf.dart, exports several files from lib/src: export 'src/cascade.dart'; export ...
extends
和super
:使用 extends 关键字来创建子类, 使用 super 关键字来引用父类
class Television {
void turnOn() {
_illuminateDisplay(); _activateIrSensor(); } // ··· } class SmartTelevision extends Television {
void turnOn() {
super.turnOn(); _bootNetworkInterface(); _initializeMemory(); _upgradeApps(); } // ··· }
factory
:工厂构造函数- 当执行构造函数并不总是创建这个类的一个新实例时,则使用 factory 关键字。
- 一个工厂构造函数可能会返回一个 cache 中的实例, 或者可能返回一个子类的实例。
- 工厂构造函数无法访问 this。
class Logger {
final String name; bool mute = false; // 从命名的 _ 可以知, // _cache 是私有属性。 static final Map<String, Logger> _cache = <String, Logger>{
}; factory Logger(String name) {
if (_cache.containsKey(name)) {
return _cache[name]; } else {
final logger = Logger._internal(name); _cache[name] = logger; return logger; } } Logger._internal(this.name); void log(String msg) {
if (!mute) print(msg); } } // 工厂构造函的调用方式与其他构造函数一样 var logger = Logger('UI'); logger.log('Button clicked');
false
和true
for
:- 闭包在 Dart 的 for 循环中会捕获循环的 index 索引值, 来避免 JavaScript 中常见的陷阱
- 如果要迭代一个实现了 Iterable 接口的对象, 可以使用 forEach() 方法
- 实现了 Iterable 的类(比如, List 和 Set)同样也支持使用 for-in 进行迭代操作 iteration
var message = StringBuffer('Dart is fun'); for (var i = 0; i < 5; i++) {
message.write('!'); } var callbacks = []; for (var i = 0; i < 2; i++) {
// 输出的是 0 和 1。 但是示例中的代码在 JavaScript 中会连续输出两个 2 callbacks.add(() => print(i)); } // 如果不需要使用当前计数值, 使用 forEach() 是非常棒的选择; callbacks.forEach((c) => c()); var collection = [0, 1, 2]; for (var x in collection) {
print(x); // 0 1 2 }
Function
:- Dart 是一门真正面向对象的语言, 甚至其中的函数也是对象,并且有它的类型 Function;
- 这也意味着函数可以被赋值给变量或者作为参数传递给其他函数。
箭头 语法
:=> expr;
语法是{ return expr; }
的简写;- 在箭头 (
=>
) 和分号 (;
) 之间只能使用一个 表达式 ,不能是 语句 - 函数有两种参数类型: required 和 optional。 required 类型参数在参数最前面, 随后是 optional 类型参数。 命名的可选参数也可以标记为 “
@required
” - 可选参数可以是
命名参数
或者位置参数
,但一个参数只能选择其中一种方式修饰。
- 命名参数 & 位置参数:
- 默认值只能是编译时常量。 如果没有提供默认值,则默认值为 null。
- list 或 map 可以作为默认值传递;
// 位置参数声明方式 bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null; // 位置参数的调用方式 isNoble(100); // 命名参数声明方式 void enableFlags({
bool bold, bool hidden= false}) {
...} // 命名参数的调用方式 enableFlags(bold: true, hidden: false); // Flutter 创建实例的表达式可能很复杂, 因此窗口小部件构造函数仅使用命名参数 const Scrollbar({
Key key, @required Widget child}) // 位置可选参数 String say(String from, String msg, [String device]) {
var result = '$from says $msg'; if (device != null) {
result = '$result with a $device'; } return result; } // list 或 map 可以作为默认值传递 void doStuff( {
List<int> list = const [1, 2, 3], Map<String, String> gifts = const {
'first': 'paper', 'second': 'cotton', 'third': 'leather' }}) {
print('list: $list'); print('gifts: $gifts'); }
Required 被定义在 meta package。 无论是直接引入(import)
package:meta/meta.dart
,或者引入了其他 package,而这个 package 输出(export)了 meta,比如 Flutter 的package:flutter/material.dart
。
implements
:隐式接口- 每个类都隐式的定义了一个接口,接口包含了该类所有的实例成员及其实现的接口。 如果要创建一个 A 类,A 要支持 B 类的 API ,但是不需要继承 B 的实现, 那么可以通过 A 实现 B 的接口。
- 一个类可以通过 implements 关键字来实现一个或者多个接口, 并实现每个接口要求的 API。
// 一个类对应一个 隐式的接口Person class Person {
// 包含在接口里,但只在当前库中可见。 final _name; // 不包含在接口里,因为这是一个构造函数。 Person(this._name); // 包含在接口里。 String greet(String who) => 'Hello, $who. I am $_name.'; } // person 接口的实现。 class Impostor implements Person {
get _name => ''; String greet(String who) => 'Hi $who. Do you know who I am?'; } // 调用 String greetBob(Person person) => person.greet('Bob'); void main() {
print(greetBob(Person('Kathy'))); print(greetBob(Impostor())); } // 实现多个接口 class Point implements Comparable, Location {
...}
get
和set
:- Getter 和 Setter 是用于对象属性读和写的特殊方法
- 使用 get 和 set 关键字实现 Getter 和 Setter ,能够为实例创建额外的属性。
class Rectangle {
num left, top, width, height; Rectangle(this.left, this.top, this.width, this.height); // 定义两个计算属性: right 和 bottom。 num get right => left + width; set right(num value) => left = value - width; num get bottom => top + height; set bottom(num value) => top = value - height; } void main() {
var rect = Rectangle(3, 4, 20, 15); assert(rect.left == 3); rect.right = 12; assert(rect.left == -8); }
import
:通过 import 指定一个库命名空间中的内如如何在另一个库中使用- import 参数只需要一个指向库的 URI(URI 代表统一资源标识符)
- 对于内置库,URI 拥有自己特殊的dart: 方案
- 对于其他的库,使用系统文件路径或者 package: 方案
- package: 方案指定由包管理器(如 pub 工具)提供的库
- 如果导入两个存在冲突标识符的库, 则可以为这两个库,或者其中一个指定前缀
// Dart Web应用程序通常使用 dart:html 库,它们可以像这样导入: import 'dart:html'; // 指定库前缀 import 'package:lib1/lib1.dart'; import 'package:lib2/lib2.dart' as lib2; // 使用 lib1 中的 Element。 Element element1 = Element(); // 使用 lib2 中的 Element。 lib2.Element element2 = lib2.Element();
hide
和show
:如果你只使用库的一部分功能,则可以选择需要导入的 内容
// Import only foo. import 'package:lib1/lib1.dart' show foo; // Import all names EXCEPT foo. import 'package:lib2/lib2.dart' hide foo;
in
:- for-in 进行迭代操作
interface
:Yes. The interface keyword was removed from Dart. Instead all classes have implicit interfaces. So if you want to define an interface you can use an abstract class instead.
library
:库和可见性- import 和 library 指令可以用来创建一个模块化的,可共享的代码库。
- 库不仅提供了 API ,而且对代码起到了封装的作用: 以下划线 (_) 开头的标识符仅在库内可见。
- 每个 Dart 应用程序都是一个库 ,虽然没有使用 library 指令
- 库可以通过包(Package)来分发,pub(集成在SDK中的包管理器)
mixin
、with
:- Dart 是一种基于类和 mixin 继承机制的面向对象的语言;
- Mixin 提供了复用类代码的一种途径, 复用的类可以在不同层级,之间可以不存在继承关系。
- 通过
with
后面跟一个或多个混入的名称,来 使用 Mixin - 通过创建一个继承自 Object 且没有构造函数的类,来 实现 一个 Mixin 。如果 Mixin 不希望作为常规类被使用,使用关键字
mixin
替换 class; - 指定只有某些类型可以使用的 Mixin - 比如, Mixin 可以调用 Mixin 自身没有定义的方法 - 使用
on
来指定可以使用 Mixin 的父类类型;
// 通过 with 后面跟一个或多个混入的名称,来 使用 Mixin class Maestro extends Person with Musical, Aggressive, Demented {
Maestro(String maestroName) {
name = maestroName; canConduct = true; } } // Mixin mixin Musical {
bool canPlayPiano = false; bool canCompose = false; bool canConduct = false; void entertainMe() {
if (canPlayPiano) {
print('Playing piano'); } else if (canConduct) {
print('Waving hands'); } else {
print('Humming to self'); } } } // Musician是一个类, MusicalPerformer这个mixin 通过on关键字 就可以调用类的方法 mixin MusicalPerformer on Musician {
// ··· }
mixin 关键字在 Dart 2.1 中被引用支持
new
:- 构造函数 创建对象。 构造函数的名字可以是
ClassName
或者ClassName.identifier
(这可能是一个工厂函数); - 构造函数前面的的
new
关键字是可选的 - 一些类提供了常量构造函数。 使用常量构造函数,在构造函数名之前加
const
关键字,来创建编译时常量; - 在 常量上下文 中, 构造函数或者字面量前的
const
可以省略;
- 构造函数 创建对象。 构造函数的名字可以是
// 例如, 以下代码使用 Point 和 Point.fromJson() 构造函数创建 Point 对象: var p1 = Point(2, 2); var p2 = Point.fromJson({
'x': 1, 'y': 2}); var p1 = new Point(2, 2); var p2 = new Point.fromJson({
'x': 1, 'y': 2}); // 常量构造函数 var p = const ImmutablePoint(2, 2); // 构造两个相同的编译时常量会产生一个唯一的, 标准的实例 var a = const ImmutablePoint(1, 1); var b = const ImmutablePoint(1, 1); assert(identical(a, b)); // 它们是同一个实例。 // 这里有很多的 const 关键字。 const pointAndLine = const {
'point': const [const ImmutablePoint(0, 0)], 'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)], }; // 保留第一个 const 关键字,其余的全部省略: // 仅有一个 const ,由该 const 建立常量上下文。 const pointAndLine = {
'point': [ImmutablePoint(0, 0)], 'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)], };
null
:- 未初始化的变量默认值是
null
,即使变量是数字 类型默认值也是null
,因为在 Dart 中一切都是对象,数字类型 也不例外;
- 未初始化的变量默认值是
int lineCount; // 在生产环境代码中 assert() 函数会被忽略,不会被调用。 在开发过程中, assert(condition) 会在非 true 的条件下抛出异常 assert(lineCount == null);
on
:- 在捕获异常
try-catch
语句中,使用on
来指定异常类型,使用catch
来捕获异常对象; - 在
mixin
声明中,指定只有某些类型可以使用这个mixin
;
- 在捕获异常
operator
:操作符- 可以被重写的操作运算符:
<
、+
、|
、[]
、>
、/
、^
、[]=
、<=
、~/
、&
、~
、>=
、*
、<<
、==
、–
、%
、>>
- 如果要重写 == 操作符,需要重写对象的 hashCode getter 方法。
- 可以被重写的操作运算符:
你可能会被提示 != 运算符为非可重载运算符。 因为 e1 != e2 表达式仅仅是 !(e1 == e2) 的语法糖。
// 重写 + - class Vector {
final int x, y; Vector(this.x, this.y); Vector operator +(Vector v) => Vector(x + v.x, y + v.y); Vector operator -(Vector v) => Vector(x - v.x, y - v.y); // 运算符 == 和 hashCode 部分没有列出。 有关详情,请参考下面的代码段。 // ··· } void main() {
final v = Vector(2, 3); final w = Vector(2, 2); assert(v + w == Vector(4, 5)); assert(v - w == Vector(0, 1)); }
// 重写 == 操作符 class Person {
final String firstName, lastName; Person(this.firstName, this.lastName); // 重写 hashCode,实现策略源于 Effective Java, // 第11章。 @override int get hashCode {
int result = 17; result = 37 * result + firstName.hashCode; result = 37 * result + lastName.hashCode; return result; } // 如果重写了 hashCode,通常应该从新实现 == 操作符。 @override bool operator ==(dynamic other) {
if (other is! Person) return false; Person person = other; return (person.firstName == firstName && person.lastName == lastName); } } void main() {
var p1 = Person('Bob', 'Smith'); var p2 = Person('Bob', 'Smith'); var p3 = 'not a person'; assert(p1.hashCode == p2.hashCode); assert(p1 == p2); assert(p1 != p3); }
part
:You may have heard of the part directive, which allows you to split a library into multiple Dart files. We recommend that you avoid using part and create mini libraries instead.
return
:返回函数返回值;static
:使用static
关键字实现类范围的变量和方法- 静态变量(类变量)对于类级别的状态是非常有用的;
- 静态变量只到它们被使用的时候才会初始化;
- 静态方法(类方法)不能在实例上使用,因此它们不能访问
this
; - 静态函数可以当做编译时常量使用。 例如,可以将静态方法作为参数传递给常量构造函数。
// 静态变量 class Queue {
static const initialCapacity = 16; // ··· } void main() {
assert(Queue.initialCapacity == 16); } // 静态方法 import 'dart:math'; class Point {
num x, y; Point(this.x, this.y); static num distanceBetween(Point a, Point b) {
var dx = a.x - b.x; var dy = a.y - b.y; return sqrt(dx * dx + dy * dy); } } void main() {
var a = Point(2, 2); var b = Point(4, 4); var distance = Point.distanceBetween(a, b); assert(2.8 < distance && distance < 2.9); print(distance); }
对于常见或广泛使用的工具和函数, 应该考虑使用顶级函数而不是静态方法;
代码准守风格推荐指南中的命名规则, 使用 lowerCamelCase 来命名常量。
sync
yield
(sync*
、async*
):- 当您需要延迟生成( lazily produce )一系列值时, 可以考虑使用_生成器函数_。 Dart 内置支持两种生成器函数:
- Synchronous 生成器: 返回一个 Iterable 对象
- Asynchronous 生成器: 返回一个 Stream 对象
- 当您需要延迟生成( lazily produce )一系列值时, 可以考虑使用_生成器函数_。 Dart 内置支持两种生成器函数:
// 通过在函数体标记 sync*, 可以实现一个同步生成器函数。 使用 yield 语句来传递值 Iterable<int> naturalsTo(int n) sync* {
int k = 0; while (k < n) yield k++; } // 通过在函数体标记 async*, 可以实现一个异步生成器函数。 使用 yield 语句来传递值: Stream<int> asynchronousNaturalsTo(int n) async* {
int k = 0; while (k < n) yield k++; } // 如果生成器是递归的,可以使用 yield* 来提高其性能: Iterable<int> naturalsDownFrom(int n) sync* {
if (n > 0) {
yield* naturalsDownFrom(n - 1); } else {
yield n; } }
typedef
:- 在 Dart 中,函数也是对象,就想字符和数字对象一样。 使用
typedef
,或者function-type alias
为函数起一个别名, 别名可以用来声明字段及返回值类型。 当函数类型分配给变量时,typedef会保留类型信息。
- 在 Dart 中,函数也是对象,就想字符和数字对象一样。 使用
// 未使用typedef class SortedCollection {
Function compare; SortedCollection(int f(Object a, Object b)) {
compare = f; } } // Initial, broken implementation. // broken ? int sort(Object a, Object b) => 0; void main() {
SortedCollection coll = SortedCollection(sort); // 虽然知道 compare 是函数, // 但是函数是什么类型 ? // 当把 f 赋值给 compare 的时候,类型信息丢失了。 f 的类型是 (Object, Object) → int (这里 → 代表返回值类型), 但是 compare 得到的类型是 Function assert(coll.compare is Function); }
目前,typedefs 只能使用在函数类型上
// 使用typedef为函数起一个别名 typedef Compare = int Function(Object a, Object b); class SortedCollection {
Compare compare; SortedCollection(this.compare); } // Initial, broken implementation. int sort(Object a, Object b) => 0; void main() {
SortedCollection coll = SortedCollection(sort); assert(coll.compare is Function); assert(coll.compare is Compare); } // 判断任意函数的类型 typedef Compare<T> = int Function(T a, T b); int sort(int a, int b) => a - b; void main() {
assert(sort is Compare<int>); // True! }
var
:创建一个变量,初始化之后,变量仅存储对象引用;void
:函数无返回值的类型描述;如果不是void声明的函数返回值类型,那么函数就一定有返回值,实际上所有函数都会返回一个值。 如果没有明确指定返回值, 函数体会被隐式的添加 return null; 语句;
Dart 内建数据类型
支持的内建类型:
- Number
- String
- Boolean
- List(也被称为Array)
- Map
- Set
- Rune(用于在字符串中表示Unicode字符)
- Symbol
这些类型都可以被初始化为字面量;
因为在 Dart 所有的变量终究是一个对象(一个类的实例), 所以变量可以使用 构造涵数 进行初始化。 一些内建类型拥有自己的构造函数。 例如, 通过 Map()
来构造一个 map 变量。
几个参考链接,便于使用查阅:
- Dart List操作
- Dart Map操作
- Dart Set文档
- Dart 集合 API
Number
Dart 语言的 Number 有两种类型:
int
:整数值不大于64位, 具体取决于平台。double
:64位(双精度)浮点数,依据 IEEE 754 标准
int
和 double
都是 num
. 的亚类型
num
类型包括基本运算+
,-
,/
, 和*
, 以及abs()
,ceil()
, 和floor()
, 等函数方法。- 如果
num
及其亚类型找不到你想要的方法, 尝试查找使用dart:math
库。 - 按位运算符,例如
»
,定义在int
类中。int 特有的传统按位运算操作,移位(<<, >>),按位与(&)以及 按位或(|)。
var x = 1; var hex = 0xDEADBEEF; var y = 1.1; var exponents = 1.42e5; // 从 Dart 2.1 开始,必要的时候 int 字面量会自动转换成 double 类型 double z = 1; // 相当于 double z = 1.0 assert((3 << 1) == 6); // 0011 << 1 == 0110 assert((3 >> 1) == 1); // 0011 >> 1 == 0001 assert((3 | 4) == 7); // 0011 | 0100 == 0111 // 数字类型字面量是编译时常量。 在算术表达式中,只要参与计算的因子是编译时常量, 那么算术表达式的结果也是编译时常量 const msPerSecond = 1000; const secondsUntilRetry = 5; const msUntilRetry = secondsUntilRetry * msPerSecond;
字符串 与 数字 的相互转换:
// String -> int var one = int.parse('1'); assert(one == 1); // String -> double var onePointOne = double.parse('1.1'); assert(onePointOne == 1.1); // int -> String String oneAsString = 1.toString(); assert(oneAsString == '1'); // double -> String String piAsString = 3.14159.toStringAsFixed(2); assert(piAsString == '3.14');
String
Dart 字符串是一组 UTF-16
单元序列:
- 字符串通过单引号或者双引号创建。
- 字符串可以通过
${expression}
的方式内嵌表达式,如果表达式是一个标识符,则 {} 可以省略,如$var
。 - 在 Dart 中通过调用就对象的 toString() 方法来得到对象相应的字符串
- 可以使用 + 运算符来把多个字符串连接为一个(把多个字面量字符串写在一起也可以实现字符串连接);
- 使用连续三个单引号或者三个双引号实现多行字符串对象的创建:
- 使用 r 前缀,可以创建 “原始 raw” 字符串:
== 运算符用来测试两个对象是否相等。 在字符串中,如果两个字符串包含了相同的编码序列,那么这两个字符串相等
一个编译时常量的字面量字符串中,如果存在插值表达式,表达式内容也是编译时常量, 那么该字符串依旧是编译时常量。 插入的常量值类型可以是 null,数值,字符串或布尔值:
// const 类型数据 const aConstNum = 0; const aConstBool = true; const aConstString = 'a constant string'; // 非 const 类型数据 var aNum = 0; var aBool = true; var aString = 'a string'; const aConstList = [1, 2, 3]; const validConstString = '$aConstNum $aConstBool $aConstString'; //const 类型数据 // const invalidConstString = '$aNum $aBool $aString $aConstList'; //非 const 类型数据
字符常用方法和正则表达式:
- 使用正则表达式 (RegExp 对象) 可以在字符串内搜索和替换部分字符串
- String 定义了例如
split()
,contains()
,startsWith()
,endsWith()
等方法 - 字符串是不可变的对象,也就是说字符串可以创建但是不能被修改,例如,方法 replaceAll() 返回一个新字符串, 并没有改变原始字符串;
- 具体参看如下代码示例;
// 检查一个字符串是否包含另一个字符串。 assert('Never odd or even'.contains('odd')); // 一个字符串是否以另一个字符串为开头? assert('Never odd or even'.startsWith('Never')); // 一个字符串是否以另一个字符串为结尾? assert('Never odd or even'.endsWith('even')); // 查找一个字符串在另一个字符串中的位置。 assert('Never odd or even'.indexOf('odd') == 6); // 抓取一个子字符串。 assert('Never odd or even'.substring(6, 9) == 'odd'); // 使用字符串模式分割字符串。 var parts = 'structured web apps'.split(' '); assert(parts.length == 3); assert(parts[0] == 'structured'); // 通过下标获取 UTF-16 编码单元(编码单元作为字符串)。 assert('Never odd or even'[0] == 'N'); // 使用 split() 传入一个空字符串参数, // 得到一个所有字符的 list 集合; // 有助于字符迭代。 for (var char in 'hello'.split('')) {
print(char); } // 获取一个字符串的所有 UTF-16 编码单元。 var codeUnitList = 'Never odd or even'.codeUnits.toList(); assert(codeUnitList[0] == 78); // 转换为首字母大写。 assert('structured web apps'.toUpperCase() == 'STRUCTURED WEB APPS'); // 转换为首字母小写。 assert('STRUCTURED WEB APPS'.toLowerCase() == 'structured web apps'); // Trim a string. assert(' hello '.trim() == 'hello'); // 检查字符串是否为空。 assert(''.isEmpty); // 空格字符串不是空字符串。 assert(' '.isNotEmpty); // 替换部分字符串 var greetingTemplate = 'Hello, NAME!'; var greeting = greetingTemplate.replaceAll(RegExp('NAME'), 'Bob'); // greetingTemplate 没有改变。 assert(greeting != greetingTemplate); // 要以代码方式生成字符串,可以使用 StringBuffer // 在调用 toString() 之前, StringBuffer 不会生成新字符串对象。 writeAll() 的第二个参数为可选参数,用来指定分隔符, 本例中使用空格作为分隔符。 var sb = StringBuffer(); sb ..write('Use a StringBuffer for ') ..writeAll(['efficient', 'string', 'creation'], ' ') ..write('.'); // .. 是 级联运算符 var fullString = sb.toString(); assert(fullString == 'Use a StringBuffer for efficient string creation.'); // RegExp类提供与JavaScript正则表达式相同的功能。 使用正则表达式可以对字符串进行高效搜索和模式匹配。 // 下面正则表达式用于匹配一个或多个数字。 var numbers = RegExp(r'\d+'); var allCharacters = 'llamas live fifteen to twenty years'; var someDigits = 'llamas live 15 to 20 years'; // contains() 能够使用正则表达式。 assert(!allCharacters.contains(numbers)); assert(someDigits.contains(numbers)); // 替换所有匹配对象为另一个字符串。 var exedOut = someDigits.replaceAll(numbers, 'XX'); assert(exedOut == 'llamas live XX to XX years'); // 你也可以直接使用RegExp类。 Match 类提供对正则表达式匹配对象的访问。 var numbers = RegExp(r'\d+'); var someDigits = 'llamas live 15 to 20 years'; // 检查正则表达式是否在字符串中匹配到对象。 assert(numbers.hasMatch(someDigits)); // 迭代所有匹配对象 for (var match in numbers.allMatches(someDigits)) {
print(match.group(0)); // 15, then 20 }
Boolean
Dart 使用 bool
类型表示布尔值。 Dart 只有字面量 true
and false
是布尔类型, 这两个对象都是编译时常量。
Dart 的类型安全意味着不能使用 if (nonbooleanValue)
或者 assert (nonbooleanValue)
。 而是应该像下面这样,明确的进行值检查:
// 检查空字符串。 var fullName = ''; assert(fullName.isEmpty); // 检查 0 值。 var hitPoints = 0; assert(hitPoints <= 0); // 检查 null 值。 var unicorn; assert(unicorn == null); // 检查 NaN 。 var iMeantToDoThis = 0 / 0; assert(iMeantToDoThis.isNaN);
List
在 Dart 中的 Array 就是 List 对象, 通常称之为 List 。
- Dart 集合 API
var list = [1, 2, 3];
- Lists 的下标索引从 0 开始,第一个元素的索引是 0。 list.length - 1 是最后一个元素的索引
var list = [1, 2, 3]; assert(list.length == 3); assert(list[1] == 2); list[1] = 1; assert(list[1] == 1); // 在 List 字面量之前添加 const 关键字,可以定义 List 类型的编译时常量 var constantList = const [1, 2, 3]; // constantList[1] = 1; // 取消注释会引起错误。 // 使用 List 构造函数。 var vegetables = List(); // 或者仅使用一个 list 字面量。 var fruits = ['apples', 'oranges']; // 添加一个元素到 list 对象。 fruits.add('kiwis'); // 添加多个元素到 list 对象。 fruits.addAll(['grapes', 'bananas']); // 获取 list 长度。 assert(fruits.length == 5); // 移除一个元素到 list 对象。 var appleIndex = fruits.indexOf('apples'); fruits.removeAt(appleIndex); assert(fruits.length == 4); // 移除多个元素到 list 对象。 fruits.clear(); assert(fruits.length == 0); // 使用 sort() 方法排序一个 list // 下面示例中使用 compareTo() 函数, 该函数在 Comparable 中定义, 并被 String 类实现 var fruits = ['bananas', 'apples', 'oranges']; // 排序一个 list 。 fruits.sort((a, b) => a.compareTo(b)); assert(fruits[0] == 'apples'); // list 是参数化类型, 因此可以指定 list 应该包含的元素类型 // 这个 list 只能包含字符串类型。 var fruits = List<String>(); fruits.add('apples'); var fruit = fruits[0]; assert(fruit is String); // 产生静态分析警告,num 不是字符串类型。 fruits.add(5); // BAD: Throws exception in checked mode.
Set
在 Dart 中 Set 是一个元素唯一且无需的集合。
- Dart 为 Set 提供了 Set 字面量和 Set 类型
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};
,Dart 推断 halogens 类型为 Set 。如果尝试为它添加一个 错误类型的值,分析器或执行时会抛出错误;- 要创建一个空集,使用前面带有类型参数的 {} ,或者将 {} 赋值给 Set 类型的变量;
虽然 Set 类型 一直是 Dart 的核心部分, 但在 Dart2.2 中才引入了 Set 字面量 。
var names = <String>{
}; // Set<String> names = {}; // 这样也是可以的。 // var names = {}; // 这样会创建一个 Map ,而不是 Set 这会创建一个类型为 Map<dynamic, dynamic> 的对象; var ingredients = Set(); ingredients.addAll(['gold', 'titanium', 'xenon']); assert(ingredients.length == 3); // 添加一个重复的元素是无效的。 ingredients.add('gold'); assert(ingredients.length == 3); // 从 set 中移除一个元素。 ingredients.remove('gold'); assert(ingredients.length == 2); // 在 Set 字面量前增加 const ,来创建一个编译时 Set 常量: final constantSet = const {
'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine', }; // 使用 contains() 和 containsAll() 来检查一个或多个元素是否在 set 中。 var ingredients = Set(); ingredients.addAll(['gold', 'titanium', 'xenon']); // 检查一个元素是否在该 set 中。 assert(ingredients.contains('titanium')); // 检查多个元素是否在该 set 中。 assert(ingredients.containsAll(['titanium', 'xenon'])); // 交集是另外两个 set 中的公共元素组成的 set var ingredients = Set(); ingredients.addAll(['gold', 'titanium', 'xenon']); // 创建两个 set 的交集。 var nobleGases = Set.from(['xenon', 'argon']); var intersection = ingredients.intersection(nobleGases); assert(intersection.length == 1); assert(intersection.contains('xenon'));
Map
通常来说, Map 是用来关联 keys 和 values 的对象:
- keys 和 values 可以是任何类型的对象。
- 在一个 Map 对象中一个 key 只能出现一次。 但是 value 可以出现多次。
- Dart 中 Map 通过 Map 字面量 和 Map 类型来实现。
- 如果 Map 中不包含所要查找的 key,那么 Map 返回
null
- 使用
.length
函数获取当前 Map 中的 key-value 对数量 - 创建 Map 类型运行时常量,要在 Map 字面量前加上关键字 const
- 使用 remove() 方法从 map 中移除键值对
var gifts = {
// Key: Value 'first': 'partridge', 'second': 'turtledoves', 'fifth': 'golden rings' }; var nobleGases = {
2: 'helium', 10: 'neon', 18: 'argon', }; var gifts = Map(); gifts['first'] = 'partridge'; gifts['second'] = 'turtledoves'; gifts['fifth'] = 'golden rings'; var nobleGases = Map(); nobleGases[2] = 'helium'; nobleGases[10] = 'neon'; nobleGases[18] = 'argon';
上面代码,Dart 会将 gifts 的类型推断为 Map<String, String>, nobleGases 的类型推断为 Map<int, String> 。 如果尝试在上面的 map 中添加错误类型,那么分析器或者运行时会引发错误
// map 是参数化类型; // 可以指定一个 map 中 key 和 value 的类型。 var nobleGases = Map<int, String>(); // var nobleGases = {
54: 'xenon'}; // 使用 key 检索 value 。 assert(nobleGases[54] == 'xenon'); // 检查 map 是否包含 key 。 assert(nobleGases.containsKey(54)); // 移除一个 key 及其 value。 nobleGases.remove(54); assert(!nobleGases.containsKey(54)); var hawaiianBeaches = {
'Oahu': ['Waikiki', 'Kailua', 'Waimanalo'], 'Big Island': ['Wailea Bay', 'Pololu Beach'], 'Kauai': ['Hanalei', 'Poipu'] }; // 获取的所有的 key 是一个无序集合 // (可迭代 list 对象)。 var keys = hawaiianBeaches.keys; assert(keys.length == 3); assert(Set.from(keys).contains('Oahu')); // 获取的所有的 value 是一个无序集合 // (可迭代 list 对象). var values = hawaiianBeaches.values; assert(values.length == 3); assert(values.any((v) => v.contains('Waikiki'))); assert(hawaiianBeaches.containsKey('Oahu')); assert(!hawaiianBeaches.containsKey('Florida'));
公共集合方法
List, Set, 和 Map 共享许多集合中的常用功能。 其中一些常见功能由 Iterable 类定义, 这些函数由 List 和 Set 实现。
虽然Map没有实现 Iterable, 但可以使用 Map keys 和 values 属性从中获取 Iterable 对象。
var coffees = []; var teas = ['green', 'black', 'chamomile', 'earl grey']; assert(coffees.isEmpty); assert(teas.isNotEmpty); var teas = ['green', 'black', 'chamomile', 'earl grey']; teas.forEach((tea) => print('I drink $tea')); // 当在 map 对象上调用 `forEach() 方法时,函数必须带两个参数(key 和 value) hawaiianBeaches.forEach((k, v) {
print('I want to visit $k and swim at $v'); // 我想去瓦胡岛并且在 // [Waikiki, Kailua, Waimanalo]游泳, 等等。 }); var teas = ['green', 'black', 'chamomile', 'earl grey']; // map() 方法返回的对象是一个 懒求值(lazily evaluated)对象: 只有当访问对象里面的元素时,函数才会被调用。 var loudTeas = teas.map((tea) => tea.toUpperCase()); loudTeas.forEach(print); // 使用 map().toList() 或 map().toSet() , 可以强制在每个项目上立即调用函数。 var loudTeas = teas.map((tea) => tea.toUpperCase()).toList(); // 使用 Iterable 的 where() 方法可以获取所有匹配条件的元素。 使用 Iterable 的 any() 和 every() 方法可以检查部分或者所有元素是否匹配某个条件。 var teas = ['green', 'black', 'chamomile', 'earl grey']; // 洋甘菊不含咖啡因。 bool isDecaffeinated(String teaName) => teaName == 'chamomile'; // 使用 where() 来查找元素, // 这些元素在给定的函数中返回 true 。 var decaffeinatedTeas = teas.where((tea) => isDecaffeinated(tea)); // 或者 teas.where(isDecaffeinated) // 使用 any() 来检查集合中是否至少有一个元素满足条件。 assert(teas.any(isDecaffeinated)); // 使用 every() 来检查集合中是否所有元素满足条件。 assert(!teas.every(isDecaffeinated)); // 如果当且仅当该 key 不存在于 map 中,且要为这个 key 赋值, 可使用putIfAbsent()方法。 该方法需要一个方法返回这个 value var teamAssignments = {
}; teamAssignments.putIfAbsent( 'Catcher', () => pickToughestKid()); assert(teamAssignments['Catcher'] != null);
Rune
Rune 用来表示字符串中的 UTF-32 编码字符
Unicode 定义了一个全球的书写系统编码, 系统中使用的所有字母,数字和符号都对应唯一的数值编码。 由于 Dart 字符串是一系列 UTF-16 编码单元, 因此要在字符串中表示32位 Unicode 值需要特殊语法支持。
表示 Unicode 编码的常用方法是,
\uXXXX
, 这里 XXXX 是一个4位的16进制数。 例如,心形符号 (♥
) 是\u2665
。 对于特殊的非 4 个数值的情况, 把编码值放到大括号中即可。 例如,emoji 的笑脸 (�
) 是\u{1f600}
。
String 类有一些属性可以获得 rune 数据。 属性 codeUnitAt 和 codeUnit 返回16位编码数据。 属性 runes 获取字符串中的 Rune 。
下面是示例演示了 Rune 、 16-bit code units、 和 32-bit code points 之间的关系:
main() {
var clapping = '\u{1f44f}'; print(clapping); print(clapping.codeUnits); print(clapping.runes.toList()); Runes input = new Runes( '\u2665 \u{1f605} \u{1f60e} \u{1f47b} \u{1f596} \u{1f44d}'); print(new String.fromCharCodes(input)); } // console 👏 [55357, 56399] [] ♥ 😅 😎 👻 🖖 👍
谨慎使用 list 方式操作 Rune 。 这种方法很容易引发崩溃, 具体原因取决于特定的语言,字符集和操作;
Symbol
一个 Symbol 对象表示 Dart 程序中声明的运算符或者标识符。
- 你也许永远都不需要使用 Symbol ,但要按名称引用标识符的 API 时, Symbol 就非常有用了。
- 因为代码压缩后会改变标识符的名称,但不会改变标识符的符号。
- 通过字面量 Symbol ,也就是标识符前面添加一个
#
号,来获取标识符的 Symbol 。 - Symbol 字面量是编译时常量
Symbol obj = new Symbol('name'); Symbol obj = #radix import 'dart:mirrors'; void main(){
Symbol lib = new Symbol("foo_lib"); String name_of_lib = MirrorSystem.getName(lib); print(lib); print(name_of_lib); } // log: // Symbol("foo_lib") // foo_lib
函数——一等对象
Dart 是一门真正面向对象的语言, 甚至其中的函数也是对象,并且有它的类型 Function
// 虽然在 Effective Dart 中推荐 公共API中声明类型, 但是省略了类型声明,函数依旧是可以正常使用的 isNoble(atomicNumber) {
return _nobleGases[atomicNumber] != null; } // 箭头 语法:=> expr 语法是 { return expr; } 的简写 bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null; // 命名可选参数:使用 {param1, param2, …} 来指定命名参数 void enableFlags({
bool bold, bool hidden}) {
...} // 使用 @required 注释表示参数是 required 性质的命名参数 const Scrollbar({
Key key, @required Widget child}) // 位置可选参数:将参数放到 [] 中来标记参数是可选的 String say(String from, String msg, [String device]) {
...} // 默认参数值 void enableFlags({
bool bold = false, bool hidden = false}) {
...} // 为位置参数设置默认值 String say(String from, String msg, [String device = 'carrier pigeon', String mood]) {
...} // list 或 map 可以作为默认值传递 void doStuff( {
List<int> list = const [1, 2, 3], Map<String, String> gifts = const {
'first': 'paper', 'second': 'cotton', 'third': 'leather' }}) {
...} // main() 函数 // 任何应用都必须有一个顶级 main() 函数,作为应用服务的入口。 main() 函数返回值为空,参数为一个可选的 List<String> void main(List<String> arguments){
...} // 匿名函数 // 有时候也被称为 lambda 或者 closure ([[Type] param1[, …]]) {
codeBlock; }; var list = ['apples', 'bananas', 'oranges']; list.forEach((item) {
print('${list.indexOf(item)}: $item'); }); // 如果函数只有一条语句, 可以使用箭头简写 list.forEach( (item) => print('${list.indexOf(item)}: $item'));
测试函数是否相等:
void foo() {
} // 顶级函数 class A {
static void bar() {
} // 静态方法 void baz() {
} // 示例方法 } void main() {
var x; // 比较顶级函数。 x = foo; assert(foo == x); // 比较静态方法。 x = A.bar; assert(A.bar == x); // 比较实例方法。 var v = A(); // A的1号实例 var w = A(); // A的2号实例 var y = w; x = w.baz; // 两个闭包引用的同一实例(2号), // 所以它们相等。 assert(y.baz == x); // 两个闭包引用的非同一个实例, // 所以它们不相等。 assert(v.baz != w.baz); }
词法作用域和词法闭包
词法作用域:
- Dart 是一门词法作用域的编程语言,就意味着变量的作用域是固定的, 简单说变量的作用域在编写代码的时候就已经确定了。 花括号内的是变量可见的作用域。
词法闭包:
- 闭包 即一个函数对象,即使函数对象的调用在它原始作用域之外, 依然能够访问在它词法作用域内的变量
// makeAdder() 捕获了变量 addBy /// 返回一个函数,返回的函数参数与 [addBy] 相加。 Function makeAdder(num addBy) {
return (num i) => addBy + i; } void main() {
// 创建一个加 2 的函数。 var add2 = makeAdder(2); // 创建一个加 4 的函数。 var add4 = makeAdder(4); assert(add2(3) == 5); assert(add4(3) == 7); }
运算符
熟悉常用运算符即可:
- 运算符
- 算数运算符
- 关系运算符
- 类型判定运算符(参考关键字
as
、is
、is!
) - 赋值运算符
- 复合赋值运算符((如 += )将算术运算符和赋值运算符组合在了一起)
=
、–=
、/=
、%=
、>>=
、^=
、+=
、*=
、~/=
、<<=
、&=
、|=
- 逻辑运算符:
!done && (col == 0 || col == 3)
- 按位和移位运算符
- 条件表达式
- 级联运算符(
..
):..
语法为 级联调用 (cascade)。 使用级联调用, 可以简化在一个对象上执行的多个操作。 - 其他运算符
对于有两个操作数的运算符,运算符的功能由左边的操作数决定。 例如, 如果有两个操作数 Vector 和 Point, aVector + aPoint 使用的是 Vector 中定义的 + 运算符。
算数运算符使用举例:
assert(2 + 3 == 5); assert(2 - 3 == -1); assert(2 * 3 == 6); assert(5 / 2 == 2.5); // 结果是双浮点型 assert(5 ~/ 2 == 2); // 结果是整型 assert(5 % 2 == 1); // 余数 var a, b; a = 0; b = ++a; // a自加后赋值给b。 assert(a == b); // 1 == 1 a = 0; b = a++; // a先赋值给b后,a自加。 assert(a != b); // 1 != 0 a = 0; b = --a; // a自减后赋值给b。 assert(a == b); // -1 == -1 a = 0; b = a--; // a先赋值给b后,a自减。 assert(a != b); // -1 != 0
关系运算符-判等:
- 要测试两个对象x和y是否表示相同的事物, 使用
==
运算符 - 在极少数情况下, 要确定两个对象是否完全相同,需要使用 identical() 函数
==
运算符的工作原理:- 如果 x 或 y 可以 null,都为 null 时返回 true ,其中一个为 null 时返回 false。
- 结果为函数
x.==(y)
的返回值,==
运算符执行的是第一个运算符的函数。 我们甚至可以重写很多运算符;
赋值运算符:
- 使用 = 为变量赋值。 使用 ??= 运算符时,只有当被赋值的变量为 null 时才会赋值给它
// 将值赋值给变量a a = value; // 如果b为空时,将变量赋值给b,否则,b的值保持不变。 b ??= value; // 使用赋值和复合赋值运算符 var a = 2; // 使用 = 复制 a *= 3; // 复制并做乘法运算: a = a * 3 assert(a == 6);
按位和移位运算符:
final value = 0x22; final bitmask = 0x0f; assert((value & bitmask) == 0x02); // AND assert((value & ~bitmask) == 0x20); // AND NOT assert((value | bitmask) == 0x2f); // OR assert((value ^ bitmask) == 0x2d); // XOR assert((value << 4) == 0x220); // Shift left assert((value >> 4) == 0x02); // Shift right
条件表达式:
// 如果条件为 true, 执行 expr1 (并返回它的值): 否则, 执行并返回 expr2 的值 condition ? expr1 : expr2 // 如果 expr1 是 non-null, 返回 expr1 的值; 否则, 执行并返回 expr2 的值 expr1 ?? expr2
级联运算符(..
):
- 级联运算符 (
..
) 可以实现对同一个对像进行一系列的操作 - 除了调用函数, 还可以访问同一对象上的字段属性。 这通常可以节省创建临时变量的步骤, 同时编写出更流畅的代码。
querySelector('#confirm') // 获取对象 获取的对象依次执行级联运算符后面的代码 代码执行后的返回值会被忽略 ..text = 'Confirm' // 调用成员变量。 ..classes.add('important') ..onClick.listen((e) => window.alert('Confirmed!')); // 代码等价于 var button = querySelector('#confirm'); button.text = 'Confirm'; button.classes.add('important'); button.onClick.listen((e) => window.alert('Confirmed!')); // 级联运算符可以嵌套 final addressBook = (AddressBookBuilder() ..name = 'jenny' ..email = 'jenny@example.com' ..phone = (PhoneNumberBuilder() ..number = '415-555-0100' ..label = 'home') .build()) .build(); // 在返回对象的函数中谨慎使用级联操作符 var sb = StringBuffer(); sb.write('foo') // sb.write() 函数调用返回 void, 不能在 void 对象上创建级联操作 ..write('bar'); // Error: 'void' 没哟定义 'write' 函数。
严格的来讲, “两个点” 的级联语法不是一个运算符。 它只是一个 Dart 的特殊语法。
其他运算符:
()
:函数调用[]
:获取List指定索引的值.
:对象成员获取,如foo.bar
?.
:带条件的对象成员获取,如foo?.bar
foo?.bar
selects propertybar
from expression foo unlessfoo
isnull
;
控制流程语句
参考相关关键字:if
、else
、for
、while
、do-while
、break
、continue
、switch
、case
、assert
,使用try-catch
和throw
也可以改变程序流程;
异常
Dart 代码可以抛出和捕获异常。 异常表示一些未知的错误情况。 如果异常没有被捕获, 则异常会抛出, 导致抛出异常的代码终止执行:
- 和 Java 有所不同, Dart 中的所有异常是非检查异常。 方法不会声明它们抛出的异常, 也不要求捕获任何异常。
- Dart 提供了
Exception
和Error
类型, 以及一些子类型;当然也可以定义自己的异常类型。 - 此外 Dart 程序可以抛出任何
非 null 对象
, 不仅限Exception
和Error
对象。 - 抛出异常是一个表达式, 所以可以在 => 语句中使用
参考相关关键字:try
、catch
、rethrow
、on
、finally
- 使用 on 来指定异常类型, 使用 catch 来 捕获异常对象;
- 不管是否抛出异常, finally 中的代码都会被执行;
- 如果 catch 没有匹配到异常, 异常会在 finally 执行完成后,再次被抛出;
- 任何匹配的 catch 执行完成后,再执行 finally
try {
} on OutOfLlamasException {
// 指定具体异常 } on Exception catch (e) {
// 指定异常 } catch (e) {
// 没有指定的类型 }finally {
// Always clean up, even if an exception is thrown. } try {
// ··· } on Exception catch (e) {
} catch (e, s) {
print('Exception details:\n $e'); print('Stack trace:\n $s'); rethrow; }
类
Dart 是一种基于类和 mixin 继承机制的面向对象的语言。 每个对象都是一个类的实例,所有的类都继承于 Object.
类的基本使用
var p = Point(2, 2); p.y = 3; num distance = p.distanceTo(Point(4, 4)); p?.y = 4; var p1 = Point(2, 2); var p2 = Point.fromJson({
'x': 1, 'y': 2}); // Dart 2 中 new是可选的 var p1 = new Point(2, 2); var p2 = new Point.fromJson({
'x': 1, 'y': 2}); // 常量构造函数:在构造函数名之前加 const 关键字,来创建编译时常量时 var p = const ImmutablePoint(2, 2); var a = const ImmutablePoint(1, 1); var b = const ImmutablePoint(1, 1); assert(identical(a, b)); // 它们是同一个实例 // 这里有很多的 const 关键字。 const pointAndLine = const {
'point': const [const ImmutablePoint(0, 0)], 'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)], }; // 保留第一个 const 关键字,其余的全部省略:在 常量上下文 中, 构造函数或者字面量前的 const 可以省略 // 仅有一个 const ,由该 const 建立常量上下文。 const pointAndLine = {
'point': [ImmutablePoint(0, 0)], 'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)], };
获取对象的类型
使用对象的 runtimeType
属性, 可以在运行时获取对象的类型, runtimeType
属性回返回一个 Type
对象。
a.runtimeType print('The type of a is ${a.runtimeType}');
类的结构
class Point {
num x; // 声明示例变量 x,初始值为 null 。 num y; // 声明示例变量 y,初始值为 null 。 num z = 0; // 声明示例变量 z,初始值为 0 。 Point(num x, num y) {
// 还有更好的方式来实现下面代码。 this.x = x; this.y = y; } } class Point {
num x, y; // 在构造函数体执行前, // 语法糖已经设置了变量 x 和 y。 Point(this.x, this.y); }
默认构造函数:
- 在没有声明构造函数的情况下, Dart 会提供一个默认的构造函数。 默认构造函数没有参数并会调用父类的无参构造函数。
- 子类不会继承父类的构造函数。 子类不声明构造函数,那么它就只有默认构造函数 (匿名,没有参数) ,构造函数不会被继承;
命名构造函数:
- 使用命名构造函数可为一个类实现多个构造函数;
- 构造函数不能够被继承, 这意味着父类的命名构造函数不会被子类继承。 如果希望使用父类中定义的命名构造函数创建子类, 就必须在子类中实现该构造函数;
class Point {
num x, y; Point(this.x, this.y); // 命名构造函数 Point.origin() {
x = 0; y = 0; } }
调用父类非默认构造函数:
- 默认情况下,子类的构造函数会自动调用父类的默认构造函数(匿名,无参数)。 父类的构造函数在子类构造函数体开始执行的位置被调用。 如果提供了一个 initializer list (初始化参数列表), 则初始化参数列表在父类构造函数执行之前执行。
- initializer list (
初始化参数列表
) - superclass’s no-arg constructor (父类的无名构造函数)
- main class’s no-arg constructor (主类的无名构造函数)
- initializer list (
- 如果父类中没有匿名无参的构造函数, 则需要手工调用父类的其他构造函数。 在当前构造函数冒号 (
:
) 之后,函数体之前,声明调用父类构造函数。- 调用父类构造函数的参数无法访问 this
class Person {
String firstName; Person.fromJson(Map data) {
print('in Person'); } } class Employee extends Person {
// Person does not have a default constructor; // you must call super.fromJson(data). // super.fromJson(data)的参数data可以是一个表达式或者一个方法调用 Employee.fromJson(Map data) : super.fromJson(data) {
print('in Employee'); } } main() {
var emp = new Employee.fromJson({
}); // Prints: // in Person // in Employee if (emp is Person) {
// Type check emp.firstName = 'Bob'; } (emp as Person).firstName = 'Bob'; }
初始化列表:
- 除了调用超类构造函数之外, 还可以在构造函数体执行之前初始化实例变量
- 各参数的初始化用逗号分隔。
- 在开发期间, 可以使用 assert 来验证输入的初始化列表。
- 使用初始化列表可以很方便的设置 final 字段
// 在构造函数体执行之前, // 通过初始列表设置实例变量。 Point.fromJson(Map<String, num> json) : x = json['x'], y = json['y'] {
print('In Point.fromJson(): ($x, $y)'); } // 语法糖已经设置了变量 x 和 y。 Point.withAssert(this.x, this.y) : assert(x >= 0) {
print('In Point.withAssert(): ($x, $y)'); } // 使用初始化列表可以很方便的设置 final 字段 class Point {
final num x; final num y; final num distanceFromOrigin; Point(x, y) : x = x, y = y, distanceFromOrigin = sqrt(x * x + y * y); } main() {
var p = new Point(2, 3); print(p.distanceFromOrigin); }
其他构造函数:
- 重定向构造函数:有时构造函数的唯一目的是重定向到同一个类中的另一个构造函数。 重定向构造函数的函数体为空, 构造函数的调用在冒号 (
:
) 之后。 - 常量构造函数:如果该类生成的对象是固定不变的, 那么就可以把这些对象定义为编译时常量。 为此,需要定义一个
const
构造函数, 并且声明所有实例变量为final
- 工厂构造函数:当执行构造函数并不总是创建这个类的一个新实例时,则使用
factory
关键字(工厂构造函数无法访问this
);
// 重定向构造函数 class Point {
num x, y; // 类的主构造函数。 Point(this.x, this.y); // 指向主构造函数 Point.alongXAxis(num x) : this(x, 0); } // 常量构造函数 class ImmutablePoint {
static final ImmutablePoint origin = const ImmutablePoint(0, 0); final num x, y; const ImmutablePoint(this.x, this.y); } // 工厂构造函数 class Logger {
final String name; bool mute = false; // 从命名的 _ 可以知, // _cache 是私有属性。 static final Map<String, Logger> _cache = <String, Logger>{
}; factory Logger(String name) {
if (_cache.containsKey(name)) {
return _cache[name]; } else {
final logger = Logger._internal(name); _cache[name] = logger; return logger; } } Logger._internal(this.name); void log(String msg) {
if (!mute) print(msg); } }
抽象类:
- 使用
abstract
修饰符来定义 抽象类 — 抽象类不能实例化。 抽象类通常用来定义接口,以及部分实现。 如果希望抽象类能够被实例化,那么可以通过定义一个 工厂构造函数 来实现。
方法
- 对象的实例方法可以访问 this 和实例变量
- Getter 和 Setter,使用 get 和 set 关键字实现 Getter 和 Setter ,能够为实例创建额外的属性。
- 最开始实现 Getter 和 Setter 也许是直接返回成员变量; 随着需求变化, Getter 和 Setter 可能需要进行计算处理而使用方法来实现;
- 抽象方法:只定义接口不进行实现,而是留给其他类去实现。 抽象方法只存在于 抽象类 中
- 调用抽象方法会导致运行时错误
abstract class Doer {
// 定义实例变量和方法 ... void doSomething(); // 定义一个抽象方法。 } class EffectiveDoer extends Doer {
void doSomething() {
// 提供方法实现,所以这里的方法就不是抽象方法了... } }
隐式接口:
- 每个类都隐式的定义了一个接口,接口包含了该类所有的实例成员及其实现的接口。 如果要创建一个 A 类,A 要支持 B 类的 API ,但是不需要继承 B 的实现, 那么可以通过 A 实现 B 的接口。
- 一个类可以通过
implements
关键字来实现一个或者多个接口, 并实现每个接口要求的 API。如class Point implements Comparable, Location {...}
;
// person 类。 隐式接口里面包含了 greet() 方法声明。 class Person {
// 包含在接口里,但只在当前库中可见。 final _name; // 不包含在接口里,因为这是一个构造函数。 Person(this._name); // 包含在接口里。 String greet(String who) => 'Hello, $who. I am $_name.'; } // person 接口的实现。 class Impostor implements Person {
get _name => ''; String greet(String who) => 'Hi $who. Do you know who I am?'; } String greetBob(Person person) => person.greet('Bob'); void main() {
print(greetBob(Person('Kathy'))); print(greetBob(Impostor())); }
扩展类——继承:
- 使用
extends
关键字来创建子类, 使用super
关键字来引用父类
class Television {
void turnOn() {
_illuminateDisplay(); _activateIrSensor(); } // ··· } class SmartTelevision extends Television {
void turnOn() {
super.turnOn(); _bootNetworkInterface(); _initializeMemory(); _upgradeApps(); } // ··· }
重写类成员:
- 子类可以重写实例方法,getter 和 setter。 可以使用
@override
注解指出想要重写的成员
class SmartTelevision extends Television {
@override void turnOn() {
...} // ··· }
重写运算符:参考关键字 operator
noSuchMethod()
:
- 当代码尝试使用不存在的方法或实例变量时, 通过重写 noSuchMethod() 方法,来实现检测和应对处理
class A {
// 如果不重写 noSuchMethod,访问 // 不存在的实例变量时会导致 NoSuchMethodError 错误。 @override void noSuchMethod(Invocation invocation) {
print('You tried to use a non-existent member: ' + '${invocation.memberName}'); } }
类变量和方法
参考相关关键字:static
枚举
参考相关关键字:enum
为类添加功能
参考相关关键字:mixin
泛型
在 API 文档中你会发现基础数组类型 List 的实际类型是List<E>
。 <…>
符号将 List 标记为 泛型 (或 参数化) 类型。 这种类型具有形式化的参数。 通常情况下,使用一个字母来代表类型参数, 例如 E, T, S, K, 和 V 等
- 正确指定泛型类型可以提高代码质量。
- 使用泛型可以减少重复的代码。
// 一个用于缓存对象的接口 abstract class ObjectCache {
Object getByKey(String key); void setByKey(String key, Object value); } // 一个相同功能的字符串类型接口 abstract class StringCache {
String getByKey(String key); void setByKey(String key, String value); } // 泛型可以省去创建所有这些接口的麻烦 abstract class Cache<T> {
T getByKey(String key); void setByKey(String key, T value); }
使用集合字面量:
- List , Set 和 Map 字面量也是可以参数化的。 参数化字面量和之前的字面量定义类似, 对于 List 或 Set 只需要在声明语句前加
<type>
前缀, 对于 Map 只需要在声明语句前加<keyType, valueType>
前缀 var names = <String>['Seth', 'Kathy', 'Lars'];
var uniqueNames = <String>{'Seth', 'Kathy', 'Lars'};
var pages = <String, String>{};
使用泛型类型的构造函数:
- 在类名字后面使用尖括号(
<...>
)来指定泛型类型;Set<String>.from(names);
Map<int, View>();
运行时中的泛型集合:
- Dart 中泛型类型是 固化的,也就是说它们在运行时是携带着类型信息的。 例如, 在运行时检测集合的类型(Java中的泛型会被 擦除);
var names = List<String>(); names.addAll(['Seth', 'Kathy', 'Lars']); print(names is List<String>); // true
限制泛型类型:
- 可以使用 extends 实现参数类型的限制
class Foo<T extends SomeBaseClass> {...}
,如果Foo()
时不指定泛型,那么T
就是SomeBaseClass
;
使用泛型函数:
T first<T>(List<T> ts) {};
库和可见性
参考相关关键字:import
、library
、as
、show
、hide
、deferred as
实现库
实现库、包:
- 如何组织库的源文件。
- 如何使用
export
命令。 - 何时使用
part
命令。 - 何时使用
library
命令。
异步支持
Dart 库中包含许多返回 Future 或 Stream 对象的函数. 这些函数在设置完耗时任务(例如 I/O 曹组)后, 就立即返回了,不会等待耗任务完成。 使用 async 和 await 关键字实现异步编程;
- Future处理
- 声明异步函数
- 处理Stream
- 生成器:参考相关关键字
sync
和yield
Future处理:
- 使用
async
和await
;(使用try
,catch
, 和finally
来处理代码中使用 await 导致的错误) - 使用 Future API;
声明异步函数:
- 函数体被
async
标示符标记的函数,即是一个_异步函数_; - 将
async
关键字添加到函数使其返回Future; Future<String> lookUpVersion() async => '1.0.0';
注意,函数体不需要使用Future API。 如有必要, Dart 会创建 Future 对象。如果函数没有返回有效值, 需要设置其返回类型为
Future<void>
;
处理Stream:
- 从 Stream 中获取数据值的两种方式;
- 使用
async
和 一个 异步循环 (await for
) - 使用 Stream API;
Future main() async {
// ... await for (var request in requestServer) {
handleRequest(request); } // ... }
上面 表达式 返回的值必须是 Stream 类型。 执行流程如下:
- 等待,直到流发出一个值。
- 执行 for 循环体,将变量设置为该发出的值
- 重复1和2,直到关闭流。
- 使用 break 或者 return 语句可以停止接收 stream 的数据, 这样就跳出了 for 循环, 并且从 stream 上取消注册
提示: 在使用 await for 前,确保代码清晰, 并且确实希望等待所有流的结果。 例如,通常不应该使用 await for 的UI事件侦听器, 因为UI框架会发送无穷无尽的事件流。
可调用类
通过实现类的 call() 方法, 能够让类像函数一样被调用
class WannabeFunction {
call(String a, String b, String c) => '$a $b $c!'; } main() {
var wf = new WannabeFunction(); var out = wf("Hi","there,","gang"); print('$out'); }
Isolates
大多数计算机中,甚至在移动平台上,都在使用多核CPU。 为了有效利用多核性能,开发者一般使用共享内存数据来保证多线程的正确执行。 然而, 多线程共享数据通常会导致很多潜在的问题,并导致代码运行出错。
所有 Dart 代码都在隔离区( isolates )内运行,而不是线程。 每个隔离区都有自己的内存堆,确保每个隔离区的状态都不会被其他隔离区访问
元数据
使用元数据可以提供有关代码的其他信息。 元数据注释以字符 @
开头, 后跟对编译时常量 (如 deprecated
) 的引用或对常量构造函数的调用
对于所有 Dart 代码有两种可用注解:
@deprecated
@override
可以自定义元数据注解
元数据可以在 library、 class、 typedef、 type parameter、 constructor、 factory、 function、 field、 parameter 或者 variable 声明之前使用,也可以在 import 或者 export 指令之前使用。
使用反射
可以在运行时获取元数据信息
注释
- 单行注释
- 多上注释
- 文档注释
文档注释以 ///
或者 /
开始。 在连续行上使用 ///
与多行文档注释具有相同的效果
/// A domesticated South American camelid (Lama glama). /// /// 自从西班牙时代以来, /// 安第斯文化就将骆驼当做肉食类和运输类动物。 class Llama {
String name; /// 喂养骆驼 [Food]. /// /// 典型的美洲驼每周吃一捆干草。 void feed(Food food) {
// ... } /// 使用 [activity] 训练骆驼 /// [timeLimit] 分钟。 void exercise(Activity activity, int timeLimit) {
// ... } }
解析 Dart 代码并生成 HTML 文档,可以使用 SDK 中的 documentation generation tool.
总结
不清晰的地方:
- 库、包的开发;
- 元数据编程;
- 异步Stream编程;
- 文档生成;
- 隔离区isolates的使用;
- 生成器
Dart语言概述 over!
到此这篇dart编程语言pdf下载_fortran编程语言的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权、违法违规、事实不符,请将相关资料发送至xkadmin@xkablog.com进行投诉反馈,一经查实,立即处理!
转载请注明出处,原文链接:https://www.xkablog.com/bcyy/2183.html