Dart 变量

变量

学习 Dart 中的变量。下面是创建并初始化变量的示例:

var name = 'Jack';Code language: JavaScript (javascript)

变量存储的是引用,名为 name 的变量中存放着一个引用,该引用指向值为 “Bob” 的字符串对象。

引用就是地址,内存中的地址,为了方便内存存储数据,给每个内存区域标上一个地址。相当于家里的门牌一样。

编译器会自动推导 name 的类型为 String,但你也可以手动指定类型来改变推导结果。如果一个变量需要存放多种类型的对象,可以将其类型声明为 Object(必要时也可以使用 dynamic)。

Object name = 'Jack';Code language: JavaScript (javascript)

另一种写法是显式指定类型(例如我们知道名字是文本,可以用字符串String存储,那么我们直接指定字符串就可以):

String name = 'Jack';Code language: JavaScript (javascript)

Null safety 空安全

空安全可以避免因误访问值为 null 的变量而引发的错误,这类错误被称为空值引用错误( null dereference error)。当你对一个运算结果为 null 的表达式访问属性或调用方法时,就会触发空值引用错误。

有一个例外:如果 null 本身支持该属性或方法(例如 toString()hashCode),则不会报错。借助空安全,Dart 编译器会在编译阶段就识别出这类潜在错误。

有编程经验的读者应该知道,空引用的错误,是很常见的。如果可以在编译阶段就避免这个错误,那么程序的稳定性健壮性会大大提高。

举个例子:假设你想获取 int 类型变量 i 的绝对值。如果 inull,执行 i.abs() 就会触发空解引用错误。在其他编程语言中,这段代码只会在程序运行时报错;而 Dart 编译器会直接禁止这类非法调用,提前拦截问题。

空安全带来三项核心改动:

1 可空类型,指定类型时,你可以控制该类型是否允许为空。只需要在类型末尾加一个问号就可以。

String? name  // 可空类型:值可以是 null 或字符串
String name   // 非可空类型:不能为 null,只能存放字符串Code language: JavaScript (javascript)

在有些编程语言中,string name 都可以存放null的,但是在dart中,是不可以的。dart如果要存放null,就需要string? 这个类型。

2 变量在使用前必须完成初始化。可空变量默认值就是 null,相当于自带初始化;而非可空类型没有默认值,强制你手动赋予初始值。Dart 不允许访问未初始化的变量,以此杜绝一种情况

void main() {
  int year;        // 错误:未初始化的非可空变量
  print(year);     // 编译错误:year 未初始化
}
Code language: Dart (dart)

以上编译会报错

C:\dartdemo\firstdart>dart run
Building package executable...
Failed to build firstdart:firstdart:
bin/firstdart.dart:7:9: Error: Non-nullable variable 'year' must be assigned before it can be used.
  print(year);     // 编译错误:year 未初始化
        ^^^^Code language: JavaScript (javascript)

如果是可空变量 是可以这样的

void main() {
  int? age;        // 可空变量,默认值为 null
  print(age);      // 输出null 但不报错
  
  //age = 18;        // 手动赋值
  //print(age);      // 输出 18
}Code language: JavaScript (javascript)

3 不能直接对可空类型的表达式访问属性、调用方法。同样存在例外:如果是 null 原生支持的属性或方法(hashCodetoString()),则允许调用。

void main() {
  String? name; // 可空类型,默认值为 null

  // 错误:直接访问 length 会报错
  // print(name.length);

  // 正确:允许调用 null 原生支持的方法
  print(name.toString());  // 输出 "null"
  print(name.hashCode);    // 输出一个整数

  // 正确:使用空安全操作符
  print(name?.length);     // 输出 null,不报错
}Code language: PHP (php)

默认值

未手动赋值的可空变量,初始值默认为 null。即便是数字类型变量,默认值也是 null

因为在 Dart 中,数字和其他所有数据一样,本质都是对象。

void main() {
  int? lineCount;
  assert(lineCount == null);
}Code language: JavaScript (javascript)

注意,生产环境代码会忽略 assert() 校验;但在开发阶段,若 assert(条件) 内的条件不成立,程序会抛出异常

开启空安全后,非可空变量使用前必须完成初始化:

int lineCount = 0;

局部变量不必在声明的同一行赋值,但必须在使用前完成赋值。下面这段代码是合法的,因为 Dart 能分析出执行 print()lineCount 一定是非空值:

int lineCount;

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

print(lineCount);

Code language: Dart (dart)

注意上面代码lineCount = countLines();和lineCount = 0;是给lineCount赋值,并不算是使用,使用意思是用到里面的值,是读取的意思。例如下面代码就会报错

void main() {
	int lineCount;

	print(lineCount);
}
Code language: Dart (dart)

编译报错

顶层变量与类成员变量属于延迟初始化:只有第一次被使用时,才会执行赋值逻辑。

late 修饰变量

late 修饰符有两种使用场景:

  1. 声明非可空变量,后续再为其赋值;
  2. 实现变量延迟初始化。

大多数情况下,Dart 的流程分析可以识别出:某个非可空变量在使用前已经被赋予非空值。但部分场景分析会失效,最常见的两种就是顶层变量和实例变量:编译器通常无法判断它们是否完成赋值,因此不会自动放行。

如果你能确定变量在使用前一定会被赋值,但编译器识别不到,就可以给变量添加 late 修饰来消除报错:

如下代码 description是全局变量,可能无法识别出是否赋值,但是你确定它绝对会赋值的,可以使用late 修饰

