Dart 变量的类型,定义与使用

本文主要说明如何定义,使用Dart变量。

Variables(变量)

下面是声明变量并赋值的示例:

var name = 'Bob';

变量是一个引用。上面名字为 name 的变量引用了 一个内容为 “Bob” 的 String 对象。

Default value(默认值)

没有初始化的变量自动获取一个默认值为 null。类型为数字的 变量如何没有初始化其值也是 null,不要忘记了 数字类型也是对象。

int lineCount;
assert(lineCount == null);
// Variables (even if they will be numbers) are initially null.

注意: 在生产模式 assert() 语句被忽略了。在检查模式 assert(condition) 会执行,如果条件不为 true 则会抛出一个异常。详情请参考 Assert 部分。

Optional types(可选的类型)

在声明变量的时候,你可以选择加上具体 类型:

String name = 'Bob';

添加类型可以更加清晰的表达你的意图。 IDE 编译器等工具有可以使用类型来更好的帮助你, 可以提供代码补全、提前发现 bug 等功能。

注意: 对于局部变量,这里准守 代码风格推荐 部分的建议,使用 var 而不是具体的类型来定义局部变量。

Final and const

如果你以后不打算修改一个变量,使用 final 或者 const。 一个 final 变量只能赋值一次;一个 const 变量是编译时常量。 (Const 变量同时也是 final 变量。) 顶级的 final 变量或者类中的 final 变量在 第一次使用的时候初始化。

注意:实例变量可以为 final 但是不能是 const 。

下面是 final 变量的示例:

final name = 'Bob'; // Or: final String name = 'Bob';
// name = 'Alice';  // Uncommenting this causes an error

const变量为编译时常量。 如果 const 变量在类中,请定义为 static const。 可以直接定义 const和其值,也 可以定义一个 const 变量使用其他 const 变量的值来初始化其值。

const bar = 1000000;       // Unit of pressure (dynes/cm2)
const atm = 1.01325 * bar; // Standard atmosphere

const 关键字不仅仅只用来定义常量。 有可以用来创建不变的值, 还能定义构造函数为 const 类型的,这种类型 的构造函数创建的对象是不可改变的。任何变量都可以有一个不变的值。

// Note: [] creates an empty list.
// const [] creates an empty, immutable list (EIA).
var foo = const [];   // foo is currently an EIA.
final bar = const []; // bar will always be an EIA.
const baz = const []; // baz is a compile-time constant EIA.

// You can change the value of a non-final, non-const variable,
// even if it used to have a const value.
foo = [];

// You can't change the value of a final or const variable.
// bar = []; // Unhandled exception.
// baz = []; // Unhandled exception.

关于使用 const 来创建不变的值的更多信息,请参考: Lists、 Maps、 和 Classes。

Built-in types(内置的类型)

Dart 内置支持下面这些类型:

  • numbers
  • strings
  • booleans
  • lists (也被称之为 arrays)
  • maps
  • runes (用于在字符串中表示 Unicode 字符)
  • symbols

你可以直接使用字母量来初始化上面的这些类型。 例如 ‘this is a string’ 是一个字符串字面量, true 是一个布尔字面量。

由于 Dart 中每个变量引用的都是一个对象 – 一个类的实例, 你通常使用构造函数来初始化变量。 一些内置的类型具有自己的构造函数。例如, 可以使用 Map()构造函数来创建一个 map, 就像这样 new Map()。

Numbers(数值)

Dart 支持两种类型的数字:

int 整数值,其取值通常位于 -253 和 253 之间。

double 64-bit (双精度) 浮点数,符合 IEEE 754 标准。

intdouble 都是 num 的子类。 num 类型定义了基本的操作符,例如 +, -, /, 和 *, 还定义了 abs()ceil()、和 floor() 等 函数。 (位操作符,例如 >> 定义在 int 类中。) 如果 num 或者其子类型不满足你的要求,请参考 dart:math 库。

注意: 不在 -253 到 253 范围内的整数在 Dart 中的行为 和 JavaScript 中表现不一样。 原因在于 Dart 具有任意精度的整数,而 JavaScript 没有。 参考 问题 1533 了解更多信息。

整数是不带小数点的数字。下面是一些定义 整数的方式:

var x = 1;
var hex = 0xDEADBEEF;
var bigInt = 34653465834652437659238476592374958739845729;

如果一个数带小数点,则其为 double, 下面是定义 double 的一些方式:

var y = 1.1;
var exponents = 1.42e5;

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

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

整数类型支持传统的位移操作符,(<<, >>), AND (&), 和 OR (|) 。例如:

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.";

可以在字符串中使用表达式,用法是这样的: ${expression}。如果表达式是一个比赛服,可以省略 {}。 如果表达式的结果为一个对象,则 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 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 = '''
You can create
multi-line strings like this one.
''';

var s2 = """This is also a
multi-line string.""";

通过提供一个 r 前缀可以创建一个 “原始 raw” 字符串:

var s = r"In a raw string, even \n isn't special.";

参考 Runes 来了解如何在字符串 中表达 Unicode 字符。

字符串字面量是编译时常量, 带有字符串插值的字符串定义,若干插值表达式引用的为编译时常量则其结果也是编译时常量。

// These work in a const string.
const aConstNum = 0;
const aConstBool = true;
const aConstString = 'a constant string';

// These do NOT work in a const string.
var aNum = 0;
var aBool = true;
var aString = 'a string';
const aConstList = const [1, 2, 3];

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

使用字符串的更多信息请参考: 字符串和正则表达式。

