• 【Flutter系列】第一期——初识Dart语言


    一、搭建Dart开发环境与工具配置

    1、安装Dart的 jdk 【此处以Windows系统为例】

    点击跳转Dart官网jdk下载地址

    请添加图片描述

    选择 稳定版本 或 最新版本 >> 安装 >> 查看是否jdk安装成功 dark --version 【在DOS中运行该指令】

    请添加图片描述
    2、安装 VS Code,在插件处添加CodeRunner、Dart 扩展

    二、变量与常量

    1、入口方法

    • 方法入口 main() 或 void main() 【区别在于是否具有返回值】
    • 注释:三斜杠或双斜杠
    • 变量定义:前置使用 var 关键字【自动识别类型】、也可以通过类型关键词指定数据类型
    void main(){
      var str = 'Hello Dart';
      var myNum = 1234;
      print(str);
      print(myNum);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    请添加图片描述

    2、命名规则

    • 变量名称必须由 数字、字母、下划线和美元符($) 组成。
    • 注意:标识符开头不能是数字
    • 标识符不能是保留字和关键字。
    • 弯量的名字是区分大小写
    • 标识符(变量名称)一定要见名思意:变量名称建议用名词,方法名称建议用动词

    3、如何定义一个常量?

    (1)可以使用 const 和 final 关键字定义常量

    (2)两者间的区别:

    • const开始就需要赋值final 可以开始不赋值,只能赋值一次

    • final 还可以接收一个方法的返回值作为常量使用,const 不可以

    请添加图片描述

    蓝色波浪线代表未使用,红色波浪线代表系统错误

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0s5KStNX-1666167817408)(attachment:8bb2963e8bbe9238a61c33d93b3c4103)]

    三、dart 中的数据类型(常用):

    1、字符串类型(String类型)

    • 可以 var 关键字自动识别或直接声明String类型 ,利用单引号、双引号、三引号都可以(三单、三双)
    • 三个单引号或三个双引号的作用就是可以根据 字符串内容位置原样输出 【在代码行里面换行了,实际输出就会换行】
    void main(List<String> args) {
      //定义字符串
      var str1 = 'xiaoming';
      var str2 = "xiaohong";
      var str3 = """xiaozhang
                    xiaozhang""";
      var str4 = '''
                  xiaoli
                  xiaoli
                 ''';
      print(str1 + "\t" + str2 + "\t" + str3 + "\t" + str4);
      print("*******************************");
      //利用String类创建字符串
      String s1 = '单引号';
      String s2 = "双引号";
      String s3 = """
                  三个双引号
                  """;
      String s4 = '''
                  三个单引号
                  ''';
    
      print(s1 + "\t" + s2 + "\t" + s3 + "\t" + s4);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    请添加图片描述

    • 字符串拼接:
      • 可以在一个字符串中使用 "$str1 $str2"的形式拼接
      • 也可以使用 + 完成字符串拼接
    void main(List<String> args) {
      //定义两个字符串
      String s1 = "Hello";
      String s2 = "Dart";
      //拼接这两个字符串并输出
      print("$s1 $s2");
      print(s1 + " " + s2);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    请添加图片描述

    2、数值类型(Number类型)

    • 数值类型分为整型 int 和 浮点型 double

    请添加图片描述

    通过黄色框选部分我们可以发现,可以 将int类型的数据赋值给double,但是不能将double类型的数据赋值给int【低到高可以,高到低不可以】

    • 数值类型提供五类运算符:+、-、*、/、% 【加、减、乘、除、取余】
    void main(){
      int a = 1;
      int b = 2;
      print(a + b);
      print(a - b);
      print(a * b);
      print(a / b);
      print(a % b);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    请添加图片描述
    3、布尔类型(bool类型)

    • 使用 bool 关键字来声明,取值只能为 truefalse
    • 布尔类型的数据或表达式通常用于条件判断语句
    void main() {
      int a = 1;
      int b = 2;
      bool flag = true;
      //条件判断语句
      if (flag && (a == b)) { //支持 & 和 &&
        print("条件成立");
      } else {
        print("目标条件不成立");
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    4、集合类型(List、Set类型)

    • 在同一个集合/数组中可以保存不同类型的数据【可以存在子集合】
    • 元素的存储是有序的,可以根据索引取出对应元素
    • 通过 length 方法可以获取集合的大小
    void main() {
      //定义一个集合
      var list = [
        123,
        11.1,
        "this is a str",
        true,
        ['子集合', 12, false]
      ];
      //打印
      print(list);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 也可以指定集合类型:var list = <指定类型>[...]
    void main(List<String> args) {
      //定义一个集合
      var list = <String>["刘备", "关羽", "张飞"];
      var list2 = <int>[1, 2, 3, 4];
      print(list);
      print(list2);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    请添加图片描述

    • 可以通过 add 方法向集合中添加元素,如果已经指明了集合类型,就只能添加同类型的数据list.add(要添加的元素)

    • flutter的2.x版本中,还可以使用 var list = new List()创建一个集合【3.x版本已经废弃】

    • 可以通过var list = List.filled(length, element)创建固定长度的List集合【length是长度、element是集合内容】

    import 'dart:convert';
    
    void main() {
      //创建一个静态集合
      var list = List.filled(2, "");
      print(list);
      //可以修改集合内容
      list[0] = "newElement"; //此处需要注意,只能修改为与原来类型一种的元素
      list[1] = "1111";
      print(list);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    (1)创建这个静态集合也可以指定数据类型var list = List<指定类型>.filled(length, element)

    (2)因为长度固定,所以不能添加元素,也不能直接修改集合的长度

    (3)但是通过[]创建的集合可以通过list.length修改元素的长度的

    void main(List<String> args) {
      //创建集合
      var list = [1, 1.0, "1", true];
      print(list.length);
      //修改长度
      list.length = 2;
      print(list);
      // list.length = 4;
      // print(list);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    请添加图片描述

    注释掉的代码会出现报错,通过测试发现这个修改长度相当于直接删除了元素,如果想恢复长度是不允许的。【列举的是缩小长度的情况】

    4、字典类型(Map类型)

    • 是一种键值对类型的数据 list[key] = value
    • 可以通过list[key]获得对应的value【映射取值
    • 存储的数据也是可以为任意类型
    • 在定义的时候key需要用引号括起来【对于单引号和双引号都没啥影响】
    void main(List<String> args) {
      //定义一个字典
      var person = {
        "name": "小赵",
        "age": 20,
        "work": ['学生', '打工人']
      };
      //直接输出字典
      print(person);
      //根据key获取value
      print(person['work']);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    请添加图片描述

    • 可以通过var map = new Map()创建一个Maps的对象,后续再添加数据
    void main(List<String> args) {
      //定义一个字典
      var person = new Map();
      person['name'] = "小杨";
      person['salery'] = 15000;
      print(person);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    请添加图片描述

    • 通过 is 关键字可以用于判断数据类型: 变量 或 常量 is 指定数据类型,返回结果为布尔类型

    四、运算符与类型转换

    1、算数运算符

    运算符含义
    +
    -
    *
    /
    %取余
    ~/取整

    2、关系运算符

    符号含义
    ==相等
    !=不相等
    >大于
    <小于
    >=大于等于
    <=小于等于

    3、逻辑运算符

    符号含义
    !取反
    &&短路与(同真为真)
    II短路或(同假为假)
    &
    |

    4、赋值运算符

    符号含义
    a = b将b的值赋值给a
    a ??= b如果a为空,那么等于b
    a += ba = a + b
    a -= ba = a - b
    a *= ba = a * b
    a /= ba = a / b
    a %= ba = a % b
    a ~/= ba = a ~/ b

    4、条件表达式

    (1)条件判断语句 if-else、switch-case 语句

    void main() {
      bool flag = false;
      if (flag) {
        //因为这段恒为假,所以会有蓝色波浪线标记
        print("true");
      } else {
        print("false");
      }
    
      //switch 分支语句
      int num = 2;
      switch (num) {
        case 1:
          print(1);
          break;
        case 2:
          print(2);
          break;
        default:
          print("others number");
          break;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    请添加图片描述

    (2)三目运算符【可以替换一些简单的if-else语句】

    if (num % 2 == 0) {
        num = 10;
      } else {
        num = 9;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    可以替换为: num = num % 2 == 0 ? 10 : 9;

    (3)??运算符的使用【如果数据为空,那么用 ?? 后面的内容替换】

    void main() {
      var a;
      var b = a ?? 10;
      print(b);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 可以输出10,如果a不为空,那么控制台会产生报错
    • 使用Empty方法可以判断字符串是否为空:str.Empty;
    • 可以使用 try-catch 语句做异常处理

    5、Dart类型转换

    (1)String类 -> Number类可以使用 具体的Number类型.parse(String类的对象)

    void main() {
      //创建字符串
      String numStr = "123";
      String numStr2 = "12.3";
      var myNum = int.parse(numStr);
      var myNum2 = double.parse(numStr2);
      //利用 is 关键字可以判断数据类型
      print(myNum is int);
      print(myNum2 is double);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    (2)Number类 -> String类 可以使用 toString() 方法

    void main() {
      int num = 123;
      String s = num.toString();
      print(s is String);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (3)补充说明:++、– 自增自减

    • 如果++、-- 写在前面,代表先运算再赋值
    • 如果++、-- 写在后面,代表先赋值再赋值

    6、循环语句

    • for 循环
    for(声明变量; 条件判断; 变量改变){
      循环体;
    }
    
    • 1
    • 2
    • 3
    • while 循环
    while(条件表达式){
      循环体;
    }
    
    • 1
    • 2
    • 3
    • do-while 循环
    do{
      循环体;
    }while(条件表达式);
    
    • 1
    • 2
    • 3
    • do-whilewhile 的区别就在于是先执行循环体还是先判断

    • break关键字可以跳出当前循环,continue关键字可以跳过当前循环,回到循环起始位置继续循环

    五、Dart集合

    1、List 集合

    • 通过 [] 可以创建大小可变的集合,主要有两种创建方式:【以下为之前介绍过的 List 的方法】
    //通过关键字自动识别数据类型
    var list1 = ['This', 'is', 'apple'];
    //显示声明数据类型
    List list2 = ['This', 'is', 'banana'];
    //通过add方法添加元素
    list1.add('newElement');
    //通过length方法可以查看集合大小
    print(list1.length);
    //创建大小固定的集合
    List list3 = List.filled(2, "1", "2");
    //通过数组索引可以修改指定元素
    list3[0] = "修改后的元素";
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • List 的常用属性:length、isEmpty、isNotEmpty、reversed【长度、空、非空、反转数组】
    void main() {
      //创建一个集合
      List l = ["some", "fruit"];
      //length、isEmpty、isNotEmpty、reversed
      print(l.length);
      print(l.isEmpty);
      print(l.isNotEmpty);
      print(l.reversed);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    请添加图片描述

    • List的常用方法
    方法名作用
    addAll拼接数组
    indexOf查找目标值的索引
    remove(element)删除指定元素
    removeAt(int index)删除指定位置的元素
    fillRange(int start, int end, target)将指定片段替换为目标片段
    insert(int index, element)指定位置插入数据
    insertAll(int index, element)指定位置插入多个元素
    join(‘分割方式’)将集合转化为字符串
    split('分割方式)将字符串转化为集合类型

    2、Set 集合

    • 不能存储相同的元素 【可以用于元素的去重】
    • 无序的【不能根据索引去取数据】
    void main() {
      var setNum = new Set();
      setNum.addAll([1, 4, 2, 1, 3]);
      print(setNum);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 可以通过 toList() 方法将Set类型的集合转化为 List 类型的集合
    void main() {
      //创建一个set的集合
      var set = new Set();
      set.addAll([1, 2, 3, 4]);
      //利用toList()方法将集合转换成List类型
      print(set.toList() is List);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3、Map 集合

    • 是一种无序的键值对映射【key -value
    • 常用属性:【对于属性的使用,直接通过对象名.属性即可】
    属性作用
    keys获取所有的key值
    values获取所有的value值
    isEmpty判断是否为空
    isNotEmpty判断是否不为空
    void main() {
      //创建一个map的集合
      var test = new Map();
      //添加数据
      test['name'] = "小明";
      test['age'] = 18;
      //利用keys和values获取对应的值
      print(test.keys);
      print(test.values);
      //判断是否为空
      print(test.isEmpty);
      print(test.isNotEmpty);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述

    • 常用方法:
    方法名作用
    remove删除指定key对应的键值对
    addAll({…})可以添加一些映射
    containsValue可以查看是否包含指定的 key,返回 true / false
    void main() {
      //创建一个map的集合
      var test = new Map();
      //添加数据
      test['name'] = "YangJilong";
      test['age'] = 22;
      test['sex'] = '女';
      print(test);
      //remove删除指定键值对
      test.remove('sex');
      print(test);
      //一次添加多个键值对
      test.addAll({
        'work': ["吃饭", "睡觉", "打豆豆"],
        'play': ["王者荣耀", "三国杀"]
      });
      print(test);
      //是否含有指定的key
      print(test.containsKey('work'));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在这里插入图片描述
    4、集合的遍历:【其中的方法对于三种集合都适用,这里以List类型的集合为例】

    (1)通过普通 for 循环遍历

    void main() {
      //创建一个List类型的集合
      var test = ["onesheep", "twosheep", "threesheep"];
      //遍历输出test的元素值
      for (int i = 0; i < test.length; i++) {
        print(test[i]);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述
    (2)通过超级 for 循环遍历

    for (var item in test) {
        print(item);
    }
    
    • 1
    • 2
    • 3

    (3)通过forEach遍历

    test.forEach((value) {
        print(value);
      });
    
    • 1
    • 2
    • 3

    可以替换为test.forEash((value) => print(value));
    (4)通过where筛序出需要的 value

    test.where((value){
    	return value;
    })
    
    • 1
    • 2
    • 3

    因为此处我要输出这三个字符串,如果为Number类型的数据,可以根据表达式筛选数据

    (5)通过any来判断集合中是否存在满足条件的数据

    • 只要集合里有满足条件的就返回 true,否则返回 false
    test.any((value){
    	return value == 'onesheep';
    })
    
    • 1
    • 2
    • 3

    (6)通过 every 来判断集合中的数据是否全部都满足指定条件

    test.any((value){
    	return value == 'onesheep';
    })
    
    • 1
    • 2
    • 3

    六、方法

    1、方法的定义

    • 首先,方法分为系统内置方法和自定义方法
    • 自定义方法的格式如下:
    返回值 方法名(参数列表){
      方法体;
    }
    
    • 1
    • 2
    • 3

    (1)对于返回值可以为具体的数据类型,也可以为void,同时还可以省略,由系统自动识别

    (2)方法如果定义在入口方法上面,代表全局方法【要注意方法的作用域】

    (3)方法可以进行嵌套,只不过方法的作用域不同

    (4)案例分析:

    • 定义一个方法,可以接收两个参数,计算从n1到n2的所有元素和
    //指定片段和
    int sumNum(int num1, int num2) {
      var sum = 0;
      for (int i = num1; i <= num2; i++) {
        sum += i;
      }
      return sum;
    }
    
    void main() {
      var res = sumNum(1, 100);
      print(res);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    请添加图片描述

    • 定义一个方法,可以打印用户信息(包含姓名和年龄)
    String printUserInfo(String username, int age) {
      return "姓名: $username, 年龄: $age";
    }
    
    void main() {
      print(printUserInfo("YangJilong", 22));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    请添加图片描述
    2、dart 中方法的参数分为两种:必须参数和可选参数

    • 可选参数又分为命名可选参数和位置可选参数

    (1)位置可选参数
    使用中括号括起来,代表位置可选参数【是否传递该参数都可以,但是如果传递了实参要注意位置要对应上】

    String printUserInfo(String username, [int age]) {
      if (age == null) {
        return "姓名: $username, 年龄: 保密";
      }
    
      return "姓名: $username, 年龄: $age";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    存在报错:The parameter ‘age’ can’t have a value of ‘null’ because of its type ‘int’, but the implicit default value is ‘null’.

    解决办法

    • 方案一:在位置参数部分进行默认初始化,保证数据不为空
    • 方案二:使用?,代表如果数据为空的话不进行任何处理
    • 方案三:不指明参数类型
    • 下面给出的代码既有位置可选参数的案例,也有命名可选参数的案例
    //使用命名可选参数的方法,定义的时候通过{}括起来
    void printInfo(String name, {int age = 0, String sex = ''}) {
      print("姓名:$name, 年龄:$age, 性别:$sex");
    }
    
    //使用位置参数的可选方法,定义的时候通过[]括起来
    void printInfo2(String name, [int? age, String? sex]) {
      print("姓名:$name, 年龄:$age, 性别:$sex");
    }
    
    void main() {
      printInfo("wang", sex: "男", age: 18);
      printInfo2("yang", 15, '女');
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述

    (2)命名可选参数

    • 命名参数:利用大括号括起来的参数列表,在调用传递实参的时候,需要使用变量名:值

    请添加图片描述

    3、方法嵌套与匿名函数:【后续会具体说明】

    (1)案例一:直接定义两个方法,其中一个方法以另一个方法为参数【方法嵌套】

    fun() {
      print("内层方法");
    }
    
    fun2(f) {
      f();
    }
    
    void main() {
      fun2(fun);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述

    • 递归属于方法嵌套的一个常用案例,方法调用自身

    (2)案例二:没有声明方法名,而是通过一个变量来接收【属于匿名函数】

    请添加图片描述

    • 虽然说是变量接收方法,但是实际调用该匿名方法的时候,也是通过变量名加小括号使用的

    内容概述:箭头函数、匿名函数、闭包

    4、箭头函数

    • 什么是箭头函数?

    并没有明确定义,只是由语句的形式来命名的,使用了一个箭头来进行数据的处理

    • 如何使用箭头函数?
    void main() {
      //利用forEach语句遍历输出结合
      var list = ['apple', 'pear', 'banana'];
      list.forEach((element) {
        print(element);
      });
      print("*" * 20);
      list.forEach((element) => print(element));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    请添加图片描述

    通过输出结果我们可以看出 >> 使用箭头函数可以简化代码量

    • 使用箭头函数的时候我们应该注意什么?

    箭头函数只能处理一条语句【就是原来方法体里只有一条语句】

    • 案例分析:dart中提供一种map循环,一般用于修改集合的数据【先用不同形式写,再用箭头函数优化】
    void main() {
      //利用map语句将集合中大于2的元素乘2
      var list = [1, 2, 3, 4, 5];
      print("输出数组的值: $list");
      var new_list = list.map((e) {
        if (e > 2) {
          return e * 2;
        } else {
          return e;
        }
      });
      print("输出更新后数组的值: $new_list");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    请添加图片描述

    我们需要注意:通过map不能修改原集合中的元素

    我们也可以用箭头函数来实现上述案例,使用了三目运算符代替简单的条件分支语句

    void main() {
      //利用map语句将集合中大于2的元素乘2
      var list = [1, 2, 3, 4, 5];
      print("输出数组的值: $list");
      //使用了toList将结果转化为List类型的集合
      var new_list = list.map((e) => (e > 2 ? e * 2 : e)).toList();
      print("输出更新后数组的值: $new_list");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    请添加图片描述

    5、函数的相互调用

    • 为什么要在方法中调用另一个方法?

    因为我们为了让一个函数去处理一个单独的事情,方便其他需要该功能的部分进行调用

    • 我们设计两个方法:判断是否为偶数、打印输出1-n中的所有偶数
    // 判断是否为偶数
    bool isEvnNumber(int n) {
      return n % 2 == 0;
    }
    
    // 打印1-n之间的所有偶数
    void printNumber(int n) {
      for (int i = 1; i <= n; i++) {
        if (isEvnNumber(i)) {
          print(i);
        }
      }
    }
    
    void main() {
      printNumber(10);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    请添加图片描述

    6、匿名方法

    • 什么是匿名方法?

    没有名字的方法我们称之为匿名方法,通常使用一个变量来接收

    • 如何使用匿名方法?【我们通过一个案例来分析】
    void main() {
      var noName = () {
        print("我们一个匿名方法!");
      };
      noName();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    结果: 正常输出,也可以传递参数列表

    7、自执行方法【不需要调用,自己执行的方法】

    • 代码格式如下:
    void main() {
      (() {
        print("这是一个自执行方法");
      })();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    请添加图片描述

    • 自执行方法也可以传递参数,上面的括号传递形参列表,下面的括号传递实参列表

    • 代码格式如下:

    void main(){
      ((形参列表){
        方法体;
      })(实参列表);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    8、方法的递归:【方法内部调用自己】

    • 重点在于声明什么时候结束递归
    • 代码格式:
    //此处以n的阶乘为例
    var sum = 1;
    fun(int n) {
      if (n == 1) {
        return;
      }
      sum *= n;
      //递归
      fun(n - 1);
    }
    
    void main() {
      //计算5的阶乘
      fun(5);
      print(sum);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    请添加图片描述

    9、闭包

    请添加图片描述

    • 为什么使用闭包?

      • 因为全局变量会常驻内存 >> 污染全局,局部变量不常驻内存并且会被垃圾回收机制回收 >> 不会污染全局
      • 为了实现常驻内存,而且不污染全局 >> 我们提出了闭包的概念
    • 如何实现闭包呢?【让局部变量常驻内存】

    函数嵌套函数,内部函数会调用外部函数的变量或参数,return 里面的函数,就形成了闭包

    fun() {
      var a = 123;
      return () {
        print(a++);
      };
    }
    
    void main() {
      var b = fun();
      b();
      b();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    请添加图片描述

    (1)常驻内存是如何体现的呢?

    修改数据产生的影响是全局的【案例中a的值调用一次就修改一次】

    (2)用变量去接收一个函数,属于匿名函数的形式,但是仍能通过 fun() 去调用该方法


    七、类与对象

    1、背景概述

    • Dart 是一门使用单继承和类的面向对象语言,所有对象都是类的实例,所有的类都是Object类的子类
    • 面向对象编程(OOP)的三个基本特征:封装、继承、多态

    2、通过class关键字可以自定义类,类名一般采用大驼峰的命名规则

    class Person {
      String name = '僵小鱼';
      int age = 8;
      void getInfo() {
        print("姓名:${this.name}, 年龄: ${this.age}");
      }
    }
    
    void main() {
      //创建Person类的对象
      var person = new Person();
      //通过对象名.属性/方法调用
      print(person.name);
      print(person.age);
      person.getInfo();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    请添加图片描述

    3、类的构造方法【实例化一个类的时候自动触发的一个方法】

    与类名通过的方法,可以为无参的构造方法,也可以为传参的构造方法

    class Person {
      String name;
      int age;
      // 添加一个构造方法
      Person(this.name, this.age);
    }
    
    void main() {
      //创建Person类的对象
      var person = new Person("夏洛克", 20);
      //通过对象名.属性/方法调用
      print("${person.name}-----${person.age}");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    请添加图片描述

    4、命名构造函数,属于构造函数的一种

    class Person {
      String name;
      int age;
      Person.now(this.name, this.age) {
        print("姓名:$name, 年龄: $age");
      }
    }
    
    void main() {
      var p = new Person.now("sun", 20);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述

    • 可以将一个类封装成一个模块,在其他模块中可以使用 import 关键字调用该模块:import 文件位置

    5、私有成员

    • 通过变量名或方法前添加下划线,代表是私有成员,并且要把这个类抽离成一个文件

    • 此时外部其他模块中就无法直接调用该类的私有成员

    • 可以在该类中设置共有方法,为外部提供访问私有成员的接口

    (1)我们创建一个文件,保存Person类

    class Person {
      String _name;
      int _age;
      Person(this._name, this._age);
      void printInfo() {
        print("姓名: $_name, 年龄: $_age");
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    (2)我们在其他文件中导入并实例化这个Person类
    在这里插入图片描述
    通过图片我们可以看出在当前文件中,无法通过Person类的实例调用Person类的私有属性

    6、getter 和 setter 方法

    (1)getter 用于声明某些方法,在外部可以通过类的实例访问属性的方式访问该方法

    class Rect {
      //声明类型为数值型
      num height;
      num width;
      Rect(this.height, this.width);
      //getter方法
      get area {
        return height * width;
      }
    }
    
    void main() {
      Rect r = new Rect(3, 4);
      print("面积:${r.area}");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    请添加图片描述

    (2)setter 用于声明某些方法,接收外部传递的一个值,通过该方法传递到类内部的属性【传参使用等号赋值的方式】

    class Rect {
      //声明类型为数值型
      num height;
      num width;
      Rect(this.height, this.width);
      get area {
        return height * width;
      }
      //修改类的属性值
      set areaHeight(value) {
        this.height = value;
      }
    }
    
    void main() {
      Rect r = new Rect(3, 4);
      print("面积:${r.area}");
      r.areaHeight = 5;
      print("新面积:${r.area}");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    请添加图片描述

    • 总结:getter 常用与获取某些私有属性setter 常用于某些私有属性值的修改

    7、初始化列表

    • Dart提供一种形式的构造函数,可以在构造函数体运行之前初始化实例变量【我觉得适合无参的构造方法】
    Rect()
        : height = 2,
          width = 3 {}
    
    • 1
    • 2
    • 3

    8、静态成员

    • 我们可以通过static关键字定义静态属性或静态方法【可以通过类名.成员名调用】

    • 静态方法不能访问非静态成员,非静态方法既可以访问非静态成员、也可以访问静态成员

    • 在dart语言中,不能通过类的对象调用静态成员

    class Person {
      static String name = 'KeBi';
      int age = 20;
      static void show() {
        print(name);
      }
    }
    
    void main() {
      print(Person.name);
      Person.show();
      var p = new Person();
      print(p.age);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    请添加图片描述

    9、对象操作符

    • dart 中提供四种对象操作符
    符号含义
    ?条件运算符
    as类型转换
    is类型判断
    级联操作(连缀)
    • ?条件运算符一般用于处理空指针异常问题,如果对象为空,那么就不执行任何操作
      • Person类:
      class Person {
        String name;
        int age;
        Person(this.name, this.age);
        void printInfo() {
          print("姓名:${this.name}, 年龄:${this.age}");
        }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

    (1)对象为空的时候调用类方法

    void main() {
      Person p;
      p.printInfo();
    }
    
    • 1
    • 2
    • 3
    • 4

    请添加图片描述

    (2)使用?之后【正常应该什么都不会输出,但是我的还是报错】

    void main() {
      var p;
      p?.printInfo();
    }
    
    • 1
    • 2
    • 3
    • 4

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V6iTBhqG-1666245826243)(attachment:10aea26d8f6c11520b557aa4cb871a7a)]

    • is用于判断指定对象是否为指定的类型【如果对象类型属于指定的类型的子类,那么也会返回true】
    Person p = new Person(" 九州", 100);
    print(p is Object);
    
    • 1
    • 2
    • as用于强制类型转换
    var str = '';
    str = new Person("沈璐", 21);
    //如果再老版本里面,直接调用Person类中的方法会报错
    (str as Person).printInfo();
    
    • 1
    • 2
    • 3
    • 4
    • ..级联操作用于一条语句执行多个操作,彼此之间使用..进行分割
    void main(){
      Person p = new Person("沈念", 45);
      p
       ..name = "花千骨"
       ..age = 31
       ..printInfo();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    请添加图片描述


    10、类的继承

    • 子类使用 extends 关键词来继承父类
    • 子类会继承父类里面可见的属性和方法,但是不能继承父类的构造方法【可见代表公有】
    • 子类能重写父类的方法,也可以覆盖父类的属性
    • 使用super可以在子类中调用父类的公有成员

    Person类:【在案例(1)、(2)、(4)中不会再定义Person类】

    class Person {
      String name;
      int age;
      Person(this.name, this.age);
      void printInfo() {
        print("姓名:${this.name}, 年龄:${this.age}");
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    (1)使用super关键字调用父类的构造方法【有两种方式】

    //子类
    class Teacher extends Person {
      // Teacher(super.name, super.age); 
      Teacher(String name, int age) : super(name, age) {}
    }
    
    void main() {
      Teacher t1 = new Teacher("张老师", 31);
      t1.printInfo();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    (2)可以为子类添加新的属性和方法

    //子类
    class Teacher extends Person {
      //如果我不初始化,那么在Teacher构造方法处就会报错
      String sex = '';
      // Teacher(super.name, super.age);
      Teacher(String name, int age, String sex) : super(name, age) {
        this.sex = sex;
      }
      void run() {
        print("姓名:${this.name}, 年龄:${this.age},性别:${this.sex}");
      }
    }
    
    void main() {
      Teacher t1 = new Teacher("张老师", 31, '女');
      t1.run();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    请添加图片描述

    (3)如果想使用父类是匿名的构造函数,那么也可以通过 **super **直接调用

    class Person {
      String name;
      int age;
      //匿名构造函数
      Person.xxx(this.name, this.age);
      void printInfo() {
        print("姓名:${this.name}, 年龄:${this.age}");
      }
    }
    
    //子类
    class Teacher extends Person {
      String sex = '';
      //super.xxx() 调用了父类的匿名构造函数
      Teacher(String name, int age, String sex) : super.xxx(name, age) {
        this.sex = sex;
      }
      void run() {
        print("姓名:${this.name}, 年龄:${this.age},性别:${this.sex}");
      }
    }
    
    void main() {
      Teacher t1 = new Teacher("王老师", 21, '男');
      t1.run();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    请添加图片描述

    (4)可以在子类中重写父类方法【推荐在重写部分上方添加@Override

    class Teacher extends Person {
      String sex = '';
      Teacher(String name, int age, String sex) : super.xxx(name, age) {
        this.sex = sex;
      }
      //重写了父类的printInfo方法
      
      void printInfo() {
        print("姓名:${this.name}, 年龄:${this.age},性别:${this.sex}");
      }
    }
    
    void main() {
      Teacher t1 = new Teacher("大仓", 28, '男');
      //此时调用的是子类自己的方法,如果子类没有才会去上一层寻找,直至到Object类
      t1.printInfo();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    请添加图片描述


    11、抽象方法

    • 我们通常在抽象类中定义一些抽象方法,用于规范子类【抽象方法没有方法体】
    • 子类必须实现父类中的全部抽象方法
    • 使用 abstract 定义抽象类,抽象类中也可以有普通方法
    abstract class Animal {
      eat();
      love();
    }
    
    class Dog extends Animal {
      
      eat() {
        print("吃骨头");
      }
    
      
      love() {
        print("喜欢听音乐");
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    12、多态

    • 允许将子类类型的指针赋值给父类类型的指针,同一个函数调用会有不同的结果【子类实例赋值给父类引用

    • 多态就是父类定义一个方法不去实现,让继承他的子类去实现,每个子类有不同的表现

    • 案例分析:

    (1)如果将子类实例赋值给父类引用,属于向上转型,新对象只能调用父类里面有的部分【如果这部分被子类重写了,那么调用了就是子类中对应的成员】

    不能调用子类中的特有成员

    abstract class Animal {
      eat();
    }
    
    class Dog extends Animal {
      
      eat() {
        print("吃骨头");
      }
    
      run() {
        print("小狗在跑...");
      }
    }
    
    void main() {
      Animal a = new Dog();
      a.eat();
      //无法调用子类特有方法
      a.run();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    请添加图片描述

    去掉run后即可正常运行

    请添加图片描述

    13、接口

    • dart 接口没有使用 interface 关键词定义接口,但是使 implements 关键字来实现接口

    • 普通类和抽象类都可以作为接口被使用,推荐使用抽象类定义接口

    • 接口的作用也是用于规范和约束

    abstract class DB {
      add();
    }
    
    // mysql、mssql类去实现接口
    class Mysql implements DB {
      
      add() {
        print("mysql数据库添加数据");
      }
    }
    
    class Mssql implements DB {
      
      add() {
        print("mssql数据库添加数据");
      }
    }
    
    void main() {
      Mysql my = new Mysql();
      Mssql ms = new Mssql();
      my.add();
      ms.add();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 可以为DB接口、Mysql类和Mssql类创建单独的文件,通过import导入。
    • extends 抽象类 和 implements 的区别:
      • 如果要复用抽象类里面的方法,并且要用抽象方法约束自类的话我们就用 extends 继承抽象类
      • 如果只是把抽象类当做标准的话我们就用implements实现抽象类

    14、dart 支持多接口

    • 在使用implements实现多个接口时,彼此之间用逗号隔开

    • 要注意实现每个接口中的全部属性和方法【如果是抽象类定义的接口】

    abstract class A {
      fun1();
    }
    
    abstract class B {
      fun2();
    }
    
    class C implements A,B{
      
      fun1() {
        throw UnimplementedError();
      }
    
      
      fun2() {
    
        throw UnimplementedError();
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    15、mixins 类:类似实现了多继承的功能【混合类】

    请添加图片描述

    • 什么样的类能作为mixins类?

    (1)不能有构造函数

    (2)不能继承除Object类的其他类

    • 如何使用mixins类?【通过 with 关键字】
    // ignore_for_file: unnecessary_type_check
    
    class A {
      fun1() {
        print("A中的fun1");
      }
    }
    
    class B {
      fun2() {
        print("B中的fun2");
      }
    }
    
    class C with A, B {}
    
    void main() {
      var c = new C();
      c.fun1();
      c.fun2();
    
      print(c is A);
      print(c is B);
      print(c is C);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    (1)也可以使用抽象的mixins类,不过要记得实现所有抽象方法

    (2)利用 is 进行类型判断时,如果为该类型的超类,那么也会返回 true

    请添加图片描述

    七、库和泛型

    1、泛型

    • 什么是泛型?

    指在类定义时不会设置类中的属性或方法参数的具体类型,而是在类使用时(创建对象)再进行类型的定义。会在编译期检查类型是否错误。

    • 使用泛型有什么好处?

    可以减少代码冗余度,代码重用率高,还可以完成类型校验

    • 通过一个 T 来代表泛型
    T getData<T>(value){
      return value;
    }
    
    //通过以下代码调用[此处以字符串类型为例]
    getData<String>("abc");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    如果将返回值处的T去掉,可以控制只对传入参数校验,不对返回类型进行校验

    2、泛型类

    • 什么是泛型类?

    目的是为了内部参数的接收是泛型,就将这个类定义为泛型

    • 如何把一个普通类改成泛型类?
    class MyList<T> {
      List list = <T>[];
      void addElement(T value) {
        list.add(value);
      }
    
      List getList() {
        return list;
      }
    }
    
    void main() {
      MyList list = new MyList<String>();
      list.addElement("a apple");
      list.addElement("a banana");
      print(list.getList());
    
      MyList list1 = MyList<int>();
      list1.addElement(1);
      list1.addElement(2);
      list1.addElement(3);
      print(list1.getList());
    
      MyList list2 = MyList();
      list2.addElement(1);
      list2.addElement("love");
      list2.addElement(true);
      list2.addElement([2, 3, 4]);
      print(list2.getList());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    (1)创建了一个泛型类,一个泛型集合,集合中的元素也是泛型的 >> 这三个部分元素类型一致

    (2)创建了三个MyList的对象,第一个指定为String类型,第二个为int类型,第三个没有指定类型 >> 意味着第三个集合可以存储任意类型的数据

    (3)在声明运行类型时,可以不使用new关键字
    请添加图片描述

    3、泛型接口

    请添加图片描述

    • 就是使用了泛型的接口
    • 实现数据缓存功能:分为文件缓存和内存缓存两种【通过一个案例来显示泛型接口】
    abstract class Cache<T> {
      getByKey(String key);
      void setByKey(String key, T value);
    }
    
    class FileCache<T> implements Cache<T> {
      
      getByKey(String key) {
        print(key);
      }
    
      
      void setByKey(String key, T value) {
        print("我是文件缓存,把${key}${value}存储到了文件中");
      }
    }
    
    class MemoryCache<T> implements Cache<T> {
      
      getByKey(String key) {
        print(key);
      }
    
      
      void setByKey(String key, T value) {
        print("我是内存缓存,把${key}${value}存储到了内存中");
      }
    }
    
    void main() {
      var f = new FileCache<String>();
      f.setByKey("root", "root");
      f.getByKey("key已加密");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    请添加图片描述
    4、库

    • 每一个Dart文件都是一个库,使用时通过 import 关键字引入
    • dart 中的库分为:自定义库、系统内置库、第三方库

    (1)导入自定义库

    import 'lib/xxx.dart'; //库所在位置
    
    • 1

    (2)导入系统内置库【前提是有dart的jdk】

    import 'dart:io';
    import 'dart:math';
    
    • 1
    • 2

    (3)导入第三方库

    • 获取网页数据的一个案例:
      请添加图片描述请添加图片描述

    2、async 和 await

    • async 的作用是使方法变成异步方法,await是等待异步方法执行完成

    • 只有async方法才能使用await关键字调用方法

    • 如果调用别的async方法必须使用await关键字

    • 案例分析:定义一个异步方法,在主方法中去调用这个异步方法

    testAsync() async {
      return 'Hello async';
    }
    
    test() {
      return 'Hello test';
    }
    
    void main() async {
      print(test());
      var res = await testAsync();
      print(res);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    (1)对于普通方法,直接调用即可;但是调用异步方法需要配合await使用

    (2)但是如果想使用await关键字,那么当前方法只能为异步方法 >> 将main方法也定义成了异步方法

    (3)通过关键字async可以声明为异步方法

    (4)如果异步方法中只是打印一些信息,那么也可以直接通过方法名调用

    请添加图片描述

    3、使用第三方库的流程

    • 几个推荐的第三方库
    https://pub.dev/packages
    https://pub.flutter-io.cn/packages
    https://pub.dartlang.org/flutter
    
    • 1
    • 2
    • 3

    (1)将对应的依赖复制到自己项目的配置文件中【dependencies】[pubspec.yaml]

    dependencies:
      http: ^0.13.5
    
    • 1
    • 2

    (2)在dos中进入到当前项目目录,运行pub get指令开始安装依赖

    (3)找到Example文档,根据文档步骤开始使用第三方库

    import 'dart:convert' as convert;
    
    import 'package:http/http.dart' as http;
    
    void main(List<String> arguments) async {
      // This example uses the Google Books API to search for books about http.
      // https://developers.google.cn/books/docs/overview
      var url =
          Uri.https('www.googleapis.com', '/books/v1/volumes', {'q': '{http}'});
    
      // Await the http get response, then decode the json-formatted response.
      var response = await http.get(url);
      if (response.statusCode == 200) {
        var jsonResponse =
            convert.jsonDecode(response.body) as Map<String, dynamic>;
        var itemCount = jsonResponse['totalItems'];
        print('Number of books about http: $itemCount.');
      } else {
        print('Request failed with status: ${response.statusCode}.');
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 如果库中某些部分出现了重名,可以通过库名.来指定那部分的内容

    4、可以导入库中的部分功能

    • 通过 show 关键字指定引入的功能

    • 通过 hide 关键字隐藏不想引入的功能

    import 'Person' show set,get;
    import 'dart:math' hide max;
    
    • 1
    • 2

    5、延迟加载

    请添加图片描述

    八、新版特性

    1、空安全 【Flutter2.2.0 之后引入了空安全】

    • 对于指定数据类型的变量,不能赋值为空【以整型数据为例】

    在这里插入图片描述

    • 那么如何赋值空呢?

    通过在数据类型后添加 ?,就代表这个数据可以为空

    void main() {
      int? num = null;
      print(num);
    }
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

    也可以让返回指定数据类型的方法允许为空

    String? getData(var value){
      if(value == null){
        return null;
      }else{
        return "数据获取成功";
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2、类型断言

    • 如果变量不为空,那么继续执行语句;如果变量为空,那么会抛出异常 【空指针异常】

    • 一般直接用于变量后面

    void printLength(String? s) {
      print(s!.length);
    }
    
    void main() {
      printLength(null);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

    3、延迟初始化

    • 在dart2.1.3之后,如果没有构造器初始化数据,会产生报错

    在这里插入图片描述

    • 可以为属性添加关键字 late,这样代表延迟初始化【我们会通过其他方法完成初始化工作】
    class Person {
      late String name;
      late int age;
    
      setInit(String name, int age) {
        this.name = name;
        this.age = age;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    4、required关键词

    • 在老版本中 @required 的作用是注解

    • 现在为内置修饰符,用于根据需要标记命名参数(函数或类),使参数不为空

    • 如果在命名可选参数前面使用了required,代表为必须传入的参数

    printInfo(String name, {required int age}) {
      print("$name --- $age");
    }
    
    void main() {
      printInfo("wang", age: 20);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    5、常量优化

    • const 编译常量,final 运行时常量
    • final可以通过方法返回值初始化常量

    6、判断两个对象是否使用一个存储空间?

    • 使用 indentical 方法,可以判断两个对象是否为同一个存储空间的引用

    • const 关键字在多个地方创建相同的对象时,内存中只保留了一个对象

    void main() {
      var n1 = const Object();
      var n2 = const Object();
      var n3 = Object();
      var n4 = Object();
      print(identical(n1, n2));
      print(identical(n3, n4));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述

    7、常量构造函数

    • 使用 const 关键字修饰构造函数,该构造函数的属性都为 final 类型
    • 在实例化的时候也要通过 const 关键字来定义【可以让相同的实例共享存储空间】
    class Container {
      final int weight;
      final int height;
      const Container({required this.weight, required this.height});
    }
    
    void main() {
      Container c1 = const Container(weight: 100, height: 100);
      Container c2 = const Container(weight: 100, height: 100);
    
      print(identical(c1, c2));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    请添加图片描述

  • 相关阅读:
    ESP32基础应用之LVGL基础
    DS单链表--结点交换 C++
    电脑系统安装后桌面图标隔开很宽怎么调?
    如何创建专栏
    图片转文字在线怎么转换?在线转换方法分享
    MySql 实现递归with recursive
    【408篇】C语言笔记-第九章(数据结构概述)
    程序员团队如何做好项目管理?十年管理经验,真实案例分享
    uni-app之android离线打包
    BootStrap响应式项目实战之世界杯网页设计
  • 原文地址:https://blog.csdn.net/qq_61323055/article/details/127308087