『Flutter开发实战』一小时掌握Dart语言

Posted 小宋是呢

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了『Flutter开发实战』一小时掌握Dart语言相关的知识,希望对你有一定的参考价值。

 

参考:https://dart.cn/guides/language/language-tour

Dart 开发语言概览

本文将从变量和运算符开始到类和库的使用来向你介绍 Dart 编程语言的主要功能,这里假设你已经有使用其它语言进行编程的经验。

你可以通过查看 Dart 库概览 学习更多关于 Dart 核心库的知识。若还想了解更多有关语言功能的详细内容,请参阅 Dart 编程语言规范

 备忘:

你可以通过 DartPad 体验 Dart 的大部分语言功能 (了解更多), 打开 DartPad。

本页面内嵌了一些 DartPads 做例子展示,

如果你只看到了空白的框框(而没有任何内容),请查阅 DartPad 常见问题页面

一个简单的 Dart 程序

下面的应用程序代码用到了很多 Dart 的基本功能:

// Define a function.
void printInteger(int aNumber) 
  print('The number is $aNumber.'); // Print to console.


// This is where the app starts executing.
void main() 
  var number = 42; // Declare and initialize a variable.
  printInteger(number); // Call a function.

下面是上述应用程序中使用到的代码片段,这些代码片段适用于所有(或几乎所有)的 Dart 应用:

// This is a comment.

// 注释。

以双斜杠开头的一行语句称为单行注释。Dart 同样支持多行注释和文档注释。查阅注释获取更多相关信息。

void

一种特殊的类型,表示一个值永远不会被使用。类似于 main() 和 printInteger() 的函数,以 void 声明的函数返回类型,并不会返回值。

int

另一种数据类型,表示一个整型数字。 Dart 中一些其他的内置类型包括 StringList 和 bool

42

表示一个数字字面量。数字字面量是一种编译时常量。

print()

一种便利的将信息输出显示的方式。

'...' (或 "...")

表示字符串字面量。

$variableName (或 $expression)

表示字符串插值:字符串字面量中包含的变量或表达式。查阅字符串获取更多相关信息。

main()

一个特殊且 必须的 顶级函数,Dart 应用程序总是会从该函数开始执行。查阅 main() 函数 获取更多相关信息。

var

用于定义变量,通过这种方式定义变量不需要指定变量类型。

 备忘:

本站的代码遵循 Dart 风格指南 中的约定。

重要概念

当你在学习 Dart 语言时, 应该牢记以下几点:

  • 所有变量引用的都是 对象,每个对象都是一个  的实例。数字、函数以及 null 都是对象。所有的类都继承于 Object 类。

  • 尽管 Dart 是强类型语言,但是在声明变量时指定类型是可选的,因为 Dart 可以进行类型推断。在上述代码中,变量 number 的类型被推断为 int 类型。如果想显式地声明一个不确定的类型,可以使用特殊类型 dynamic

  • Dart 支持泛型,比如 List<int>(表示一组由 int 对象组成的列表)或 List<dynamic>(表示一组由任何类型对象组成的列表)。

  • Dart 支持顶级函数(例如 main 方法),同时还支持定义属于类或对象的函数(即 静态 和 实例方法)。你还可以在函数中定义函数(嵌套 或 局部函数)。

  • Dart 支持顶级 变量,以及定义属于类或对象的变量(静态和实例变量)。实例变量有时称之为域或属性。

  • Dart 没有类似于 Java 那样的 publicprotected 和 private 成员访问限定符。如果一个标识符以下划线 (_) 开头则表示该标识符在库内是私有的。可以查阅 库和可见性 获取更多相关信息。

  • 标识符 可以以字母或者下划线 (_) 开头,其后可跟字符和数字的组合。

  • Dart 中 表达式 和 语句 是有区别的,表达式有值而语句没有。比如条件表达式 expression condition ? expr1 : expr2 中含有值 expr1 或 expr2。与 if-else 分支语句相比,if-else 分支语句则没有值。一个语句通常包含一个或多个表达式,但是一个表达式不能只包含一个语句。

  • Dart 工具可以显示 警告 和 错误 两种类型的问题。警告表明代码可能有问题但不会阻止其运行。错误分为编译时错误和运行时错误;编译时错误代码无法运行;运行时错误会在代码运行时导致异常

 备忘:

如果您好奇 Dart 为什么使用下划线而不使用诸如 public 或 private 作为修饰符,请参阅 SDK 议题 #33383

关键字

下面的表格中列出了 Dart 语言所使用的关键字。

