Dart-语法基础

2021-03-13  本文已影响0人  AilurusFulgens

变量

定义

// 使用var定义一个变量,可以自动推断类型
var name = 'Bob';
// 使用指定类型定义一个变量
String text = '123';
// 使用dynamic和Object定义一个不局限于单一类型的变量
dynamic a = 1;
Object obj = true;

dynamicObject的区别
dynamic:变量在运行时确定实际类型
Object:变量在编译时确定实际类型,效率优于dynamic

默认值

Dart 中一切皆为对象,包括 intdouble

// Dart中,未初始化的变量均有一个默认值null
// 但对于未声明成可空的变量,使用前必须先赋值,否则无法使用
int i; // defalut null

final & const

// final修饰的变量是不可变的
final String s = '1';
// const修饰的变量表示编译时常亮
// 如果const修饰的变量在class中,则必须加上`static`关键字
const c = 'c';
class A {
  static const a = '1';
}

late

Since Dart 2.12


内置类型

Numbers

Dart 中包含两种 Numbers 类型,intdouble

Strings

使用单引号 ' ' 或双引号 " " 来创建字符串
使用 ${表达式} 进行字符串插值
使用三引号""" """创建多行字符串
在字符串前添加r,如r'n\n',生成原始raw字符串 n\n

// 使用+进行字符串拼接
var s1 = 'aaa' + 'bbb' + 'ccc';
print(s1); // aaabbbccc

// 将多个字符串放在一起进行字符串拼接
var s2 = 'aaa' 'bbb' 'ccc';
print(s2); // aaabbbccc

// 使用三个单引号或双引号创建多行字符串
var s3 = '''
  aaa
bbb
  ccc''';
print(s3);
//   aaa
// bbb
//   ccc

// 在字符串前加上r作为前缀创建raw字符串,不会处理任何的转义字符
var s4 = r'123 \n';
print(s4); // 123 \n

Booleans

bool b = true;
true / false

Lists

// 定义
var list1 = [1, 2, 3];
// 数组内可添加尾随逗号,方便格式化
var list2 = [
  'apple',
  'banana',
  'orange',
];

// 可以使用add/addAll添加元素
list1.add(4);
list1.addAll([5, 6, 7]);

// 循环list
for (var i = 0; i < list1.length; i++) {
  ...
}
for (var value in list1) {
  ...
}
list1.forEach((element) {
  ...
});

// 创建一个常量list,在list前添加const,或者用const声明
var constList = const ['a', 'b', 'c'];
const constList2 = [1, 2, 3];

// 可以使用...扩展操作符,将一个list中所有元素插入到另一个list中
var list1_1 = [0, ...list1]; //[0, 1, 2, 3, 4, 5, 6, 7]
// 可以使用...?空感知扩展操作符来避免发生异常
var listNull;
var list1Null = [0, ...?listNull]; //[0]

// 创建list时,可使用if或者for
var flag = false;
var listIf = [1, 2, if (flag) 3]; //[1, 2]
var listFor = [1, 2, for (var i in listIf) i]; //[1, 2, 1, 2]

const list = []var list = const [] 的区别
const list = []:list是一个常量数组,无法修改数组内的元素,也无法修改引用
var list = const []:list引用一个常量数组,无法修改数组内的元素,但可以修改list的引用

Sets

// 定义
var set1 = {1, 2};
// 定义一个空的set,需要在{}前指定类型,或赋给一个set类型的变量
var set2 = <String>{};
Set<String> set3 = {};
// 若向下面这样创建,则会默认生成一个Map<dynamic, dynamic>
var map = {};

// 可以使用add/addAll添加元素
set1.add(3);
set1.addAll({4, 5, 6});

// 循环set
for (var value in set1) {
  ...
}
set1.forEach((element) {
  ...
});

// 创建一个常量set,在set前添加const,或者用const声明
var constSet = const {1};
const constSet2 = {2};

//set可支持list一样的.../...?/if/for

