• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

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

武飞扬头像
Bow.贾斯汀
帮助1

一、搭建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 = b a = a b
a -= b a = a - b
a *= b a = a * b
a /= b a = a / b
a %= b a = a % b
a ~/= b a = 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();
}

学新通

  • 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方法
  @override
  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 {
  @override
  eat() {
    print("吃骨头");
  }

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

12、多态

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

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

  • 案例分析:

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

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

abstract class Animal {
  eat();
}

class Dog extends Animal {
  @override
  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 {
  @override
  add() {
    print("mysql数据库添加数据");
  }
}

class Mssql implements DB {
  @override
  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{
  @override
  fun1() {
    throw UnimplementedError();
  }

  @override
  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> {
  @override
  getByKey(String key) {
    print(key);
  }

  @override
  void setByKey(String key, T value) {
    print("我是文件缓存,把${key}和${value}存储到了文件中");
  }
}

class MemoryCache<T> implements Cache<T> {
  @override
  getByKey(String key) {
    print(key);
  }

  @override
  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.谷歌.cn/books/docs/overview
  var url =
      Uri.https('www.谷歌apis.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));
}

学新通

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfhhchk
系列文章
更多 icon
同类精品
更多 icon
继续加载