Dart【01】基本概念和变量

215 阅读8分钟

写在前面

本篇文章是《Dart教程》系列的第一篇,意在帮助有意学习Flutter的朋友了解Dart语言。

基础概念

  • 应用程序需要顶级main()函数,从这里开始执行
  • 所有可以放入变量的内容都是对象。
  • Dart是强类型的。但可以使用var来声明变量,Dart可以自动推断类型。
  • 支持空安全。
  • 支持顶级变量与顶级函数(直接写在.dart文件里的变量和函数)。
  • 如果类,变量,方法以下划线 ( _) 开头,则它对其库是私有的。

变量声明

变量简介

这是创建变量并对其进行初始化的示例:

var name = 'Bob' ; 

变量仅存储对象引用。以上代码为变量name包含对String类型的值为“ Bob”的对象的引用。

上面的代码中name变量的类型推断为String,但是您可以通过指定它来更改它的类型。

如果对象不限于单一类型,请指定Object类型(或在必要时使用dynamic)。

Object name = 'Bob';

另一个选择是显式声明将要推断的类型:

String name = 'Bob';

不要轻易使用var,使用var的时机请参考effective-dart。

PS:简单看了下Flutter源代码,几乎没有var声明的变量。

变量仅存储对象引用

一段代码示例来演示变量仅存储对象引用:

//示例3
class Test{
  int _num = 0;
}
void main(){
  var test1 = Test();
  var test2 = Test();
  var a = test1;
  var b  = a;
  print(identical(b,test1));
  print(identical(b,test2));
  test1._num = 10;
  print(b._num);
}

PS:identical函数:检查两个引用是否指向同一个对象。

输出如下:

true false 10

另一段代码示例来证明变量仅存储对象引用:

//示例4
void main(){
  var test = Test();
  changeValue(test);
  print(test._num);
}
class Test {
  int _num = 0;
}
void changeValue(Test data) {
  print(data._num);
  data._num = 10;
}

输出如下:

0 10

看了上面两个例子,你应该已经理解Dart中变量仅存储对象引用的含义了。

默认值

可为空的未初始化变量默认值为null

如果启用空安全,则必须在使用非空变量之前对其进行初始化。

换句话说,您不必在声明该变量时进行初始化,但是你需要在使用它之前进行初始化。

例如,以下代码是正确的,因为当lineCount传递给print()时,Dart可以检测到lineCount是非空的:

//示例5
int lineCount;
​
if (true) {
  lineCount = 1;
} else {
  lineCount = 0;
}
  
print(lineCount);

late

Dart 2.12版本添加了late修饰符,该修饰符有两个作用:

  • 声明一个在声明后初始化的,不能为空的变量。
  • 延迟初始化变量。

如果您确定在使用变量之前已设置了变量,但无法通过Dart的编译时检查,则可以通过将变量标记为late来修复错误:

//示例6
late String test;
​
void main() {
  test = 'Freida!';
  print(test);
}
  

如果未能初始化late变量,则使用该变量时会发生运行时错误。

如果将变量标记为late,但在声明时对其进行初始化,那么初始化程序将在变量第一次使用时运行。

这种延迟初始化在以下几种情况下是很方便的:

  • 可能不会用到该变量,并且初始化它的成本很高。
  • 您正在初始化实例变量,并且其初始值设定项需要访问this

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

late String temperature = _readThermometer(); // 延迟初始化

final

如果您从不打算更改变量,请使用finalconst代替var或其他类型。final修饰的变量只能设置一次。final修饰的顶级变量或类变量(静态变量)在首次使用时被初始化。

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

//示例7
final name = 'Bob'; // final修饰的变量可以没有类型注解
final String nickname = 'Bobby';

您不能更改final变量的值:

name = 'Alice'; // 错误:final变量只能设置一次。

const

简介

如果你想要编译时常量,使用const

const修饰的变量在编译时就已经固定(const变量隐式为final),也就意味着const修饰的变量必须在声明时进行初始化,例如const list = [1]