abstract 2elseimport 2super
as 2enuminswitch
assertexport 2interface 2sync 1
async 1extendsisthis
await 3extension 2library 2throw
breakexternal 2mixin 2true
casefactory 2newtry
catchfalsenulltypedef 2
classfinalon 1var
constfinallyoperator 2void
continueforpart 2while
covariant 2Function 2rethrowwith
defaultget 2returnyield 3
deferred 2hide 1set 2 
doifshow 1 
dynamic 2implements 2static 2 

应该避免使用这些单词作为标识符。但是,带有上标的单词可以在必要的情况下作为标识符:

  • 带有上标 1 的关键字为 上下文关键字,只有在特定的场景才有意义,它们可以在任何地方作为有效的标识符。

  • 带有上标 2 的关键字为 内置标识符,其作用只是在javascript代码转为Dart代码时更简单,这些关键字在大多数时候都可以作为有效的标识符,但是它们不能用作类名或者类型名或者作为导入前缀使用。

  • 带有上标 3 的关键字为 Dart 1.0 发布后用于 支持异步 相关内容。不能在由关键字 asyncasync* 或 sync* 标识的方法体中使用 await 或 yield 作为标识符。

其它没有上标的关键字为 保留字,均不能用作标识符。

变量

下面的示例代码将创建一个变量并将其初始化:

var name = 'Bob';

变量仅存储对象的引用。这里名为 name 的变量存储了一个 String 类型对象的引用,“Bob” 则是该对象的值。

name 变量的类型被推断为 String,但是你可以为其指定类型。如果一个对象的引用不局限于单一的类型,可以根据设计指南将其指定为 Object 或 dynamic 类型。

dynamic name = 'Bob';

除此之外你也可以指定类型:

String name = 'Bob';

 备忘:

本文遵循 风格建议指南 中的建议,通过 var 声明局部变量而非使用指定的类型。

默认值

在 Dart 中,未初始化的变量拥有一个默认的初始化值:null。即便数字也是如此,因为在 Dart 中一切皆为对象,数字也不例外。

int lineCount;
assert(lineCount == null);

 备忘:

assert() 的调用将会在生产环境的代码中被忽略掉。在开发过程中,assert(condition) 将会在 条件判断 为 false 时抛出一个异常。详情请查阅 Assert

Final 和 Const

如果你不想更改一个变量,可以使用关键字 final 或者 const 修饰变量,这两个关键字可以替代 var 关键字或者加在一个具体的类型前。一个 final 变量只可以被赋值一次;一个 const 变量是一个编译时常量(const 变量同时也是 final 的)。顶层的 final 变量或者类的 final 变量在其第一次使用的时候被初始化。

 备忘:

实例变量可以是 final 的但不可以是 const 的, final 实例变量必须在构造器开始前被初始化,比如在声明实例变量时初始化,或者作为构造器参数,或者将其置于构造器的 初始化列表中。

下面的示例中我们创建并设置两个 final 变量:

final name = 'Bob'; // Without a type annotation
final String nickname = 'Bobby';

你不能修改一个 final 变量的值:

name = 'Alice'; // Error: a final variable can only be set once.

使用关键字 const 修饰变量表示该变量为 编译时常量。如果使用 const 修饰类中的变量,则必须加上 static 关键字,即 static const(译者注:顺序不能颠倒)。在声明 const 变量时可以直接为其赋值,也可以使用其它的 const 变量为其赋值:

const bar = 1000000; // 直接赋值 [Unit of pressure (dynes/cm2)]
const double atm = 1.01325 * bar; // 利用其它 const 变量赋值 (Standard atmosphere)

const 关键字不仅仅可以用来定义常量,还可以用来创建 常量值,该常量值可以赋予给任何变量。你也可以将构造函数声明为 const 的,这种类型的构造函数创建的对象是不可改变的。

var foo = const [];
final bar = const [];
const baz = []; // 相当于 `const []` (Equivalent to `const []`)

如果使用初始化表达式为常量赋值可以省略掉关键字 const,比如上面的常量 baz 的赋值就省略掉了 const。详情请查阅 不要冗余地使用 const

没有使用 final 或 const 修饰的变量的值是可以被更改的,即使这些变量之前引用过 const 的值。

foo = [1, 2, 3]; // foo 的值之前为 const [] (Was const [])

常量的值不可以被修改:

baz = [42]; // 报错:常量不可以被赋值。(Error: Constant variables can't be assigned a value.)

你可以在常量中使用 类型检查和强制类型转换 (is 和 as)、 集合中的 if 以及 展开操作符 (... 和 ...?):

const Object i = 3; // Where i is a const Object with an int value...
const list = [i as int]; // Use a typecast.
const map = if (i is int) i: "int"; // Use is and collection if.
const set = if (list is List<int>) ...list; // ...and a spread.

 备忘: Although a final object cannot be modified, its fields can be changed. In comparison, a const object and its fields cannot be changed: they’re immutable.

