Flutter 基础 | Dart 语法

该系列记录了从零开始学习 Flutter 的学习路径,第一站就是 Dart 语法。本文可以扫除看 Flutter 教程,写 Flutter 代码中和语言有关的绝大部分障碍。值得收藏~

声明并初始化变量

int i = 1; // 非空类型必须被初始化
int? k = 2; // 可空类型
int? h; // 只声明未初始化,则默认为 null
var j = 2; // 自动推断类型为int
late int m; // 惰性加载
final name = 'taylor'; // 不可变量
final String name = 'taylor'; // 不可变量

Dart 中语句的结尾是带有分号;的。

Dart 中声明变量时可以选择是否为它赋初始值。但非空类型必须被初始化。

Dart 中声明变量可以显示指明类型,类型分为可空和非空,前者用类型?表示。也可以用var来声明变量,此时编译器会根据变量初始值自动推断类型。

late关键词用于表示惰性加载,它让非空类型惰性赋值成为可能。得在使用它之前赋值,否则会报运行时错误。

惰性加载用于延迟计算耗时操作,比如:

late String str = readFile();

str 的值不会被计算,直到它第一次被使用。

??可为空类型提供默认值

String? name;
var ret = name ?? ''

如果 name 为空则返回空字串,否则返回 name 本身。

数量

在 Dart 中intdouble是两个有关数量的内建类型,它们都是num的子类型。

若声明变量为num,则可同时被赋值为intdouble

num i = 1;
i = 2.5;

字串

''""都可以定义一个字串

var str1 = 'this is a str';
var str2 = "this is another str";

字串拼接

使用+拼接字符串

var str = 'abc'+'def'; // 输出 abcdef

多行字串