late String description;

void main() {
  description = 'Feijoada!';
  print(description);
}
Code language: Dart (dart)

提醒

若声明了 late 变量却始终没有赋值,当代码访问该变量时会抛出运行时异常。

如果你在声明 late 变量的同时直接给出初始值,那么这段初始化代码只会在变量第一次被访问时执行。这种延迟初始化在两种场景下十分实用:

  1. 该变量有可能全程不会被使用,且初始化操作开销很大;
  2. 初始化实例变量时,初始化逻辑需要访问 this

下面示例中,如果全程没有使用 temperature,开销较大的 readThermometer() 函数永远不会执行:

// 本程序仅在访问 temperature 时才会调用 readThermometer()
late String temperature = readThermometer(); // 延迟初始化
Code language: JavaScript (javascript)

final 与 const

如果你不希望变量的值被修改,可以使用 finalconst;两者可以单独使用,也可以搭配类型标注,替代 var

  • final 变量只能赋值一次;
  • const 变量是编译期常量(const 变量隐式带有 final 特性)。

注意

实例成员变量可以用 final 修饰,但不能使用 const;类级别的常量请使用 static const

下面是定义 final 变量的示例:

final name = 'Jack'; // 不标注类型
final String nickname = 'Jason';
Code language: PHP (php)

你无法修改 final 变量的值,编译器会直接报错:

void main() {
	final name = 'Jack'; // 不标注类型
	final String nickname = 'Jason';
	
	name = 'Alice'; // 报错:final 变量仅允许赋值一次
}Code language: JavaScript (javascript)
C:\dartdemo\firstdart>dart run
Building package executable...
Failed to build firstdart:firstdart:
bin/firstdart.dart:6:2: Error: Can't assign to the final variable 'name'.
        name = 'Alice'; // 报错:final 变量仅允许赋值一次Code language: PHP (php)

const 用于定义编译期常量。如果常量定义在类内部,需要加上 static 修饰为 static const。声明 const 变量时,赋值内容必须是编译期可确定的值,例如数字、字符串字面量、其他 const 变量,或是常量数字参与算术运算的结果:

const bar = 1000000; // 压强单位(达因/平方厘米)
const double atm = 1.01325 * bar; // 标准大气压
Code language: JavaScript (javascript)

const 关键字不只是用来声明常量变量,你还可以用它创建常量对象,或是定义能生成常量实例的构造函数。任意变量都可以接收一个常量对象作为值。

var foo = const [];
final bar = const [];
const baz = []; // 等价于 const []
Code language: PHP (php)

const 变量的赋值表达式中可以省略 const 关键字,就像上面 baz 的写法。

即便变量曾经接收常量对象,只要它不是 final、也不是 const,就可以修改它的引用指向:

foo = [1, 2, 3]; // foo 原本指向常量空数组,现在可以更换引用
Code language: JavaScript (javascript)

const 变量不允许重新赋值,编译器会报错:

// 静态分析:代码错误
baz = [42]; // 报错:常量变量不允许重新赋值
Code language: JavaScript (javascript)

定义常量时,可以搭配类型判断、类型强转(isas)、集合 if、展开运算符(......?):

void main() {
	const Object i = 3; // i 是 Object 类型常量,内部存放 int 值
	const list = [i as int]; // 使用类型强转
	const map = {if (i is int) i: 'int'}; // 使用 is 判断与集合if
	const set = {if (list is List<int>) ...list}; // 搭配展开运算符
}Code language: JavaScript (javascript)

注意

final 修饰的对象本身引用不可修改,但对象内部的字段可以更改;与之对比,const 修饰的对象及其内部所有数据都不可变更,属于完全不可变。

想要了解更多使用 const 创建常量集合的内容,请查看 列表、映射、类 相关文档。

* 通配符变量

版本说明,通配符变量要求 Dart 语言版本至少为 3.7。

_ 命名的通配符变量是无绑定局部变量 / 参数,本质只是占位符。如果带有初始化表达式,表达式依然会执行,但你无法读取该变量存储的值。同一作用域内可以定义多个名为 _ 的变量,不会出现命名冲突。

什么是通配符变量

Dart 中单个下划线 _ 就是通配符变量:

  1. 用来接收不需要使用的值,告诉编译器 “这个变量我不会读取、不会用到”;
  2. 编译器不会提示「变量定义未使用」警告;
  3. 不能读取 _(读取会报错),只能用来占位丢弃数据。
// 只取第1、3个值,第二个用 _ 丢弃
final [a, _, c] = [10, 20, 30];
print(a); // 10
print(c); // 30
// print(_); // 报错,不能读取通配符Code language: PHP (php)

顶层声明、会影响库私有访问权限的类成员,不允许使用通配符变量。仅块级作用域内的声明可以使用,示例如下:

  1. 局部变量声明
void main() {
  var _ = 1;
  int _ = 2;
}
Code language: JavaScript (javascript)
  1. for 循环变量
for (var _ in list) {}
Code language: PHP (php)
  1. catch 捕获参数
try {
  throw '!';
} catch (_) {
  print('oops');
}
Code language: PHP (php)
  1. 泛型类型、函数泛型参数
class T<_> {}
void genericFunction<_>() {}

takeGenericCallback(<_>() => true);
Code language: JavaScript (javascript)
  1. 函数参数
Foo(_, this._, super._, void _()) {}

list.where((_) => true);

void f(void g(int _, bool _)) {}

typedef T = void Function(String _, String _);Code language: JavaScript (javascript)
Previous:

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注