可以查阅 ListsMaps 和 Classes 获取更多关于使用 const 创建常量值的信息。

内置类型

Dart 语言支持下列的类型:

  • numbers
  • strings
  • booleans
  • lists (也被称为 arrays)

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

  • symbols

可以直接使用字面量来初始化上述类型。例如 'This is a string' 是一个字符串字面量,true 是一个布尔字面量。

由于 Dart 中每个变量引用都指向一个对象(一个  的实例),你通常也可以使用 构造器 来初始化变量。一些内置的类型有它们自己的构造器。例如你可以使用 Map() 来创建一个 map 对象。

Numbers

Dart 支持两种 Number 类型:

int

整数值;长度不超过 64 位,具体取值范围依赖于不同的平台。在 DartVM 上其取值位于 -263 至 263 - 1 之间。编译成 JavaScript 的 Dart 使用 JavaScript 数字,其允许的取值范围在 -253 至 253 - 1 之间。

double

64 位的双精度浮点数字,且符合 IEEE 754 标准。

int 和 double 都是 num 的子类。 num 中定义了一些基本的运算符比如 +、-、*、/ 等,还定义了 abs()ceil() 和 floor() 等方法(位运算符,比如 >> 定义在 int 中)。如果 num 及其子类不满足你的要求,可以查看 dart:math 库中的 API。

整数是不带小数点的数字,下面是一些定义整数字面量的例子:

var x = 1;
var hex = 0xDEADBEEF;

如果一个数字包含了小数点,那么它就是浮点型的。下面是一些定义浮点数字面量的例子:

var y = 1.1;
var exponents = 1.42e5;

整型字面量将会在必要的时候自动转换成浮点数字面量:

double z = 1; // Equivalent to double z = 1.0.

 版本提示:

在 Dart 2.1 之前,在浮点数上下文中使用整数字面量是错误的。

下面是字符串和数字之间转换的方式:

// 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');

整型支持传统的位移操作,比如移位(<<、>>)、按位与(&)、按位或(|),例如:

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;

Strings

Dart 字符串是 UTF-16 编码的字符序列。可以使用单引号或者双引号来创建字符串:

var s1 = 'Single quotes work well for string literals.';
var s2 = "Double quotes work just as well.";
var s3 = 'It\\'s easy to escape the string delimiter.';
var s4 = "It's even easier to use the other delimiter.";

// 代码中文解释
var s1 = '使用单引号创建字符串字面量。';
var s2 = "双引号也可以用于创建字符串字面量。";
var s3 = '使用单引号创建字符串时可以使用斜杠来转义那些与单引号冲突的字符串:\\'。';
var s4 = "而在双引号中则不需要使用转义与单引号冲突的字符串:'";

在字符串中,请以 $表达式 的形式使用表达式,如果表达式是一个标识符,可以省略掉 。如果表达式的结果为一个对象,则 Dart 会调用该对象的 toString 方法来获取一个字符串。

var s = 'string interpolation';

assert('Dart has $s, which is very handy.' ==
    'Dart has string interpolation, ' +
        'which is very handy.');
assert('That deserves all caps. ' +
        '$s.toUpperCase() is very handy!' ==
    'That deserves all caps. ' +
        'STRING INTERPOLATION is very handy!');

// 代码中文解释
var s = '字符串插值';

assert('Dart 有$s,使用起来非常方便。' == 'Dart 有字符串插值,使用起来非常方便。');
assert('使用$s.substring(3,5)表达式也非常方便' == '使用插值表达式也非常方便。');

 备忘:

== 运算符负责判断两个对象的内容是否一样,如果两个字符串包含一样的字符编码序列,则表示相等。

你可以使用 + 运算符或并列放置多个字符串来连接字符串:

var s1 = 'String '
    'concatenation'
    " works even over line breaks.";
assert(s1 ==
    'String concatenation works even over '
        'line breaks.');

var s2 = 'The + operator ' + 'works, as well.';
assert(s2 == 'The + operator works, as well.');

// 代码中文解释
var s1 = '可以拼接'
    '字符串'
    "即便它们不在同一行。";
assert(s1 == '可以拼接字符串即便它们不在同一行。');

var s2 = '使用加号 + 运算符' + '也可以达到相同的效果。';
assert(s2 == '使用加号 + 运算符也可以达到相同的效果。');

使用三个单引号或者三个双引号也能创建多行字符串:

var s1 = '''
你可以像这样创建多行字符串。
''';

var s2 = """这也是一个多行字符串。""";

在字符串前加上 r 作为前缀创建 “raw” 字符串(即不会被做任何处理(比如转义)的字符串):

var s = r'In a raw string, not even \\n gets special treatment.';

// 代码中文解释
var s = r'在 raw 字符串中,转义字符串 \\n 会直接输出 “\\n” 而不是转义为换行。';