Maps

// 定义一个map
var map = {'a':1,'b':2};
// 也可使用构造器创建map
// 若构造器未指定key value类型,则默认创建dynamic类型
var map2 = Map<int, int>();
map2[1] = 11;
map2[2] = 22;

// 循环map
map.forEach((key, value) {
  ...
});

// const,...,...?,if,for与list和set类似

Runes 与 grapheme clusters

不常用,Dart中文网

Symbols

不常用,Dart中文网


运算符

类型判断运算符 as & is & is!

as:类型转换
is:类型判断,若类型匹配,返回true
is!:类型判断,若类型匹配,返回false

// 如果emp是null或者不是Person类型,会抛异常
(emp as Person).firstName = 'Bob';
// 如果emp是null或者不是Person类型,不会抛异常,会走else
if (emp is Person) {
  emp.firstName = 'Bob';
}

赋值运算符

使用??=来为null值赋值,若不是null,则不会赋值

var i;
i ??= 1;
print(i); // 1
i ??= 2;
print(i); // 1

条件表达式

condition ? expr1 : expr2:与Java一样,三目运算符
expr1 ?? expr2:若expr1 non-null,则返回expr1,否则返回expr2

var flag = true;
print(flag ? "true" : "false"); // true
String? str;
print(str ?? "str is null"); // str is null
str = "abc";
print(str ?? "str is null"); // abc

级联运算符 .. / ?..

使用级联运算符../?..可以让你在同一个对象上执行一系列的操作,函数调用或访问字段

void main() {
  var t = Test()
    ..a() // aaa
    ..b() // bbb
    ..flag = false;
  print(t.flag); // false
}

class Test {
  var flag = true;

  void a() {
    print('aaa');
  }

  void b() {
    print('bbb');
  }
}

?..需要dart >= 2.12

void main() {
  var t = createTest(true)
    ?..a() // aaa
    ..b() // bbb
    ..flag = false;
  print(t?.flag); // false
}

class Test {
  var flag = true;

  void a() {
    print('aaa');
  }

  void b() {
    print('bbb');
  }
}

Test? createTest(bool flag) {
  if (flag)
    return Test();
  else
    return null;
}

Functions 函数

函数的定义与Java类似;
除了使用void修饰返回值的函数没有返回值,其他所有函数均有返回,若没有显式的返回值,默认最后一行执行return null

bool isNull(obj) {
  return obj == null;
}

如果不定义返回类型,该函数依然有效,但不推荐

isNull(obj) {
  return obj == null;
}

如果函数体内只有一个表达式,可以使用=>语法

isNull(obj) => obj == null;

参数 Parameters

函数有两种形式的参数:必要参数可选参数,必要参数定义在列表前面,可选参数定义在必要参数后面;
可选参数可以是 命名的使用{}括起来的参数) 或者 位置的使用[]括起来的参数);
必要参数不能设置默认值,可选参数可以设置默认值;

void main() {
  fun(1, "Hello"); //a: 1, b: Hello, c: null, d: 123
  fun(1, "Hello", c: 1, d: "Dart"); //a: 1, b: Hello, c: 1.0, d: Dart
  fun(1, "Hello", d: "Dart", c: 2.5); //a: 1, b: Hello, c: 2.5, d: Dart
  fun(1, "Hello", d: "Dart"); //a: 1, b: Hello, c: null, d: Dart
}

/// [a] 必要参数
/// [b] 必要参数
/// [c] 可选命名参数
/// [d] 可选命名参数,默认值为 123
void fun(int a, String b, {double? c, String d = "123"}) {
  print("a: $a, b: $b, c: $c, d: $d");
}

函数作为一级对象

Dart中,一切皆对象,所以函数也有类型Function,所以函数可以赋值给对象或者作为参数传递。

void main() {
  // 将fun函数作为参数传递
  [1, 2, 3].forEach(fun);
  // 将函数赋值给变量
  var f = (String b) => "@@@@ $b";
  print(f("123")); //@@@@ 123
}