Booleans(布尔值)

为了代表布尔值,Dart 有一个名字为 bool 的类型。 只有两个对象是布尔类型的:true 和 false 所创建的对象, 这两个对象也都是编译时常量。

当 Dart 需要一个布尔值的时候,只有 true 对象才被认为是 true。 所有其他的值都是 flase。这点和 JavaScript 不一样, 像 1、 “aString”、 以及 someObject 等值都被认为是 false。

例如,下面的代码在 JavaScript 和 Dart 中都是合法的代码:

var name = 'Bob';
if (name) {
  // Prints in JavaScript, not in Dart.
  print('You have a name!');
}

如果在 JavaScript 中运行,则会打印出 “You have a name!”,在 JavaScript 中 name 是非 null 对象所以认为是 true。但是在 Dart 的生产模式下 运行,这不会打印任何内容,原因是 name 被转换为 false了,原因在于 name != true。 如果在 Dart 检查模式运行,上面的 代码将会抛出一个异常,表示 name 变量不是一个布尔值。

下面是另外一个在 JavaScript 和 Dart 中表现不一致的示例:

if (1) {
  print('JS prints this line.');
} else {
  print('Dart in production mode prints this line.');
  // However, in checked mode, if (1) throws an
  // exception because 1 is not boolean.
}

注意: 上面两个示例只能在 Dart 生产模式下运行, 在检查模式下,会抛出异常表明 变量不是所期望的布尔类型。

Dart 这样设计布尔值,是为了避免奇怪的行为。很多 JavaScript 代码 都遇到这种问题。 对于你来说,在写代码的时候你不用这些写代码: if (nonbooleanValue),你应该显式的 判断变量是否为布尔值类型。例如:

// Check for an empty string.
var fullName = '';
assert(fullName.isEmpty);

// Check for zero.
var hitPoints = 0;
assert(hitPoints <= 0);

// Check for null.
var unicorn;
assert(unicorn == null);

// Check for NaN.
var iMeantToDoThis = 0 / 0;
assert(iMeantToDoThis.isNaN);

Lists(列表)

也许 array (或者有序集合)是所有编程语言中最常见的集合类型。 在 Dart 中数组就是 List 对象。所以 通常我们都称之为 lists。

Dart list 字面量和 JavaScript 的数组字面量类似。下面是一个 Dart list 的示例:

var list = [1, 2, 3];

Lists 的下标索引从 0 开始,第一个元素的索引是 0. list.length - 1 是最后一个元素的索引。 访问 list 的长度和元素与 JavaScript 中的用法一样:

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; // Uncommenting this causes an error.

List 类型有很多函数可以操作 list。 更多信息参考 泛型集合

Maps 通常来说,Map 是一个键值对相关的对象。 键和值可以是任何类型的对象。每个 键 只出现一次, 而一个值则可以出现多次。Dart 通过 map 字面量 和 Map 类型支持 map。

下面是一些创建简单 map 的示例:

var gifts = {
// Keys      Values
  'first' : 'partridge',
  'second': 'turtledoves',
  'fifth' : 'golden rings'
};

var nobleGases = {
// Keys  Values
  2 :   'helium',
  10:   'neon',
  18:   'argon',
};

使用 Map 构造函数也可以实现同样的功能:

var gifts = new Map();
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';

var nobleGases = new Map();
nobleGases[2] = 'helium';
nobleGases[10] = 'neon';
nobleGases[18] = 'argon';

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

如果所查找的键不存在,则返回 null:

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

使用 .length 来获取 map 中键值对的数目:

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

同样使用 const 可以创建一个 编译时常量的 map:

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

// constantMap[2] = 'Helium'; // Uncommenting this causes an error.

关于 Map 的更多信息请参考 泛型 和 Maps。

Runes

在 Dart 中,runes 代表字符串的 UTF-32 code points。

Unicode 为每一个字符、标点符号、表情符号等都定义了 一个唯一的数值。 由于 Dart 字符串是 UTF-16 code units 字符序列, 所以在字符串中表达 32-bit Unicode 值就需要 新的语法了。

通常使用 \uXXXX 的方式来表示 Unicode code point, 这里的 XXXX 是4个 16 进制的数。 例如,心形符号 (♥) 是 \u2665。 对于非 4 个数值的情况, 把编码值放到大括号中即可。 例如,笑脸 emoji (😆) 是 \u{1f600}。

String 类 有一些属性可以提取 rune 信息。 codeUnitAtcodeUnit 属性返回 16-bit code units。 使用 runes 属性来获取字符串的 runes 信息。

下面是示例演示了 runes、 16-bit code units、和 32-bit code points 之间的关系。 点击运行按钮 ( red-run.png ) 查看 runes 。

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));
}

注意: 使用 list 操作 runes 的时候请小心。 根据所操作的语种、字符集等, 这种操作方式可能导致你的字符串出问题。 更多信息参考 Stack Overflow 上的一个问题: 我如何在 Dart 中反转一个字符串?

Symbols

一个 Symbol object 代表 Dart 程序中声明的操作符或者标识符。 你也许从来不会用到 Symbol,但是该功能对于通过名字来引用标识符的情况 是非常有价值的,特别是混淆后的代码, 标识符的名字被混淆了,但是 Symbol 的名字不会改变。

使用 Symbol 字面量来获取标识符的 symbol 对象,也就是在标识符 前面添加一个 # 符号:

#radix
#bar

Symbol 字面量定义是编译时常量。

关于 symbols 的详情,请参考 dart:mirrors - reflection。