你可以查阅 Runes 与 grapheme clusters 获取更多关于如何在字符串中表示 Unicode 字符的信息。

字符串字面量是一个编译时常量,只要是编译时常量都可以作为字符串字面量的插值表达式:

// 可以将下面三个常量作为字符串插值拼接到字符串字面量中。(These work in a const string.)
const aConstNum = 0;
const aConstBool = true;
const aConstString = 'a constant string';

// 而下面三个常量不能作为字符串插值拼接到字符串字面量。
var aNum = 0;
var aBool = true;
var aString = 'a string';
const aConstList = [1, 2, 3];

const validConstString = '$aConstNum $aConstBool $aConstString';
// const invalidConstString = '$aNum $aBool $aString $aConstList';

可以查阅 字符串和正则表达式 获取更多关于如何使用字符串的信息。

Booleans

Dart 使用 bool 关键字表示布尔类型,布尔类型只有两个对象 true 和 false,两者都是编译时常量。

Dart 的类型安全不允许你使用类似 if (nonbooleanValue) 或者 assert (nonbooleanValue) 这样的代码检查布尔值。相反,你应该总是显示地检查布尔值,比如像下面的代码这样:

// 检查是否为空字符串 (Check for an empty string).
var fullName = '';
assert(fullName.isEmpty);

// 检查是否小于等于零。
var hitPoints = 0;
assert(hitPoints <= 0);

// 检查是否为 null。
var unicorn;
assert(unicorn == null);

// 检查是否为 NaN。
var iMeantToDoThis = 0 / 0;
assert(iMeantToDoThis.isNaN);

Lists

数组(Array)是几乎所有编程语言中最常见的集合类型,在 Dart 中数组由 List 对象表示。通常称之为 List

Dart 中 List 字面量看起来与 JavaScript 中数组字面量一样。下面是一个 Dart List 的示例:

var list = [1, 2, 3];

 备忘:

这里 Dart 推断出 list 的类型为 List<int>,如果往该数组中添加一个非 int 类型的对象则会报错。你可以阅读 类型推断 获取更多相关信息。

你可以在 Dart 的集合类型的最后一个项目后添加逗号。这个尾随逗号并不会影响集合,但它能有效避免「复制粘贴」的错误。

var list = [
  'Car',
  'Boat',
  'Plane',
];

List 的下标索引从 0 开始,第一个元素的下标为 0,最后一个元素的下标为 list.length - 1。你可以像 JavaScript 中的用法那样获取 Dart 中 List 的长度以及元素:

var list = [1, 2, 3];
assert(list.length == 3);
assert(list[1] == 2);

list[1] = 1;
assert(list[1] == 1);

在 List 字面量前添加 const 关键字会创建一个编译时常量:

var constantList = const [1, 2, 3];
// constantList[1] = 1; // This line will cause an error.

Dart 在 2.3 引入了 扩展操作符...)和 空感知扩展操作符...?),它们提供了一种将多个元素插入集合的简洁方法。

例如,你可以使用扩展操作符(...)将一个 List 中的所有元素插入到另一个 List 中:

var list = [1, 2, 3];
var list2 = [0, ...list];
assert(list2.length == 4);

如果扩展操作符右边可能为 null ,你可以使用 null-aware 扩展操作符(...?)来避免产生异常:

var list;
var list2 = [0, ...?list];
assert(list2.length == 1);

可以查阅扩展操作符建议获取更多关于如何使用扩展操作符的信息。

Dart 还同时引入了 集合中的 if 和 集合中的 for 操作,在构建集合时,可以使用条件判断 (if) 和循环 (for)。

下面示例是使用 集合中的 if 来创建一个 List 的示例,它可能包含 3 个或 4 个元素:

var nav = [
  'Home',
  'Furniture',
  'Plants',
  if (promoActive) 'Outlet'
];

下面是使用 集合中的 for 将列表中的元素修改后添加到另一个列表中的示例:

var listOfInts = [1, 2, 3];
var listOfStrings = [
  '#0',
  for (var i in listOfInts) '#$i'
];
assert(listOfStrings[1] == '#1');

你可以查阅 集合中使用控制流建议 获取更多关于在集合中使用 if 和 for 的细节内容和示例。

List 类中有许多用于操作 List 的便捷方法,你可以查阅 泛型 和 集合 获取更多与之相关的信息。

Sets

在 Dart 中,set 是一组特定元素的无序集合。 Dart 支持的 set 由 set literals 和 Set 类提供。

 版本提示:

尽管 Set 类型(type) 一直都是 Dart 的一项核心功能,但是 Set 字面量(literals) 是在 Dart 2.2 中才加入的。

下面是使用 Set 字面量来创建一个 Set 集合的方法:

var halogens = 'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine';

 备忘:

Dart 推断 halogens 变量是一个 Set<String> 类型的集合,如果往该 Set 中添加类型不正确的对象则会报错。你可以查阅 类型推断 获取更多与之相关的内容。

可以使用在  前加上类型参数的方式创建一个空的 Set,或者将  赋值给一个 Set 类型的变量:

var names = <String>; // 类型+的形式创建Set。
// Set<String> names = ; // 声明类型变量的形式创建 Set (This works, too).
// var names = ; // 这样的形式将创建一个 Map 而不是 Set (Creates a map, not a set.)

 

Set 还是 map? Map 字面量语法相似于 Set 字面量语法。因为先有的 Map 字面量语法,所以  默认是 Map 类型。如果忘记在  上注释类型或赋值到一个未声明类型的变量上,那么 Dart 会创建一个类型为 Map<dynamic, dynamic> 的对象。

使用 add() 方法或 addAll() 方法向已存在的 Set 中添加项目:

var elements = <String>;
elements.add('fluorine');
elements.addAll(halogens);

使用 .length 可以获取 Set 中元素的数量:

var elements = <String>;
elements.add('fluorine');
elements.addAll(halogens);
assert(elements.length == 5);

可以在 Set 字面量前添加 const 关键字创建一个 Set 编译时常量:

final constantSet = const 
  'fluorine',
  'chlorine',
  'bromine',
  'iodine',
  'astatine',
;
// constantSet.add('helium'); // This line will cause an error.

从 Dart 2.3 开始,Set 可以像 List 一样支持使用扩展操作符(... 和 ...?)以及 Collection If 和 Collection For 操作。你可以查阅 List 扩展操作符 和 List 集合操作符 获取更多相关信息。

你也可以查阅 泛型 以及 Set 获取更多相关信息。

Maps

通常来说,Map 是用来关联 keys 和 values 的对象。其中键和值都可以是任何类型的对象。每个  只能出现一次但是  可以重复出现多次。 Dart 中 Map 提供了 Map 字面量以及 Map 类型两种形式的 Map。

下面是一对使用 Map 字面量创建 Map 的例子:

var gifts = 
  // 键:    值
  'first': 'partridge',
  'second': 'turtledoves',
  'fifth': 'golden rings'
;

var nobleGases = 
  2: 'helium',
  10: 'neon',
  18: 'argon',
;

 备忘:

Dart 将 gifts 变量的类型推断为 Map<String, String>,而将 nobleGases 的类型推断为 Map<int, String>。如果你向这两个 Map 对象中添加不正确的类型值,将导致运行时异常。你可以阅读 类型推断 获取更多相关信息。

你也可以使用 Map 的构造器创建 Map:

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';

 备忘: If you come from a language like C# or Java, you might expect to see new Map() instead of just Map(). In Dart, the new keyword is optional. For details, see Using constructors.

如果你之前是使用的 C# 或 Java 这样的语言,也许你想使用 new Map() 构造 Map 对象。但是在 Dart 中,new 关键词是可选的。(译者注:且不被建议使用) 你可以查阅 构造函数的使用 获取更多相关信息。

向现有的 Map 中添加键值对与 JavaScript 的操作类似:

var gifts = 'first': 'partridge';
gifts['fourth'] = 'calling birds'; // 添加键值对 (Add a key-value pair)

从一个 Map 中获取一个值的操作也与 JavaScript 类似。

var gifts = 'first': 'partridge';
assert(gifts['first'] == 'partridge');

如果检索的 Key 不存在于 Map 中则会返回一个 null:

var gifts = 'first': 'partridge';
assert(gifts['fifth'] == null);

使用 .length 可以获取 Map 中键值对的数量:

var gifts = 'first': 'partridge';
gifts['fourth'] = 'calling birds';
assert(gifts.length == 2);

在一个 Map 字面量前添加 const 关键字可以创建一个 Map 编译时常量:

final constantMap = const 
  2: 'helium',
  10: 'neon',
  18: 'argon',
;

// constantMap[2] = 'Helium'; // This line will cause an error.

Map 可以像 List 一样支持使用扩展操作符(... 和 ...?)以及集合的 if 和 for 操作。你可以查阅 List 扩展操作符 和 List 集合操作符 获取更多相关信息。

你也可以查阅 泛型 以及 Maps 获取更多相关信息。

Runes 与 grapheme clusters

在 Dart 中,runes 公开了字符串的 Unicode 码位。使用 characters 包 来访问或者操作用户感知的字符,也被称为 Unicode (扩展) grapheme clusters

Unicode 编码为每一个字母、数字和符号都定义了一个唯一的数值。因为 Dart 中的字符串是一个 UTF-16 的字符序列,所以如果想要表示 32 位的 Unicode 数值则需要一种特殊的语法。