void fun(int a) => print(a);

匿名函数

一个没有名字的函数被称为 匿名函数,又称 Lambda表达式Closure闭包

void main() {
  // 最常见的匿名函数就在forEach循环中
  [1, 2, 3].forEach((element) {
    print(element);
  });

  // 若函数体只有一行,则也可以使用=>语法
  [1, 2, 3].forEach((element) => print(element));
}

流程控制语句

void main() {
  int num = 14;
  // 第一个参数传入一个bool类型的表达式,第二个参数为可选参数,用作异常提示
  assert(num < 10, "num 必须小于 10");
  print('num < 10');
}

异常 Exception & Error

Dart 中的异常都是非必检异常,可以不捕获。

抛出异常

Dart 默认提供了ExceptionError及它们一系列子类,Dart还可以将任何非null对象作为异常抛出。

throw Exception("123");
throw "abc";

捕获异常

使用try catch来捕获。
catch支持两个参数,第一个参数(e):异常对象;第二个参数(s):栈信息。

void main() {
  try {
    test();
  } catch (e, s) {
    print(e);
    print(s);
  }
}

void test() {
  throw Exception("123");
}

// Exception: 123
// #0      test (package:flutter_demo/test.dart:11:3)
// #1      main (package:flutter_demo/test.dart:3:5)
// #2      _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:283:19)
// #3      _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)

可以使用on来指定异常。

void main() {
  try {
    test('1.1');
  } on int catch (e) {
    // throw的是int类型
    print('int error');
  } on String catch (e) {
    // throw的是String类型
    print('String error');
  } catch (e) {
    // throw的其他类型
    print("other type error");
  }
}

void test(i) {
  if (i is int) {
    throw 1;
  } else if (i is String) {
    throw "123";
  }
  throw true;
}

rethrow

可以使用rethrow将捕获的异常再次抛出。

void main() {
  try {
    test();
  } catch (e) {
    print("other type error");
  }
}

void test() {
  try {
    dynamic a = true;
    print(a++);
  } catch(e) {
    print('123');
    rethrow;
  }
}

// test中使用rethrow输出:
// 123
// other type error

// test中不使用rethrow输出:
// 123

finally

与Java类似,无论如何,都会执行到finally代码块。


类 class

定义,属性声明

与java类似

class Test {
  int a;
  String? b;
  bool c = true;

  // 使用下划线来声明一个私有属性
  int _d = 1;
}

使用下划线_来声明变量,表示这是一个私有变量。

构造函数

构造函数语法糖

class Test {
  int a;
  int b = 1;

  // 构造函数,语法糖,未赋值且非空字段必须添加到里面
  Test(this.a);
  // Test(this.a, this.b); // 错误,注意:构造函数不能重载,只能实现一个
}

命名构造函数

class Test {
  int a;

  // 命名构造函数
  Test.a(this.a);
}

参数初始化列表

class Test {
  int a;
  String? b;
  bool c = true;

  // 参数初始化列表
  Test.abc(this.a)
      : b = "str",
        c = false;

  Test.fromMap(Map map)
      : a = map['a'],
        b = map['b'],
        c = map['c'];
}

重定向构造函数

class Test {
  int a;

  Test(this.a);

  // 重定向构造函数,只能使用命名构造函数重定向到默认构造函数
  Test.no() : this(0);
}

私有构造方法

class Test {
  int a;

  // 私有构造方法,在命名构造方法命名前添加下划线_
  Test._p(this.a);
}

常量构造函数

// 常量构造函数
// 在类的构造函数前加上 const 关键字并确保所有实例变量均为 final 来实现常量构造函数
class ConstTest {
  final int x;
  final int y;

  const ConstTest(this.x, this.y);
}

