Flutter 基础 | Dart 语法

简介: 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'; // 不可变量,name 不能再次被赋值
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声明纯字符串,其中不会发生转义。


// 输出 this is a raw \n string
var str = r'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 = <Int>{}; // 声明一个空 set
var set3 = new Set(); // 声明一个空 set
var set4 = Set(); // 声明一个空 set,new 关键词可有可无


Map


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


var map = {
    'a': 1,
    'b': 2,
}; // 声明并初始化一个 map,自动推断类型为 Map<String,Int>
var map2 = Map<String,Int>(); // 声明一个空 map
map2['a'] = 1; // 写 map
var value = map['a']; //读 map


读写Map都通过[]


const & final


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 不能新增元素。


它和 final 的区别在集合上体现的很明显:


final numbers = [1,2,3];
numbers.add(4); // 没有问题
numbers = [4,5,6]; // 报错‘*The final variable can only be set once.’
const nums = [1,2,3];
nums.add(4);// 报错‘Cannot add to an unmodifiable list’ 
nums = [4,5,6];// 报错‘Constant variables can’t be assigned a value’


final 表示引用不可变,而内容是可变的。


const 表示内容和引用都不可变



声明类


class Pointer {
    double x;
    double y;
    void func() {...} // void 表示没有返回值
    double getX(){
        return x;
    }
}


  • 用关键词 class声明一个类。


  • 类体中用类型 变量名;来声明类成员变量。


  • 类体中用返回值 方法名(){方法体}来声明类实例方法。


继承


class Pointer {}
class SubPointer extends Pointer {}


继承使用关键词extends


构造方法


上述代码会在 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. 初始化列表


  1. 父类构造方法


  1. 子类构造方法


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 来实现初始化。


getter setter


若在类成员变量前增加getset关键词,表示需要自定义该变量的存取逻辑:


class Pointer {
  double x;
  double y;
  double get distance;// 抽象方法
  Pointer(this.x, this.y);
}


因为类不是抽象的,而却包含一个抽象方法,此时编译器会报错“ distance 必须有一个方法体,因为 Pointer 并不是抽象的”。可以这样改:


abstract class Pointer {
  double x;
  double y;
  double get distance;
  Pointer(this.x, this.y);
}


若不想抽象化,也可以直接就地实现:


class Pointer {
  double x;
  double y;
  double get distance {
    return sqrt(x * x + y * y);
  }
  Pointer(this.x, this.y);
}


distance 的语义是“坐标点与原点的距离”。在 Dart 中这些基于现有属性求值而来的额外属性,通常被实现为 getter。


distance 的求值只有一行代码,也可以用如下简洁的方式表达:


class Pointer {
  double x;
  double y;
  double get distance => sqrt(x * x + y * y);
  Pointer(this.x, this.y);
}


该功能在 AndroidStudio 中有一个快捷键,Alt + Enter > Convert to expression body


然后就可以像这样读取这个成员:


var pointer = Pointer(3,4);
pointer.distance; // 5


getter 也常被用于私有成员访问器:


class Pointer {
  double _x; // 私有成员
  double _y; // 私有成员
  Pointer(this._x, this._y);
  double get x => _x; // 私有成员访问器
  double get y => _y; // 私有成员访问器
}


默认情况下,公有类成员变量(不以_开头的变量)都有一个隐含的 getter 方法,即直接返回变量。


setter 也是同理:


class Pointer {
  double x;
  double y;
  set moveBy(double delta);// 抽象方法,会报错
  Pointer(this.x, this.y);
}


这次换一种方式,将抽象方法的实现延迟到子类:


abstract class Pointer {
  double x;
  double y;
  set moveBy(double delta);
  Pointer(this.x, this.y);
}
class SubPointer extends Pointer {
  SubPointer(double x, double y) : super(x, y);
  // 实现父类抽象的 set
  @override
  set moveBy(double delta) {
    x += delta;
    y += delta;
  }
}


此时 moveBy 的语义是将“将坐标点平移 delta 个单位”。然后就可以像这样为它赋值:


var pointer = Pointer(3,4);
pointer.moveBy = 2; // Pointer(5,6)


定义额外 setter 和 getter 的意义在于 “把复杂的赋值和取值逻辑包装在一个变量的存取表达式中”。 这是一件功德无量的事情,它降低了上层代码的复杂度。