表示 Unicode 字符的常见方式是使用 \\uXXXX,其中 XXXX 是一个四位数的 16 进制数字。例如心形字符(♥)的 Unicode 为 \\u2665。对于不是四位数的 16 进制数字,需要使用大括号将其括起来。例如大笑的 emoji 表情(😆)的 Unicode 为 \\u1f600

如果你需要读写单个 Unicode 字符,可以使用 characters 包中定义的 characters getter。它将返回 Characters 对象作为一系列 grapheme clusters 的字符串。下面是使用 characters API 的样例:

import 'package:characters/characters.dart';
...
var hi = 'Hi 🇩🇰';
print(hi);
print('The end of the string: $hi.substring(hi.length - 1)');
print('The last character: $hi.characters.last\\n');

输出取决于你的环境,大致类似于:

$ dart bin/main.dart
Hi 🇩🇰
The end of the string: ???
The last character: 🇩🇰

有关使用 characters 包操作字符串的详细信息,请参阅用于 characters 包的样例 和 API 参考

 备忘:

在使用 List 操作 Rune 的时候需要小心,根据所操作的语种、字符集等不同可能会导致字符串出现问题,具体可参考 Stack Overflow 中的提问: [我如何在 Dart 中反转一个字符串?][How do I reverse a String in Dart?]。

Symbols

Symbol 表示 Dart 中声明的操作符或者标识符。你几乎不会需要 Symbol,但是它们对于那些通过名称引用标识符的 API 很有用,因为代码压缩后,尽管标识符的名称会改变,但是它们的 Symbol 会保持不变。

可以使用在标识符前加 # 前缀来获取 Symbol:

#radix
#bar

Symbol 字面量是编译时常量。

函数

Dart 是一种真正面向对象的语言,所以即便函数也是对象并且类型为 Function,这意味着函数可以被赋值给变量或者作为其它函数的参数。你也可以像调用函数一样调用 Dart 类的实例。详情请查阅 可调用的类

下面是定义一个函数的例子:

bool isNoble(int atomicNumber) 
  return _nobleGases[atomicNumber] != null;

虽然高效 Dart 指南建议在公开的 API 上定义返回类型,不过即便不定义,该函数也依然有效:

isNoble(atomicNumber) 
  return _nobleGases[atomicNumber] != null;

如果函数体内只包含一个表达式,你可以使用简写语法:

bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;

语法 => 表达式 是  return 表达式;  的简写, => 有时也称之为 箭头 函数。

 备忘:

在 => 与 ; 之间的只能是 表达式 而非 语句。比如你不能将一个 if语句 放在其中,但是可以放置 条件表达式

参数

函数可以有两种形式的参数:必要参数 和 可选参数。必要参数定义在参数列表前面,可选参数则定义在必要参数后面。可选参数可以是 命名的 或 位置的

 备忘:

某些 API(特别是 Flutter 控件的构造器)只使用命名参数,即便参数是强制性的。可以查阅下一节获取更多信息。

向函数传入参数或者定义函数参数时,可以使用 [尾随逗号][trailing comma]。

命名参数

命名参数默认为可选参数,除非他们被特别标记为必要的。

当你调用函数时,可以使用 参数名参数值 的形式来指定命名参数。例如:

enableFlags(bold: true, hidden: false);

When defining a function, use 参数1参数2, … to specify named parameters:

定义函数时,使用 param1param2, … 来指定命名参数:

/// 设置 [bold] 和 [hidden] 标识……
/// Sets the [bold] and [hidden] flags...
void enableFlags(bool bold, bool hidden) ...

虽然命名参数是可选参数的一种类型,但是你仍然可以使用 @required 注解来标识一个命名参数是必须的参数,此时调用者必须为该参数提供一个值。例如:

const Scrollbar(Key key, @required Widget child)

如果调用者想要通过 Scrollbar 的构造函数构造一个 Scrollbar 对象而不提供 child 参数,则会导致编译错误。

@required 注解定义在 meta package 中,可以通过导入 package:meta/meta.dart 包使用。

可选的位置参数

使用 [] 将一系列参数包裹起来作为位置参数:

String say(String from, String msg, [String device]) 
  var result = '$from says $msg';
  if (device != null) 
    result = '$result with a $device';
  
  return result;

下面是不使用可选参数调用上述函数的示例:

assert(say('Bob', 'Howdy') == 'Bob says Howdy');

下面是使用可选参数调用上述函数的示例:

assert(say('Bob', 'Howdy', 'smoke signal') ==
    'Bob says Howdy with a smoke signal');

默认参数值

可以用 = 为函数的命名参数和位置参数定义默认值,默认值必须为编译时常量,没有指定默认值的情况下默认值为 null

下面是设置可选参数默认值示例:

/// 设置 [bold] 和 [hidden] 标识……
/// Sets the [bold] and [hidden] flags ...
void enableFlags(bool bold = false, bool hidden = false) ...

// bold 的值将为 true;而 hidden 将为 false。
enableFlags(bold: true);

 

在老版本的 Dart 代码中会使用冒号(:)而不是 = 来设置命名参数的默认值。原因在于刚开始的时候命名参数只支持 :。不过现在这个支持已经过时,所以我们建议你现在仅 使用 = 来指定默认值

下一个示例将向你展示如何为位置参数设置默认值:

String say(String from, String msg,
    [String device = 'carrier pigeon']) 
  var result = '$from says $msg with a $device';
  return result;


assert(say('Bob', 'Howdy') ==
    'Bob says Howdy with a carrier pigeon');

List 或 Map 同样也可以作为默认值。下面的示例定义了一个名为 doStuff() 的函数,并为其名为 list 和 gifts 的参数指定了一个 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');

main() 函数

每个 Dart 程序都必须有一个 main() 顶级函数作为程序的入口,main() 函数返回值为 void 并且有一个 List<String> 类型的可选参数。

下面是一个 Web 应用的 main() 函数示例:

void main() 
  querySelector('#sample_text_id')
    ..text = 'Click me!'
    ..onClick.listen(reverseText);

 备忘:

上述代码中的 .. 语法称之为 级联调用。使用级联访问可以在一个对象上执行多个操作。

下面是使用命令行访问带参数的 main() 函数示例:

// 使用命令 dart args.dart 1 test 运行该应用
// Run the app like this: dart args.dart 1 test
void main(List<String> arguments) 
  print(arguments);

  assert(arguments.length == 2);
  assert(int.parse(arguments[0]) == 1);
  assert(arguments[1] == 'test');

你可以通过使用 参数库 来定义和解析命令行参数。

函数是一级对象

可以将函数作为参数传递给另一个函数。例如:

void printElement(int element) 
  print(element);


var list = [1, 2, 3];

// 将 printElement 函数作为参数传递。
list.forEach(printElement);

你也可以将函数赋值给一个变量,比如:

var loudify = (msg) => '!!! $msg.toUpperCase() !!!';
assert(loudify('hello') == '!!! HELLO !!!');

该示例中使用了匿名函数。下一节会有更多与其相关的介绍。

匿名函数

大多数方法都是有名字的,比如 main() 或 printElement()。你可以创建一个没有名字的方法,称之为 匿名函数、 Lambda 表达式 或 Closure 闭包。你可以将匿名方法赋值给一个变量然后使用它,比如将该变量添加到集合或从中删除。

匿名方法看起来与命名方法类似,在括号之间可以定义参数,参数之间用逗号分割。

后面大括号中的内容则为函数体:

([[类型参数[, …]])
  函数体;
;

下面代码定义了只有一个参数 item 且没有参数类型的匿名方法。 List 中的每个元素都会调用这个函数,打印元素位置和值的字符串:

var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) 
  print('$list.indexOf(item): $item');
);

点击运行按钮执行代码。

如果函数体内只有一行返回语句,你可以使用胖箭头缩写法。粘贴下面代码到 DartPad 中并点击运行按钮,验证两个函数是否一致。

list.forEach(
    (item) => print('$list.indexOf(item): $item'));

词法作用域

Dart 是词法有作用域语言,变量的作用域在写代码的时候就确定了,大括号内定义的变量只能在大括号内访问,与 Java 类似。

下面是一个嵌套函数中变量在多个作用域中的示例:

bool topLevel = true;

void main() 
  var insideMain = true;

  void myFunction() 
    var insideFunction = true;

    void nestedFunction() 
      var insideNestedFunction = true;

      assert(topLevel);
      assert(insideMain);
      assert(insideFunction);
      assert(insideNestedFunction);
    
  

注意 nestedFunction() 函数可以访问包括顶层变量在内的所有的变量。

词法闭包

闭包 即一个函数对象,即使函数对象的调用在它原始作用域之外,依然能够访问在它词法作用域内的变量。

函数可以封闭定义到它作用域内的变量。接下来的示例中,函数 makeAdder() 捕获了变量 addBy。无论函数在什么时候返回,它都可以使用捕获的 addBy 变量。

/// 返回一个将 [addBy] 添加到该函数参数的函数。
/// Returns a function that adds [addBy] to the
/// function's argument.
Function makeAdder(int addBy) 
  return (int i) => addBy + i;


void main() 
  // 生成加 2 的函数。
  var add2 = makeAdder(2);

  // 生成加 4 的函数。
  var add4 = makeAdder(4);

  assert(add2(3) == 5);
  assert(add4(3) == 7);

测试函数是否相等