如果想用const修饰类变量,则将其标记为static const

使用const关键字需要在声明变量的地方,将设置为编译时常量,编译时常量类型有很多,例如数字或字符串,const变量或对常量进行算术运算:

//示例8
const num = 1000000;
const atm = 1.5 * num;

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

var foo = const [];
final bar = const [];
const baz = []; //等同于const []

可以在const声明的初始化表达式中省略const,就像上面的baz那样。

您可以更改不是用finalconst修饰的变量,即使该变量的const变量:

foo = [1, 2, 3];//正确

您不能更改const变量的值:

baz = [ 42 ]; //错误:无法为常量变量分配值。  

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

const Object i = 3; 
const list = [i as int]; 
const map = {if (i is int) i: "int"}; 
const set = {if (list is List<int>) ...list}; 
       

如果声明了一个const集合,则其中的所有内容也必须递归地为const

const不能修饰类的实例(instance)变量。

注意:尽管final修饰的对象无法重新赋值,但是可以更改对象的字段。相比之下,const对象及其字段不能更改:它们是不可变的。

使用

关于const还有一个重要的知识点:

const修饰的相同的变量存储的引用都一致,换句话说所有内容相同的const变量都是同一个对象。

对于任何给定的const变量,无论对const表达式求值多少次,都将重用单个const对象。

有点难读是吧,一段代码示例来向你展示这句话的含义:

//示例9.1
const a = [1, 2];
const b = [1, 2];
print(identical(a, b));

输出如下:

true

为了区别const变量和普通变量的区别,再来看一段例子:

//示例9.2
var a = [1, 2];
var b = [1, 2];
print(identical(a, b));

false

不可变对象

const可以使对象不可变。为了使一个类的对象不可变,我们需要在构造函数中使用const关键字, 并将所有字段都设置为final, 如下所示:

//示例10
class A {
  final a, b;
​
  const A(this.a, this.b);
}
var a = A(1, 2);

a对象就是不可变的对象。使用const修饰类的构造函数还有一个好处,我们可以在声明变量的时候在值上添加const:

var a = const A(1, 2);

声明变量的时候在值上添加const可以避免重复创建相同对象浪费内存。

PS:在Flutter源代码中,我们可以看到所有的widget都是不可变对象。同样我们在使用某些可以在编译之前确定属性的widget时,用const创建是更好的做法,避免重复创建相同对象浪费内存。

总结

Dart _私有的级别?

声明当前变量的文件。

Dart是值传递还是引用传递?

值传递

Dart中哪些变量是延迟初始化的?如何将成员变量延迟初始化?

顶级变量,类变量(static)。

使用late关键字初始化成员变量。

PS:之前的文章中我们有这样一句话"final修饰的顶级变量或类变量(静态变量)在首次使用时被初始化。",其实顶级变量和类变量(static)本身就是延迟初始化的,final关键字只是没有影响顶级变量和类变量本身就有的延迟初始化的功能,并非是final关键字赋予了顶级变量和类变量延迟初始化的功能。latefinal有区别的一点是,late可以让本身不是延迟初始化的成员变量变成延迟初始化。

多说一句,const作为编译时常量,会影响顶级变量和类变量本身就有的延迟初始化的功能。这个问题上面没有讲,答不上来也正常,后续顶级变量应该也不会再写了,如果不知道这个容易形成误区,所以把这个知识加到这里。

Dart final和const的共同点和区别?

1.共同点:

  • 都是声明常量,初始化后不能再赋值。
  • 声明的类型可以省略。

2.区别:

final:

  • 运行时常量,可以初始化一次。
  • 可以修饰可变对象。
  • 可以修饰实例(instance)变量。

const:

  • 编译时常量,声明时必须进行初始化。
  • 不可以修饰可变对象。
  • 不可以修饰实例(instance)变量。
  • const修饰的顶级变量和类变量不能延迟初始化,编译时就进行初始化。

Dart如何创建不可变对象?

使用const修饰类的构造方法,使用final修饰类里的所有属性。