重载运算符


class Pointer {
    double x;
    double y;
    Pointer(this.x, this.y);
    operator +(p) => Pointer(this.x + p.x, this.y + p.y);
}


使用关键词operator表示重载运算符,这里重载的+,即重新定义加号运算符,然后就可以像这样使用:


var p1 = Pointer(1,2);
var p2 = Pointer(3,4);
var p3 = p1 + p2; // Pointer(4,6)


方法


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文件中)。定义在类中的方法没有可见性修饰符publicprivateprotected,而是简单的以下划线区分,_开头的函数及变量是私有的,否则是公有的。


可选参数 & 命名参数


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}) {...}


匿名方法


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


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


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


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


静态方法


static fun1(){
    ...
}


使用static关键词标记方法为静态的,它不隶属于类实例,所以也无法访问类成员。


异常


使用throw抛出异常:


throw StateError('invalid state.');


在 Dart 中任何类型都可以被抛出,比如下面抛出一个字符串:


throw "wrong input";


使用try-on-catch-finally捕获异常:


try {
  // 可能发生异常的语句
} on TimeoutException {
  // 捕获超时异常
} catch (e) {
  // 捕获任何异常
} finally {
  // 不管异常有没有发生这里都会执行
}


操作符


三元操作符


三元操作符格式如下:布尔值 ? 表达式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();
}


关键词


所有的关键词如下所示:



参考


Language tour | Dart


推荐阅读





目录
相关文章
|
2月前
|
Dart
如何在 Flutter 项目中使用 Dart 语言?
如何在 Flutter 项目中使用 Dart 语言?
127 58
|
17天前
|
Dart UED 开发者
flutter鸿蒙版本通过底部导航栏的实现熟悉架构及语法
这篇博客详细解析了一个 Flutter 应用的完整代码,实现了带有底部导航栏的功能,允许用户在不同页面之间切换。通过逐行讲解,帮助读者理解 Flutter 的结构、状态管理和组件交互。代码涵盖了从引入包、创建主入口、定义无状态和有状态组件,到构建用户界面的全过程。希望对 Flutter 开发者有所帮助。
145 3
|
20天前
|
Dart
flutter dart mixin 姿势
flutter dart mixin 姿势
|
1月前
|
Dart 开发者 Windows
flutter:dart的学习
本文介绍了Dart语言的下载方法及基本使用,包括在Windows系统上和VSCode中的安装步骤,并展示了如何运行Dart代码。此外,还详细说明了Dart的基础语法、构造函数、泛型以及库的使用方法。文中通过示例代码解释了闭包、运算符等概念,并介绍了Dart的新特性如非空断言操作符和延迟初始化变量。最后,提供了添加第三方库依赖的方法。
29 12
|
1月前
|
Dart UED 索引
flutter鸿蒙版本通过底部导航栏的实现熟悉架构及语法
flutter鸿蒙版本通过底部导航栏的实现熟悉架构及语法
23 2
|
3月前
|
Dart 前端开发 JavaScript
Flutter&Dart-异步编程Future、Stream极速入门
Flutter&Dart-异步编程Future、Stream极速入门
76 4
Flutter&Dart-异步编程Future、Stream极速入门
|
3月前
|
Dart JavaScript 前端开发
Dart或Flutter中解决异常-type ‘int‘ is not a subtype of type ‘double‘
Dart或Flutter中解决异常-type ‘int‘ is not a subtype of type ‘double‘
133 4
|
3月前
|
Dart
Flutter笔记:手动配置VSCode中Dart代码自动格式化
Flutter笔记:手动配置VSCode中Dart代码自动格式化
460 5
|
3月前
|
Dart 开发工具 Android开发
Android Studio导入Flutter项目提示Dart SDK is not configured
Android Studio导入Flutter项目提示Dart SDK is not configured
301 4
|
3月前
|
Dart 安全 API
Android跨平台开发之Dart 3.5 与 Flutter 3.24:革新跨平台应用开发
【Dart 3.5 与 Flutter 3.24:革新跨平台应用开发】首发于公众号“AntDream”。本文深度解析 Dart 3.5 和 Flutter 3.24 的新特性,包括空安全强化、Web 与原生互操作性增强及 Flutter GPU API 等,展示了如何提升代码质量和用户体验。
63 1