跳到主要内容

变量

这是一个创建变量并初始化它的示例

dart
var name = 'Bob';

变量存储引用。名为 name 的变量包含对值为 "Bob" 的 String 对象的引用。

`name` 变量的类型被推断为 String,但您可以通过指定来更改该类型。如果对象不限于单一类型,请指定 Object 类型(或在必要时指定 dynamic)。

dart
Object name = 'Bob';

另一种选择是显式声明将被推断的类型

dart
String name = 'Bob';

空安全

#

Dart 语言强制执行健全的空安全。

空安全可以防止因意外访问设置为 null 的变量而导致的错误。该错误称为空指针解引用错误。当您访问属性或在计算结果为 null 的表达式上调用方法时,会发生空指针解引用错误。此规则的一个例外是当 null 支持属性或方法时,例如 toString()hashCode。使用空安全,Dart 编译器会在编译时检测到这些潜在错误。

例如,假设您要查找 int 变量 i 的绝对值。如果 inull,则调用 i.abs() 会导致空指针解引用错误。在其他语言中,尝试这样做可能会导致运行时错误,但 Dart 的编译器禁止这些操作。因此,Dart 应用程序不会导致运行时错误。

空安全引入了三个关键更改

  1. 当您为变量、参数或其他相关组件指定类型时,您可以控制该类型是否允许 null。要启用可空性,请在类型声明的末尾添加 ?

    dart
    String? name  // Nullable type. Can be `null` or string.
    
    String name   // Non-nullable type. Cannot be `null` but can be string.
  2. 您必须在使用变量之前初始化它们。可空变量默认为 null,因此默认情况下它们会被初始化。Dart 不会为不可空类型设置初始值。它强制您设置初始值。Dart 不允许您观察未初始化的变量。这可以防止您访问接收者类型可能为 nullnull 不支持所使用的方法或属性的属性或调用方法。

  3. 您无法访问可空类型表达式的属性或调用方法。同样的例外情况适用于 null 支持的属性或方法,例如 hashCodetoString()

健全的空安全将潜在的运行时错误更改为编辑时分析错误。当非空变量出现以下情况时,空安全会标记它:

  • 未使用非空值初始化。
  • 被赋值为 null 值。

此检查允许您在部署应用程序 *之前* 修复这些错误。

默认值

#

具有可空类型的未初始化变量的初始值为 null。即使是数字类型的变量最初也为 null,因为数字(就像 Dart 中的所有其他事物一样)都是对象。

dart
int? lineCount;
assert(lineCount == null);

使用空安全,您必须在使用不可空变量的值之前初始化它们

dart
int lineCount = 0;

您不必在声明局部变量的位置初始化它,但您需要在使用它之前为其赋值。例如,以下代码是有效的,因为 Dart 可以检测到在 lineCount 传递给 print() 时它不是 null

dart
int lineCount;

if (weLikeToCount) {
  lineCount = countLines();
} else {
  lineCount = 0;
}

print(lineCount);

顶层变量和类变量是延迟初始化的;初始化代码在首次使用变量时运行。

延迟变量

#

`late` 修饰符有两个用例

  • 声明在声明后初始化的不可空变量。
  • 延迟初始化变量。

通常,Dart 的控制流分析可以检测到何时在不可空变量使用之前将其设置为非空值,但有时分析会失败。两个常见的情况是顶层变量和实例变量:Dart 通常无法确定它们是否已设置,因此它不会尝试。

如果您确定变量在使用前已设置,但 Dart 不同意,您可以通过将变量标记为 late 来修复错误

dart
late String description;

void main() {
  description = 'Feijoada!';
  print(description);
}

当您将变量标记为 late 但在其声明时对其进行初始化时,初始化程序会在首次使用该变量时运行。在以下几种情况下,这种延迟初始化非常方便

  • 可能不需要该变量,并且初始化它的成本很高。
  • 您正在初始化一个实例变量,并且它的初始化程序需要访问 this

在以下示例中,如果从未使用过 temperature 变量,则永远不会调用开销很大的 readThermometer() 函数

dart
// This is the program's only call to readThermometer().
late String temperature = readThermometer(); // Lazily initialized.

Final 和 Const

#

如果您从不打算更改变量,请使用 finalconst,可以代替 var 或作为类型的补充。final 变量只能设置一次;const 变量是编译时常量。(const 变量隐式为 final。)

这是一个创建和设置 final 变量的示例

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

您无法更改 final 变量的值

✗ 静态分析:失败dart
name = 'Alice'; // Error: a final variable can only be set once.

对于您希望成为编译时常量的变量,请使用 const。如果 const 变量位于类级别,请将其标记为 static const。在您声明变量的位置,将值设置为编译时常量,例如数字或字符串字面量、const 变量或常量数字的算术运算结果

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

`const` 关键字不仅仅用于声明常量变量。您还可以使用它来创建常量 *值*,以及声明 *创建* 常量值的构造函数。任何变量都可以具有常量值。

dart
var foo = const [];
final bar = const [];
const baz = []; // Equivalent to `const []`

您可以从 const 声明的初始化表达式中省略 const,就像上面的 baz 一样。有关详细信息,请参阅 不要冗余地使用 const。

您可以更改非 final、非 const 变量的值,即使它曾经具有 const

dart
foo = [1, 2, 3]; // Was const []

您无法更改 const 变量的值

✗ 静态分析:失败dart
baz = [42]; // Error: Constant variables can't be assigned a value.

您可以定义使用类型检查和转换(isas)、集合 if 和扩展运算符(......?)的常量

dart
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.

有关使用 const 创建常量值的更多信息,请参阅 列表、映射和类。

通配符变量

#

名称为 _ 的通配符变量声明一个非绑定的局部变量或参数;本质上是一个占位符。初始化器(如果存在)仍然会执行,但该值不可访问。在同一命名空间中可以存在多个名为 _ 的声明,而不会发生冲突错误。

可能影响库隐私的顶层声明或成员不是通配符变量的有效用法。块作用域本地的声明(例如以下示例)可以声明通配符

  • 局部变量声明。

    dart
    main() {
      var _ = 1;
      int _ = 2;
    }
  • For 循环变量声明。

    dart
    for (var _ in list) {}
  • Catch 子句参数。

    dart
    try {
      throw '!';
    } catch (_) {
      print('oops');
    }
  • 泛型类型和函数类型参数。

    dart
    class T<_> {}
    void genericFunction<_>() {}
    
    takeGenericCallback(<_>() => true);
  • 函数参数。

    dart
    Foo(_, this._, super._, void _()) {}
    
    list.where((_) => true);
    
    void f(void g(int _, bool _)) {}
    
    typedef T = void Function(String _, String _);