void main() {
  var c1 = ConstTest(1, 1);
  var c2 = ConstTest(1, 1);
  var c3 = const ConstTest(1, 1);
  var c4 = const ConstTest(1, 1);
  var c5 = const ConstTest(1, 2);
  // 使用new关键字(可省略)创建的对象,始终为false
  print(c1 == c2);
  // 使用const关键字创建的对象,当参数一致时,为true
  print(c3 == c4);
  // 使用const关键字创建的对象,当参数不一致时,为false
  print(c3 == c5);
}

工厂构造函数

// 工厂构造函数
class FactoryTest {
  // 使用factory修饰的构造函数,就是工厂构造函数,必须返回一个实例对象
  factory FactoryTest.get() {
    return FactoryTest();
  }

  FactoryTest();
}

使用工厂构造函数构造类的实例时并非总是会返回新的实例对象。例如,工厂构造函数可能会从缓存中返回一个实例,或者返回一个子类型的实例。

Getter & Setter

每一个属性都有一个隐式的Getter方法,非final属性的话,还有一个Setter方法。

void main() {
  var p = Point(1, 2);
  print('x: ${p.x}, y: ${p.y}'); //x: 2, y: 2
  p.x = 2;
  p.y = 3;
  print('x: ${p.x}, y: ${p.y}'); //x: 4, y: 3
}

class Point {
  int _x;

  // 每一个属性都有一个隐式的Getter方法,非final属性的话,还有一个Setter方法
  int y;

  Point(this._x, this.y);

  // 为私有属性_x提供自定义get方法 [returnType] get [functionName] {}
  int get x => _x * 2;

  // 为私有属性_x提供自定义set方法 set [functionName](param) {}
  set x(int x) => _x = x;
}

运算符重载

使用operator修饰符来修饰

void main() {
  var p1 = Point(1, 2);
  var p2 = Point(3, 4);
  var p3 = p1 + p2;
  print("p3.x: ${p3.x}, p3.y: ${p3.y}"); //p3.x: 4, p3.y: 6
}

class Point {
  int x;
  int y;

  Point(this.x, this.y);

  // 运算符重载,+运算符
  Point operator +(Point other) => Point(x + other.x, y + other.y);
}

抽象类,抽象方法

使用abstract关键字修饰的类,就是抽象类;
在抽象类中,没有方法体的方法就是抽象方法。

void main() {
  var animal = Dog();
  print(animal.name()); //狗
  print(animal.species()); //动物
}

// 定义一个抽象类
abstract class Animal {
  // 定义一个抽象方法,不写方法体即可,子类必须重写,不像java,不需要也不可添加abstract关键字
  String name();

  String species() => "动物";
}

class Dog extends Animal {
  @override
  String name() => "狗";
}

隐式接口

与Java不同,Dart中没有interface关键字,Dart中每个类都隐式的定义了一个包含所有实例成员的接口,并且这个类实现了这个接口,所以你可以implement某个class,重写其内部所有属性及方法即可。

class Person {
  var name = "姓名";

  String greet() => "你好啊!";
}

class P implements Person {
  @override
  String name = "小P";

  @override
  String greet() => "我是小P,你好啊!";
}

接口与继承的区别:

  1. 单继承,多实现;
  2. 继承可以有选择的重写父类方法并且可以使用super,实现强制重新定义接口所有成员;

枚举类 enum

void main() {
  var greenIndex = colorIndex(Color.green);
  print(greenIndex); //1
}

enum Color { red, green, blue }

int colorIndex(Color color) {
  switch (color) {
    case Color.red:
      return Color.red.index;
    case Color.green:
      return Color.green.index;
    case Color.blue:
      return Color.blue.index;
  }
}

枚举类有两个限制:

  • 枚举不能成为子类,也不可以Mixin,也不可以实现一个枚举;
  • 不能显示得实例化一个枚举类。

Mixin

Mixin是一种在多重继承中复用某各类中代码的方法模式。
使用with关键字并在其后跟上Mixin类的名字来使用Mixin模式。
一个没有构造函数的类,就是Mixin类。

