美文网首页
Dart-语法基础

Dart-语法基础

作者: AilurusFulgens | 来源:发表于2021-03-13 23:03 被阅读0次

    变量

    定义

    // 使用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

    • 如果你确定一个变量在使用时是非空的,那就可以添加late关键字,让编译器在该变量定义时不校验是否可空。
    • 当使用late关键字修饰变量的同时对该变量进行赋值,则只有当该变量第一次使用时,才会进行真正的赋值。

    内置类型

    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));
    }
    

    流程控制语句

    • ifelse
    • for 循环
    • whiledo-while
    • breakcontinue
    • switchcase
      以上这些均与Java类似
    • assert:断言,若表达式为true,执行接下来的代码,若为false,抛出异常。仅在调试模式生效,生产环境会被忽略。
    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);
    }
    

    相关文章

      网友评论

          本文标题:Dart-语法基础

          本文链接:https://www.haomeiwen.com/subject/uittcltx.html