下面是顶级函数,静态方法和示例方法相等性的测试示例:

void foo()  // 定义顶层函数 (A top-level function)

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;

  // 这两个闭包引用了相同的实例对象,因此它们相等。
  assert(y.baz == x);

  // 这两个闭包引用了不同的实例对象,因此它们不相等。
  assert(v.baz != w.baz);

返回值

所有的函数都有返回值。没有显示返回语句的函数最后一行默认为执行 return null;

foo() 

assert(foo() == null);

运算符

Dart 支持下表的操作符。你可以将这些运算符实现为 一个类的成员

描述

运算符

一元后缀

表达式++ 表达式-- () [] . ?.

一元前缀

-表达式 !表达式 ~表达式 ++表达式 --表达式

乘除法

* / % ~/

加减法

+ -

位运算

<< >> >>>

二进制与

&

二进制异或

^

二进制或

|

关系和类型测试

>= > <= < as is is!

相等判断

== !=

逻辑与

&&

逻辑或

||

空判断

??

条件表达式

表达式 1 ? 表达式 2 : 表达式 3

级联

..

赋值

= *= /= += -= &= ^= 等等……

 请注意:

上述运算符优先级是对 Dart 解析器行为的效仿。更准确的描述,请参阅 Dart 语言规范 中的语法。

一旦你使用了运算符,就创建了表达式。下面是一些运算符表达式的示例:

a++
a + b
a = b
a == b
c ? a : b
a is T

运算符表 中,运算符的优先级按先后排列,即第一行优先级最高,最后一行优先级最低,而同一行中,最左边的优先级最高,最右边的优先级最低。例如:% 运算符优先级高于 == ,而 == 高于 &&。根据优先级规则,那么意味着以下两行代码执行的效果相同:

// 括号提高了可读性。
// Parentheses improve readability.
if ((n % i == 0) && (d % i == 0)) ...

// 难以理解,但是与上面的代码效果一样。
if (n % i == 0 && d % i == 0) ...

 请注意:

对于有两个操作数的运算符,左边的操作数决定了运算符的功能。比如对于一个 Vector 对象和一个 Point 对象,表达式 aVector + aPoint 中所使用的是 Vector 对象中定义的相加运算符 (+)。

算术运算符

Dart 支持常用的算术运算符:

运算符描述
+
-表达式一元负, 也可以作为反转(反转表达式的符号)
*
/
~/除并取整
%取模

示例:

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); // 取余

assert('5/2 = $5 ~/ 2 r $5 % 2' == '5/2 = 2 r 1');

Dart 还支持自增自减操作。

Operator

++var

var = var + 1 (表达式的值为 var + 1)

var++var = var + 1 (表达式的值为 var)
--varvar = var – 1 (表达式的值为 var – 1)
var--var = var – 1 (表达式的值为 var)

示例:

var a, b;

a = 0;
b = ++a; // 在 b 赋值前将 a 增加 1。
assert(a == b); // 1 == 1

a = 0;
b = a++; // 在 b 赋值后将 a 增加 1。
assert(a != b); // 1 != 0

a = 0;
b = --a; // 在 b 赋值前将 a 减少 1。
assert(a == b); // -1 == -1

a = 0;
b = a--; // 在 b 赋值后将 a 减少 1。
assert(a != b); // -1 != 0

关系运算符

下表列出了关系运算符及含义:

Operator

==

相等

!=不等
>大于
<小于
>=大于等于
<=小于等于

要判断两个对象 x 和 y 是否表示相同的事物使用 == 即可。(在极少数情况下,可能需要使用 identical() 函数来确定两个对象是否完全相同。)。下面是 == 运算符的一些规则:

  1. 假设有变量 x 和 y,且 x 和 y 至少有一个为 null,则当且仅当 x 和 y 均为 null 时 x == y 才会返回 true,否则只有一个为 null 则返回 false。

  2. x.==(y) 将会返回值,这里不管有没有 y,即 y 是可选的。也就是说 == 其实是 x 中的一个方法,并且可以被重写。详情请查阅重写运算符

下面的代码给出了每一种关系运算符的示例:

assert(2 == 2);
assert(2 != 3);
assert(3 > 2);
assert(2 < 3);
assert(3 >= 3);
assert(

以上是关于『Flutter开发实战』一小时掌握Dart语言的主要内容,如果未能解决你的问题,请参考以下文章

『Flutter开发实战』十分钟入门Dart语言

Flutter完整开发实战详解(一Dart语言和Flutter基础)

Flutter入门Dart语言:操作符的基本用法

Flutter 实现高仿开眼 APP 的页面开发 01

Flutter--实战Flutter 简介

低价前景广阔的谷歌-Dart语言初见与Flutter进阶实战课程高级开发人员必备的融合开发技术