使用'''声明多行字符串

var str = '''
this is a
multiple line string
'''
;

纯字串

使用r声明纯字符串,其中不会发生转义。

var str = r'this is a raw \n string'; // 输出 this is a raw \n string

字串内嵌表达式

字符串中可以内嵌使用${}来包裹一个有返回值的表达式。

var str = 'today is ${data.get()}';

字串和数量相互转化:

int.parse('1'); // 将字串转换为 int
double.parse('1.1'); // 将字串转换为 double
1.toString(); // 将 int 转换为字串
1.123.toStringAsFixed(2); // 将 double 转换为字串,输出 '1.12'

集合

声明 List

与有序列表对应的类型是List

[]声明有序列表,并用,分割列表元素,最后一个列表元素后依然可以跟一个,以消灭复制粘贴带来的错误。

var list = [1,2,3,];

存取 List 元素

列表是基于索引的线性结构,索引从 0 开始。使用[index]可以获取指定索引的列表元素:

var first = list[0]; // 获取列表第一个元素
list[0] = 1; //为列表第一个元素赋值

展开操作符

...是展开操作符,用于将一个列表的所有元素展开:

var list1 = [1, 2, 3];
var list2 = [...list1, 4, 5, 6];

上述代码在声明 list2 时将 list1 展开,此时 list2 包含 [1,2,3,4,5,6]

除此之外,还有一个可空的展开操作符...?,用于过滤为null的列表:

var list; // 声明时未赋初始值,则默认为 null
var list2 = [1, ...?list]; // 此时 list2 内容还是[1]

条件插入

iffor是两个条件表达式,用于有条件的向列表中插入内容:

var list = [
'aa',
'bb',
if (hasMore) 'cc'
];

如果 hasMore 为 true 则 list 中包含'cc',否则就不包含。

var list = [1,2,3];
var list2 = [
'0',
for (var i in list) '$i'
];// list2 中包含 0,1,2,3

在构建 list2 的时候,通过遍历 list 来向其中添加元素。

Set

Set中的元素是可不重复的。

{}声明Set,并用,分割元素:

var set = {1,2,3}; // 声明一个 set 并赋初始元素
var set2 = {}; // 声明一个空 set
var set3 = new Set(); // 声明一个空 set
var set4 = Set(); // 声明一个空 setnew 关键词可有可无

Map

Map是键值对,其中键可以是任何类型但不能重复。

var map = {
'a': 1,
'b': 2,
}; // 声明并初始化一个 map,自动推断类型为 Map

var map2 = Map(); // 声明一个空 map
map2['a'] = 1; // 写 map
var value = map['a']; //读 map
,int>,int>

读写Map都通过[]

const

const是一个关键词,表示一经赋值则不可修改:

// list
var list = const [1,2,3];
list.add(4); // 运行时报错,const list 不可新增元素

// set
var set = const {1,2,3};
set.add(4); // 运行时报错,const set 不可新增元素

// map
var map = const {'a': 1};
map['b'] = 2; // 运行时报错,const map 不能新增元素。

声明类

class Pointer {
double x;
double y;

void func() {...} // void 表示没有返回值
double getX(){
return x;
}
}
  • 用关键词 class声明一个类。
  • 类体中用类型 变量名;来声明类成员变量。
  • 类体中用返回值 方法名(){方法体}来声明类实例方法。

构造方法

上述代码会在 x ,y 这里报错,说是非空字段必须被初始化。通常在构造方法中初始化成员变量。

构造方法是一种特殊的方法,它返回类实例且签名和类名一模一样。

class Point {
double x = 0;
double y = 0;
// 带两个参数的构造方法
Point(double x, double y) {
this.x = x;
this.y = y;
}
}

这种给成员变量直接赋值的构造方法有一种简洁的表达方式:

class Point {
double x = 0;
double y = 0;

Point(this.x, this.y); // 当方法没有方法体时,得用;表示结束
}

命名构造方法

Dart 中还有另一个构造方法,它的名字不必和类名一致:

class Point {
double x;
double y;

Point.fromMap(Map map)
: x = map['x'],
y = map['y'];
}

为 Point 声明一个名为fromMap的构造方法,其中的:表示初始化列表,初始化列表用来初始化成员变量,每一个初始化赋值语句用,隔开。

初始化列表的调用顺序是最高的,在一个类实例化时会遵循如下顺序进行初始化:

  1. 初始化列表
  2. 父类构造方法
  3. 子类构造方法

Point.fromMap() 从一个 Map 实例中取值并初始化给成员变量。

然后就可以像这样使用命名构造方法:

Map map = {'x': 1.0, 'y': 2.0};
Point point = Point.fromMap(map);

命名构造方法的好处是可以将复杂的成员赋值的逻辑隐藏在类内部。

继承构造方法

子类的构造方法不能独立存在,而是必须调用父类的构造方法:

class SubPoint extends Point {
SubPoint(double x, double y) {}
}

上述 SubPointer 的声明会报错,提示得调用父类构造方法,于是改造如下:

class SubPoint extends Point {
SubPoint(double x, double y) : super(x, y);
}

在初始化列表中通过super调用了父类的构造方法。父类命名构造方法的调用也是类似的:

class SubPoint extends Point {
SubPoint(Map map) : super.fromMap(map);
}

构造方法重定向

有些构造方法的目的只是调用另一个构造方法,为此可以在初始化列表中通过this实现:

class Point {
double x = 0;
double y = 0;

Point(this.x, this.y);
Point.onlyX(double x): this(x, 0);
}

Point.onlyX() 通过调用另一个构造方法并为 y 值赋值为 0 来实现初始化。

方法

Dart 中方法也是一种类型,对应Function类,所以方法可以被赋值给变量或作为参数传入另一个方法。

// 下面声明的两个方法是等价的。
bool isValid(int value){
return value != 0;
}

isValid(int value){// 可自动推断返回值类型为 bool
return value != 0;
}

声明一个返回布尔值的方法,它需传入一个 int 类型的参数。

其中方法返回值bool是可有可无的。

bool isValid(int value) => value != 0;

如果方法体只有一行表达式,可将其书写成单行方法样式,方法名和方法体用=>连接。

Dart 中的方法不必隶属于一个类,它也可以顶层方法的形式出现(即定义在.dart文件中)。定义在类中的方法没有可见性修饰符public private protected ,而是简单的以下划线区分,_开头的函数及变量是私有的,否则是公有的。

可选参数 & 命名参数

Dart 方法可以拥有任意数据的参数,对于非必要参数,可将其声明为可选参数,调用方法时,就不用为其传入实参:

bool isValid(int value1, [int value2 = 2, int value3 = 3]){...}

定义了一个具有两个可选参数的方法,其中第二三个参数用[]包裹,表示是可选的。而且在声明方法时为可选参数提供了默认值,以便在未提供相应实参时使用。所以如下对该方法的调用都是合法的。

var ret = isValid(1) // 不传任何可选参数
var ret2 = isValid(1,2) // 传入1个可选参数
var ret3 = isValid(1,2,3) // 传入2个可选参数

使用[]定义可选参数时,如果想只给 value1,value3 传参,则无法做到。于是乎就有了{}

bool isValid(int value1, {int value2 = 2, int value3 = 3}) {...}

然后就可以跳过 value2 直接给 value3 传参:

var ret = isValid(1, value3 : 3)

这种语法叫可选命名参数

Dart 还提供了关键词required指定在众多可选命名参数中哪些是必选的:

bool isValid(int value1, {int value2, required int value3}) {...}

匿名方法

匿名方法表示在给定参数上进行一顿操作,它的定义语法如下:

(类型 形参) {
方法体
};

如果方法体只有一行代码可以将匿名函数用单行表示:

(类型 形参) => 方法体;

操作符

三元操作符

三元操作符格式如下:布尔值 ? 表达式1 : 表达式2;

var ret = isValid ? 'good' : 'no-good';

如果 isValid 为 true 则返回表达式1,否则返回表达式2。

瀑布符

该操作符..用于合并在同一对象上的多个连续操作:

val paint = Paint()
..color = Colors.black
..strokeCap = StrokeCap.round
..strokeWidth = 5.0

构建一个画笔对象并连续设置了 3 个属性。

如果对象可控则需使用?..

paint?..color = Colors.black
..strokeCap = StrokeCap.round
..strokeWidth = 5.0

类型判定操作符

as 是强转操作符,表示将一个类型强转为另一个类型。

is 是类型判定操作符,用于判断某个实例是否是指定类型。

is! 是与 is 相反的判定。

流程控制

if-else

if (isRaining()) {
you.bringRainCoat();
} else if (isSnowing()) {
you.wearJacket();
} else {
car.putTopDown();
}

for

for (var i = 0; i < 5; i++) {
message.write('!');
}

如果不需要关心循环的索引值,则可以这样:

for (var item in list) {
item.do();
}

while

while (!isDone()) {
doSomething();
}
do {
printLine();
} while (!atEndOfPage());

break & continue

break & continue 可用于 for 和 while 循环。

break用于跳出循环

var i = 0
while (true) {
if (i > 2) break;
print('$i');
i++;
} // 输出 0,1,2

continue用于跳过当前循环的剩余代码:

for (int i = 0; i < 10; i++) {
if (i % 2 == 0) continue;
print('$i');
}// 输出 1,3,5,7,9

switch-case

Dart 中的 switch-case 支持 String、int、枚举的比较,以 String 为例:

var command = 'OPEN';
switch (command) {
case 'CLOSED':
case 'PENDING': // 两个 case 共用逻辑
executePending();
break; // 必须有 break
case 'APPROVED':
executeApproved();
break;
case 'DENIED':
executeDenied();
break;
case 'OPEN':
executeOpen();
break;
default: // 当所有 case 都未命中时执行 default 逻辑
executeUnknown();
}

关键词

所有的关键词如下所示:

abstract 2elseimport 2show 1
as 2enuminstatic 2
assertexport 2interface 2super
async 1extendsisswitch
await 3extension 2late 2sync 1
breakexternal 2library 2this
casefactory 2mixin 2throw
catchfalsenewtrue
classfinalnulltry
constfinallyon 1typedef 2
continueforoperator 2var
covariant 2Function 2part 2void
defaultget 2required 2while
deferred 2hide 1rethrowwith
doifreturnyield 3
dynamic 2implements 2set 2


作者:唐子玄
链接:https://juejin.cn/post/7028710779171897351
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册