void main() {
  var c = C();
  print(c.a()); //B a
  print(c.b()); //B b
}

class A {
  String a() => "A a";
}

class B {
  String a() => "B a";

  String b() => "B b";
}

class C with A, B {}

以上述代码为例:若类A,B,C中均有重名方法,那C类的对象在执行重名方法时的逻辑为:

  1. 优先执行C类中的方法;
  2. 如果C类中没有该方法,会从with后面,从后往前寻找拥有该方法的类,并执行其类中对应的方法;

若你想让一个类只能被用做Mixin类使用,无法像其他正常类使用,将class改为mixin关键字即可。
使用mixin关键字声明的类无法被实例化。

mixin D {
  String d() => "D d";
}

你也可以使用on关键字来指定哪些类可以使用该Mixin类。

class E {}

// 确保mixin F只能被E的子类mixin
mixin F on E {}

// 正确
class G extends E with F {}

// 错误,因为想要使用mixin F,H必须继承自E才可以
// class H with F {}

类变量和方法(静态变量和静态方法) static

使用关键字static修饰的变量或方法。

void main() {
  print(StaticTest.str);
  print(StaticTest.greet());
}

class StaticTest {
  int i = 0;
  static String str = "你好啊";

  static String greet() => "Hello!";
}

静态变量只有在首次使用时初始化。
对于一些通用或常用的静态方法,应该将其定义成顶级函数而非静态方法。


生成器 Generators

当你需要延迟地生成一连串的值时,可以考虑使用 生成器函数。Dart 内置支持两种形式的生成器方法:
同步生成器:返回一个Iterable对象。
异步生成器:返回一个Stream对象。

在函数后添加sync*关键字,并将返回值设置为Iterable来实现一个同步生成器函数,并使用yield来传递值。
在函数后添加async*关键字,并将返回值设置为Stream来实现一个异步生成器函数,并使用yield来传递值。
如果生成器是递归调用的,可以使用yield*来提升性能

// 在函数后添加 sync* 关键字,并将返回值设置为 Iterable 来实现一个 同步 生成器函数
Iterable<int> naturalsTo(int n) sync* {
  int k = 0;
  while (k < n) {
    print(k);
    yield k++;
  }
}

// 在函数后添加 async* 关键字,并将返回值设置为 Stream 来实现一个 异步 生成器函数
Stream<int> asyncNaturalsTo(int n) async* {
  int k = 0;
  while (k < n) {
    print(k);
    yield k++;
  }
}

// 如果生成器是递归调用的,可以使用 yield* 来提升性能
Iterable<int> naturalsDownFrom(int n) sync* {
  if (n > 0) {
    yield n;
    yield* naturalsDownFrom(n - 1);
  }
}

可调用类Callable classes

通过实现类的call方法,允许使用类似函数调用的方式来使用该类的实例

void main() {
  var test = Test();
  print(test.call(3)); //6
  print(test(2)); //4
}

class Test {
  String call(int a) => "${a * 2}";
}

类型定义 typedef

Dart 2.13之前,typedef只能用于函数类型;
typedef f = int Function(int a, int b);

Dart 2.13之后,typedef可以用作类型别名;
typedef IntList = List<int>;
IntList il = [1, 2, 3];


元数据 @

使用元数据可以为代码增加一些额外的信息。
元数据注解以 @开头,其后紧跟一个编译时常量(比如 deprecated)或者调用一个常量构造函数

class Todo {
  final String who;
  final String what;

  const Todo(this.who, this.what);
}

@Todo("seth", "playing football")
void doSomething() {
  print("do something");
}

元数据可以在 library、class、typedef、type parameter、constructor、factory、function、field、parameter、variable 声明之前使用,也可以在 import 或 export 之前使用。
可使用反射在运行时获取元数据信息。

extension 扩展

void main() {
  '42'.parseInt();
}

extension NumberParsing on String {
  int parseInt() => int.parse(this);
}
上一篇下一篇

猜你喜欢

热点阅读