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

请添加图片描述

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

请添加图片描述

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

请添加图片描述

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

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

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

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

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

请添加图片描述

  • 可以通过 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)创建这个静态集合也可以指定数据类型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);
}

请添加图片描述

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

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

请添加图片描述

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

请添加图片描述

  • 通过 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;
  }
}

请添加图片描述

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

if (num % 2 == 0) {
    num = 10;
  } else {
    num = 9;
  }

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

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

void main() {
  var a;
  var b = a ?? 10;
  print(b);
}
  • 可以输出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);
}

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

void main() {
  int num = 123;
  String s = num.toString();
  print(s is String);
}

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

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

6、循环语句

  • for 循环
for(声明变量; 条件判断; 变量改变){
  循环体;
}
  • while 循环
while(条件表达式){
  循环体;
}
  • do-while 循环
do{
  循环体;
}while(条件表达式);
  • 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] = "修改后的元素";
  • 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);
}

请添加图片描述

  • 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);
}
  • 可以通过 toList() 方法将Set类型的集合转化为 List 类型的集合
void main() {
  //创建一个set的集合
  var set = new Set();
  set.addAll([1, 2, 3, 4]);
  //利用toList()方法将集合转换成List类型
  print(set.toList() is List);
}

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

在这里插入图片描述

  • 常用方法:
方法名作用
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'));
}

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

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

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

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

for (var item in test) {
    print(item);
}

(3)通过forEach遍历

test.forEach((value) {
    print(value);
  });

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

test.where((value){
	return value;
})

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

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

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

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

test.any((value){
	return value == 'onesheep';
})

六、方法

1、方法的定义

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

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

请添加图片描述

  • 定义一个方法,可以打印用户信息(包含姓名和年龄)
String printUserInfo(String username, int age) {
  return "姓名: $username, 年龄: $age";
}

void main() {
  print(printUserInfo("YangJilong", 22));
}

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

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

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

String printUserInfo(String username, [int age]) {
  if (age == null) {
    return "姓名: $username, 年龄: 保密";
  }

  return "姓名: $username, 年龄: $age";
}

存在报错: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, '女');
}

在这里插入图片描述

(2)命名可选参数

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

请添加图片描述

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

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

fun() {
  print("内层方法");
}

fun2(f) {
  f();
}

void main() {
  fun2(fun);
}

在这里插入图片描述

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

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

请添加图片描述

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

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

4、箭头函数

  • 什么是箭头函数?

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

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

请添加图片描述

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

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

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

  • 案例分析: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");
}

请添加图片描述

我们需要注意:通过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");
}

请添加图片描述

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

请添加图片描述

6、匿名方法

  • 什么是匿名方法?

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

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

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

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

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

请添加图片描述

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

  • 代码格式如下:

void main(){
  ((形参列表){
    方法体;
  })(实参列表);
}

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

  • 重点在于声明什么时候结束递归
  • 代码格式:
//此处以n的阶乘为例
var sum = 1;
fun(int n) {
  if (n == 1) {
    return;
  }
  sum *= n;
  //递归
  fun(n - 1);
}

void main() {
  //计算5的阶乘
  fun(5);
  print(sum);
}

请添加图片描述

9、闭包

请添加图片描述

  • 为什么使用闭包?

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

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

fun() {
  var a = 123;
  return () {
    print(a++);
  };
}

void main() {
  var b = fun();
  b();
  b();
}

请添加图片描述

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

请添加图片描述

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

请添加图片描述

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

在这里插入图片描述

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

5、私有成员

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

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

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

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

class Person {
  String _name;
  int _age;
  Person(this._name, this._age);
  void printInfo() {
    print("姓名: $_name, 年龄: $_age");
  }
}

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

请添加图片描述

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

请添加图片描述

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

7、初始化列表

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

请添加图片描述

9、对象操作符

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

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

void main() {
  Person p;
  p.printInfo();
}

请添加图片描述

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

void main() {
  var p;
  p?.printInfo();
}

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

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

请添加图片描述


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)使用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();
}

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

请添加图片描述

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

请添加图片描述

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

请添加图片描述


11、抽象方法

  • 我们通常在抽象类中定义一些抽象方法,用于规范子类【抽象方法没有方法体】
  • 子类必须实现父类中的全部抽象方法
  • 使用 abstract 定义抽象类,抽象类中也可以有普通方法
abstract class Animal {
  eat();
  love();
}

class Dog extends Animal {
  
  eat() {
    print("吃骨头");
  }

  
  love() {
    print("喜欢听音乐");
  }
}

12、多态

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

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

  • 案例分析:

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

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

abstract class Animal {
  eat();
}

class Dog extends Animal {
  
  eat() {
    print("吃骨头");
  }

  run() {
    print("小狗在跑...");
  }
}

void main() {
  Animal a = new Dog();
  a.eat();
  //无法调用子类特有方法
  a.run();
}

请添加图片描述

去掉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();
}
  • 可以为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();
  }

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)也可以使用抽象的mixins类,不过要记得实现所有抽象方法

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

请添加图片描述

七、库和泛型

1、泛型

  • 什么是泛型?

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

  • 使用泛型有什么好处?

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

  • 通过一个 T 来代表泛型
T getData<T>(value){
  return value;
}

//通过以下代码调用[此处以字符串类型为例]
getData<String>("abc");

如果将返回值处的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)创建了三个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已加密");
}

请添加图片描述
4、库

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

(1)导入自定义库

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

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

import 'dart:io';
import 'dart:math';

(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)对于普通方法,直接调用即可;但是调用异步方法需要配合await使用

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

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

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

请添加图片描述

3、使用第三方库的流程

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

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

dependencies:
  http: ^0.13.5

(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}.');
  }
}
  • 如果库中某些部分出现了重名,可以通过库名.来指定那部分的内容

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

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

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

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

5、延迟加载

请添加图片描述

八、新版特性

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

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

在这里插入图片描述

  • 那么如何赋值空呢?

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

void main() {
  int? num = null;
  print(num);
}

在这里插入图片描述

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

String? getData(var value){
  if(value == null){
    return null;
  }else{
    return "数据获取成功";
  }
}

2、类型断言

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

  • 一般直接用于变量后面

void printLength(String? s) {
  print(s!.length);
}

void main() {
  printLength(null);
}

在这里插入图片描述

3、延迟初始化

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

在这里插入图片描述

  • 可以为属性添加关键字 late,这样代表延迟初始化【我们会通过其他方法完成初始化工作】
class Person {
  late String name;
  late int age;

  setInit(String name, int age) {
    this.name = name;
    this.age = age;
  }
}

4、required关键词

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

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

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

printInfo(String name, {required int age}) {
  print("$name --- $age");
}

void main() {
  printInfo("wang", age: 20);
}

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

在这里插入图片描述

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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bow.贾斯汀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值