Dart学习笔记(B站)

B站自学

视频地址:Dart Flutter教程_Dart Flutter2入门


变量、常量以及命名规则(chapter02)

dart是一个强大的脚本语言,可以不预先定义变量类型,会自动类型推断

  var str = '你好dart';
  print(str is String); // true

  var myNum = 1234;
  print(myNum is int); // true

dart变量命名区分大小写,Age和age是2个不同的变量


const和final

const值不变,一开始就得赋值

final可以开始不赋值,只能赋一次;而final不仅有const的编译时常量的特征,最重要的它是运行时常量,并且final是惰性初始化,即在运行时第一次使用前才初始化

如果要调用1个方法赋予1个常量,只能用final,如下:

// 如果要调用1个方法赋予1个常量,只能用final
  final a = new DateTime.now();
  print(a); // 2021-06-17 18:43:52.069089

如果使用const则报错:

const b = new DateTime.now(); // ERROR
print(b); // ERROR

dart数据类型详解(chapter03)

String类型

字符串类型可以是单引号也可以是双引号

使用3个单引号输出可以自动断行

  var str = '''
  this is str1
  this is str2
  ''';
  print(str);

字符串拼接2种方法

  String str1 = '你好';
  String str2 = 'Dart';
  print('$str1 $str2');
  print(str1 + ' ' + str2);

num类型

int不能被赋值doubledouble可以被赋值int

 int a = 123;
  // a = 23.23; // ERROR
  print(a);

  double b = 44.33;
  b = 234;
  print(b);  // 234.0

所以,int必须是整型,double可以是整型,也可以是浮点型。

List类型(数组/集合)

List中可以插入不同的数据类型

创建List的几种方式:

  • 不指定类型
  var list1 = ["张三", 20, true]; // 创建1个不指定类型的List
  • 指定类型:采用<Type>[]
  var list2 = <String>[]; // 存放的必须是String类型
  • 使用List关键字
  List list = ['张三', '李四', '王五'];
  List list = <String>['张三', '李四', '王五'];
  • 创建1个固定长度的List(通过 List.filled(length,fill)创建的List没法修改长度)
  // 创建1个固定长度的集合
  var list4 = List.filled(2, "a");
  print(list4); // [a, a]
  list4[1] = "c";
  print(list4); // [a, c]
  // list4.add("b"); // ERROR,因为List长度固定为2
  // list4.length = 4; // ERROR,通过 List.filled(length,fill)创建的List没法修改长度
  • 创建1个固定长度并指定类型的集合
  // 创建1个固定长度并指定类型的集合
  var list5 = List<String>.filled(2, "str");
  print(list5); // [str, str]

Maps类型

Maps类型有点类似JavaScript中的JSON对象

Maps的几种定义方式:

  • 第一种定义Maps的方式
 // 第一种定义Maps的方式
  var person = {
    "name": "张三",
    "age": 20,
    "work": ["程序员", "外卖员"]
  };

  print(person); // {name: 张三, age: 20}
  // 这里和js不同,js中是person.name
  print(person['name']); // 张三
  print(person['work']); // [程序员, 外卖员]
  • 第二种定义Maps的方式
// 第二种定义Maps的方式
  var p = new Map();
  p['name'] = '李四';
  p['age'] = 22;
  p['work'] = ["工程师", "程序员"];
  print(p); // {name: 李四, age: 22, work: [工程师, 程序员]}

Dart中可以通过is关键字来判断类型

void main() {
  var str = "hello world";

  if (str is String) {
    print('是String类型'); // print
  } else if (str is int) {
    print('是int类型');
  } else {
    print('是其它类型');
  }
}


运算符、条件表达式、Dart类型转换(chapter04)

算术运算符

取整操作符~/

int a = 13;
int b = 5;

print(a ~/ b);  // 2 

赋值运算符

??=表示如果a为空,则把b赋值给a

  int c;
  c ??= 99;
  print(c); // 99

  int d = 8;
  d ??= 88;
  print(d);  // 8

??运算符效果如下

  var a;
  var b = a ?? 10;
  print(b); // 10

  var c = 7;
  var d = c ?? 99;
  print(d); // 7

类型转换

Number类型转String类型用toString()String类型转Number类型用parse

StringNumber

  String str = '123';
  var intNum = int.parse(str);
  var doubleNum = double.parse(str);
  print(intNum); // 123
  print(intNum is int); // true
  print(doubleNum); // 123.0
  print(doubleNum is double); // true

String为带小数的字符串时,只能用double.parse()

  String str = '123.1';
 //  var intNum = int.parse(str); // ERROR
 // print(intNum); // ERROR
  var doubleNum = double.parse(str);
  print(doubleNum);  // 123.1

如果后端返回空字符串,前端做try catch处理

  // 报错,输出0
  String price = '';
  try {
    var priceNum = double.parse(price);
    print(priceNum);
    print(priceNum is double);
  } catch (e) {
    print(0);  // 0
  }

NumberString

// Number类型转String
  var myNum = 12;
  var str = myNum.toString();
  print(str); // 12
  print(str is String); // true

isEmpty判断字符串是否为空

  var str = 'xxx';
  if (str.isEmpty) {
    print('str空');
  } else {
    print('str非空');  // str非空
  }

isNaN用于检查值是否是非数字值

 var num = 0 / 0;
  if (num.isNaN) {
    print('num is NaN');  // num is NaN
  }

循环语句(chapter05)

for循环

  • 循环打印List中的元素
  List list = ['张三', '李四', '王五'];

  for (int i = 0; i < list.length; i++) {
    print(list[i]);
  }
  • 循环打印List中的Map元素:
  List list = [
    {"name": "chengzhu", "age": 38},
    {"name": "zhangyun", "age": 37},
    {"name": "donglei", "age": 34}
  ];

  for (int i = 0; i < list.length; i++) {
    print(list[i]['name']);
  }

输出:

chengzhu
zhangyun
donglei
  • 循环打印二维数组:
List list = [
    {
      "country": '西班牙',
      "clubs": [
        {"name": "皇家马德里", "champion": 14},
        {"name": "巴塞罗那", "champion": 5},
        {"name": "马德里竞技", "champion": 0}
      ]
    },
    {
      "country": '英格兰',
      "clubs": [
        {"name": "曼联", "champion": 3},
        {"name": "利物浦", "champion": 6},
        {"name": "切尔西", "champion": 2}
      ]
    },
    {
      "country": '意大利',
      "clubs": [
        {"name": "AC米兰", "champion": 7},
        {"name": "国际米兰", "champion": 3},
        {"name": "尤文图斯", "champion": 2}
      ]
    }
  ];

  for (int i = 0; i < list.length; i++) {
    print(list[i]['country']);
    for (int j = 0; j < list[i]['clubs'].length; j++) {
      var club = list[i]['clubs'][j]['name'];
      var num = list[i]['clubs'][j]['champion'];
      print(' $club $num');
    }
  }

输出

西班牙
 皇家马德里 14
 巴塞罗那 5
 马德里竞技 0
英格兰
 曼联 3
 利物浦 6
 切尔西 2
意大利
 AC米兰 7
 国际米兰 3
 尤文图斯 2

break和continue

break语句功能:

  • switch语句中使流程跳出switch结构
  • 在循环语句中使流程跳出当前循环,遇到break循环终止,后面代码也不会执行

强调:

  • 如果在循环中已经执行了break语句,就不会执行循环体中位于break后的语句
  • 在多层循环中,1个break语句只能往外跳出1层

continue语句的功能:

  • 只能在循环语句中使用,使本次循环结束,即跳出循环题中下面尚未执行的语句,接着进行
  // 打印1~10中不等于4的所有值的
  for (int i = 1; i <= 10; i++) {
    if (i == 4) {
      continue; // 跳过当前循环,然后循环还会继续执行
    }
    print(i);
  }

输出

1
2
3
5
6
7
8
9
10
  • continue可以在for循环以及while循环,但是不建议用在while循环中,容易死循环

打印1~10中不等于4的所有值:遇到continue,本次循环结束,下面循环继续

// 打印1~10中不等于4的所有值的
  for (int i = 1; i <= 10; i++) {
    if (i == 4) {
      continue; // 跳过当前循环,然后循环还会继续执行
    }
    print(i);
  }

上面改成break,则只打印出1,2,3:遇到break,循环结束

  for (int i = 1; i <= 10; i++) {
    if (i == 4) {
      break; // 跳过本次循环,后面循环不执行
    }
    print(i);
  }

break语句只能向外跳出1层循环,如下:

  // break语句只能向外跳出1层循环
  for (var i = 0; i < 5; i++) {
    for (var j = 0; j < 3; j++) {
      if (j == 1) {
        break; // 只能跳出1层循环,后面j=2也不执行了
      }
      print('里层$j');
    }
    print('外层--$i');
  }

输出:

里层0
外层--0
里层0
外层--1
里层0
外层--2
里层0
外层--3
里层0
外层--4

集合类型List Set Map详解(chapter06)

List

List定义和属性

定义List可以用var也可以用List,通过<type>指定List类型

  var list1 = ['香蕉', '苹果', '西瓜'];
  var list2 = <String>['足球', '篮球', '网球'];
  
  List list3 = ['香蕉', '苹果', '西瓜'];
  List list4 = <String>['足球', '篮球', '网球'];

创建固定长度的List,不能使用add方法,只能修改内容,同样也可以指定List类型

  List list1 = List.filled(2, "");
  list1[1] = 'chengzhu';
  print(list1); // [, chengzhu]

  // 创建指定长度和固定类型的List
  List list2 = List<String>.filled(2, "");
  list2[0] = 'zhangyun';
  print(list2); // [zhangyun, ]

List常用的属性和方法:

 常用属性:
        length          长度
        reversed        翻转
        isEmpty         是否为空
        isNotEmpty      是否不为空
    常用方法:  
        add         增加
        addAll      拼接数组
        indexOf     查找  传入具体值
        remove      删除  传入具体值
        removeAt    删除  传入索引值
        fillRange   修改   
        insert(index,value);            指定位置插入    
        insertAll(index,list)           指定位置插入List
        toList()    其他类型转换成List  
        join()      List转换成字符串
        split()     字符串转化成List
        forEach   
        map
        where
        any
        every

List常用属性:

  var list = ['香蕉', '苹果', '西瓜'];
  print(list.length); // 3

  //翻转List元素,列表元素倒序排序,注意翻转后不是List
  print(list.reversed); // (西瓜, 苹果, 香蕉)
  print(list.reversed is List); // false
  var newList = list.reversed.toList();

  // 此时newList是List类型
  print(newList); // [西瓜, 苹果, 香蕉]
  print(newList is List); // true

  print(list.isEmpty); // false
  print(list.isNotEmpty); // true

List常用方法

add()增加元素:

  var list = ['香蕉', '苹果', '西瓜'];
  list.add('桃子');
  print(list); // [香蕉, 苹果, 西瓜, 桃子]

addAll()拼接List

  // addAll()传入List
  list.addAll(['椰子', '荔枝']);
  print(list); // [香蕉, 苹果, 西瓜, 桃子, 椰子, 荔枝]

indexOf()查找元素:

  // indexOf查找数据,查找不到返回-1,查找到返回索引值
  print(list.indexOf('荔枝')); // 5
  print(list.indexOf('火龙果')); // -1

remove()移除单个元素,参数是元素:

  // remove方法,参数是元素
  list.remove('西瓜');
  print(list); // [香蕉, 苹果, 桃子, 椰子, 荔枝]
  list.remove('菠萝蜜'); // 不存在的元素不影响
  print(list); // [香蕉, 苹果, 桃子, 椰子, 荔枝]

removeAt()传入索引值移除元素

  // removeAt方法,参数是索引值
  list.removeAt(0); // 删除香蕉
  print(list); // [苹果, 桃子, 椰子, 荔枝]

fillRange()修改元素

 // 修改元素,fillRange方法,参数是start,end,元素
  list.fillRange(1, 3, '榴莲');  // 修改元素1和元素2
  print(list); // [苹果, 榴莲, 榴莲, 荔枝]

insert()指定位置插入单条数据

  // 指定位置插入单条数据
  list = ['香蕉', '苹果', '西瓜'];
  list.insert(1, '椰子');
  print(list); // [香蕉, 椰子, 苹果, 西瓜]

insertAll()指定位置插入多条数据

 // 指定位置插入多条数据
  list = ['香蕉', '苹果', '西瓜'];
  list.insertAll(1, ['椰子', '榴莲']);
  print(list); // [香蕉, 椰子, 榴莲, 苹果, 西瓜]

join()List转成字符串

  // List转换成字符串
  list = ['香蕉', '苹果', '西瓜'];
  String str = list.join('-'); // 以-号分割
  print(str); // 香蕉-苹果-西瓜
  print(str is String); // true

split()把字符串转成List

  // 字符串转List
  var str2 = '香蕉-苹果-西瓜';
  List list2 = str.split('-');
  print(list2); // [香蕉, 苹果, 西瓜]
  print(list2 is List); // true

Set

Set定义和属性

Set最主要的功能就是去除数组重复内容,Set是没有顺序且不能重复的集合,所以不能通过索引去获取值

定义1个Set并添加元素:注意打印出来是{}不是[]

  var s = new Set();
  s.add('香蕉');
  s.add('苹果');
  print(s); // {香蕉, 苹果}
  s.add('苹果'); // 去重
  print(s); // {香蕉, 苹果}

SetList

  // Set转List
  List l = s.toList();
  print(l); // [香蕉, 苹果]
  print(l is List); // true

利用Set给List去重:

 // 数组去重
  var list = ['香蕉', '苹果', '椰子', '香蕉', '苹果', '椰子', '梨子'];
  var set = new Set();
  set.addAll(list); // List作为参数传入
  print(set.toList()); // [香蕉, 苹果, 椰子, 梨子]

Maps

Maps定义和属性

映射Maps是无序的键值对

定义Maps的2种方式:

  // 第1种方式定义Map
  var person = {"name": '张三', "age": 30};
  print(person); // {name: 张三, age: 30}

  // 第2种方式定义Map
  var m = new Map();
  m['name'] = '李四';
  m['age'] = 28;
  print(m); // {name: 李四, age: 28}
  
  // 第3种方式定义Map
  Map person = {"name": '张三', "age": 30};
  print(person);  // {name: 张三, age: 30}

Maps常用的属性和方法:

映射(Maps)是无序的键值对:

    常用属性:
        keys            获取所有的key值
        values          获取所有的value值
        isEmpty         是否为空
        isNotEmpty      是否不为空
    常用方法:
        remove(key)     删除指定key的数据
        addAll({...})   合并映射  给映射内增加属性
        containsValue   查看映射内的值  返回true/false
        forEach   
        map
        where
        any
        every

keysvaluesisEmptyisNotEmpty属性:

  // keys获取所有的key值
  Map person = {"name": '张三', "age": 30, "sex": '男'};
  print(person.keys); // (name, age, sex)
  print(person.keys.toList()); // [name, age, sex]

  // values获取所有的value值
  print(person.values.toList()); // [张三, 30, 男]
  
  // isEmpty和isNotEmpty
  print(person.isEmpty); // false
  print(person.isNotEmpty); // true

Maps常用方法

addAll()增加键值对,remove()删除键值对,containsKey()containsValue()

 Map person = {"name": '张三', "age": 30, "sex": '男'};

  // 增加键值对,没有单独的add方法
  person.addAll({
    'work': ['程序员', '测试员'],
    'height': 178
  });
  print(person); // {name: 张三, age: 30, sex: 男, work: [程序员, 测试员], height: 178}

  // 删除键值对
  person.remove('height');
  print(person); // {name: 张三, age: 30, sex: 男, work: [程序员, 测试员]}

  // 查找是否包含key或者value
  print(person.containsKey('name')); // true
  print(person.containsValue('张三')); // true

List、Set和Maps通用的方法

forEach()map()where()any()every()

使用forfor-in遍历List:

  List myList = ['香蕉', '苹果', '西瓜'];

 // 常规for循环遍历
  for (var i = 0; i < myList.length; i++) {
    print(myList[i]);
  }

  // for-in遍历
  for (var item in myList) {
    print(item);
  }

ListMaps使用forEach()方法,注意参数是1个函数:

 List myList = ['香蕉', '苹果', '西瓜'];
 Map myMap = {
    "name": '张三',
    "age": 30,
    "sex": '男',
    'work': ['程序员', '测试员'],
    'height': 178
  };

 // List遍历forEach参数是以value为参数的1个函数
  myList.forEach((value) {
    print('$value');
  });

  // Map遍历forEach参数是以(k,v)为参数的函数
  myMap.forEach((key, value) {
    print('$key --- $value');
  });

输出:

香蕉
苹果
西瓜
name --- 张三
age --- 30
sex --- 男
work --- [程序员, 测试员]
height --- 178

map()方法可以逐个修改List里面的元素:注意map()的参数是(param){}形式,最好不要用箭头函数

 List myList = [1, 2, 3];

// map方法
  var newList = myList.map((item) {
    var value = item * 2;
    return value;
  });

  print(myList); // [1, 2, 3]
  print(newList); // (2, 4, 6)
  print(newList.toList()); // [2, 4, 6]

实验发现使用箭头函数只能用逗号隔开语句,并且不能有返回值

  var newList = myList.map((item) => {
    print(item),   // 逗号隔开语句
    print(item * 2)
  });

where()方法:条件判断后过滤

  var myList = [1, 3, 4, 5, 7, 8, 9];

  // 查找>5的新List
  var newList = myList.where((value) {
    return value > 5;
  });
  print(myList); // [1, 3, 4, 5, 7, 8, 9]

  print(newList); // (7, 8, 9)
  print(newList.toList()); // [7, 8, 9]

any()方法:判断集合里面是否有满足条件的元素,满足返回true

  var myList = [1, 3, 4, 5, 7, 8, 9];

  // any()方法判断集合里面是否有满足条件的元素,满足则返回true
  var f = myList.any((item) {
    return item > 5;
  });
  print(f); // true

every()方法判断集合里面是否每个元素都满足条件,满足返回true

  var myList = [1, 3, 4, 5, 7, 8, 9];

  // every()方法判断集合里面是否每个元素都满足条件,满足返回true
  var e = myList.every((item) {
    return item > 5;
  });
  print(e); // false

Set使用forEach()遍历:

  var list = ['香蕉', '苹果', '椰子', '香蕉', '苹果', '椰子', '梨子'];
  var set = new Set();
  set.addAll(list); // List作为参数传入
  print(set); // {香蕉, 苹果, 椰子, 梨子}

  set.forEach((item) {
    print(item);
  });

只有1行代码也可以使用箭头函数:

  set.forEach((element) => print(element));

输出:

香蕉
苹果
椰子
梨子

Maps使用forEach()遍历:

  Map myMap = {
    "name": '张三',
    "age": 30,
    "sex": '男',
    'work': ['程序员', '测试员'],
    'height': 178
  };

  myMap.forEach((key, value) {
    print('$key --- $value');
  });
}

输出:

name --- 张三
age --- 30
sex --- 男
work --- [程序员, 测试员]
height --- 178

Dart中的函数、函数的定义、可选参数、默认参数、命名参数、箭头函数、匿名函数、闭包(上) (chapter07)

函数的定义:

返回类型  方法名称(参数1,参数2,...{
        方法体
        return 返回值;
      }

建议函数返回值和参数类型都指定

void main() {
  print(sum(100)); // 5050
  print(sum(90)); // 4095
}

/**
 * 建议函数返回值和参数类型都指定
 */
int sum(int n) {
  int sum = 0;
  for (var i = 0; i <= n; i++) {
    sum = sum + i;
  }
  return sum;
}

可选参数

在Dart中定义带可选参数的方法,可选参数放在最后,同时用[]包裹

void main() {
  // 使用可选参数
  print(printUserInfo1('chengzhu')); // 姓名:chengzhu
  print(printUserInfo1('zhangyun', 38)); // 姓名:zhangyun --- 年龄:38
}

String printUserInfo1(String userName, [int age]) {
  if (age != null) {
    return '姓名:$userName --- 年龄:$age';
  }
  return '姓名:$userName';
}

默认参数

如下,sex默认为男

void main() {
  // 使用默认参数
  print(printUserInfo2('donglei')); // 姓名:donglei --- 年龄:保密 --- 性别:男
  print(printUserInfo2('dubeibei', '女', 30)); // 姓名:dubeibei --- 年龄:30 --- 性别:女
  // print(printUserInfo2('dubeibei',30)); // ERROR
}

/**
 * 默认参数sex,默认参数一般放在可选参数前面
 */
String printUserInfo2(String userName, [String sex = '男', int age]) {
  if (age != null) {
    return '姓名:$userName --- 年龄:$age --- 性别:$sex';
  }
  return '姓名:$userName --- 年龄:保密 --- 性别:$sex';
}

命名参数

注意命名参数传值的时候需要用param:value形式,这点和可选参数不同

void main() {
  // 调用命名参数
  print(printUserInfo1('dubeibei'));
  print(printUserInfo1('dubeibei', age: 30));
  print(printUserInfo1('dubeibei', age: 30, sex: '女'));
}

/**
 * 定义1个命名参数
 */
String printUserInfo1(String userName, {int age, String sex = '男'}) {
  if (age != null) {
    return '姓名:$userName --- 年龄:$age --- 性别:$sex';
  }
  return '姓名:$userName --- 年龄:保密 --- 性别:$sex';
}

输出:

姓名:dubeibei --- 年龄:保密 --- 性别:男
姓名:dubeibei --- 年龄:30 --- 性别:男
姓名:dubeibei --- 年龄:30 --- 性别:女

把方法作为参数传递

把1个方法作为参数传递:

如下所示:

void main() {
  fn2(fn1); // fn1
}

fn1() {
  print('fn1');
}

fn2(fnName) {
  fnName(); // 执行传入的方法
}


匿名方法

定义匿名方法:

void main() {
  fn(); // 我是1个匿名方法
}

/**
 * 定义1个匿名方法
 */
var fn = () {
  print('我是1个匿名方法');
};

Dart中的函数、函数的定义、可选参数、默认参数、命名参数、箭头函数、匿名函数、闭包(下) (chapter08)

箭头函数

箭头函数只能写1条语句

  List list = ['苹果', '香蕉', '西瓜'];

  // 写法1
  list.forEach((item) => print(item)); // 不使用{}

  // 写法2
  list.forEach((item) => {
        print(item) // {}内注意没有分号
      });

使用map()结合箭头函数实现元素修改:

 List list = [4, 1, 2, 3, 4];

  // 使用map
  var newList = list.map((value) {
    if (value > 2) {
      return value * 2;
    }
    return value;
  });

  print(newList.toList()); // [8, 1, 2, 6, 8]

  // 使用箭头函数
  var newList2 = list.map((value) => value > 2 ? value * 2 : value);
  print(newList2.toList());  // [8, 1, 2, 6, 8]

匿名方法

定义和使用匿名方法

void main() {
  // 调用匿名方法
  printNum(99); // 99

  printNum2(123); // 123
}

/**
 * 匿名方法
 */
var printNum = (int n) {
  print(n);
};

/**
 * 箭头函数定义匿名方法
 */
var printNum2 = (n) => print(n);

自执行方法

自执行方法写法为()(),第1个()定义参数类型,第2个()为传入的参数

void main() {
  // 定义自执行方法
  ((int n) {
    print(n); // 12
    print('我是自执行方法'); // 我是自执行方法
  })(12); // 传入参数
}

方法的递归

递归求阶乘:

void main() {
  fn(5);
  print(sum);  // 120
}

// 全局变量
var sum = 1;

void fn(n) {
  sum *= n;
  if (n == 1) {
    return;
  }
  fn(n - 1);
}

递归求和:

void main() {
  print(fn(100)); // 5050
}

var sum = 0;

int fn(int n) {
  sum += n;
  if (n == 0) {
    return 0;
  }
  fn(n - 1);
  return sum;
}

闭包

全局变量特点:常驻内存,污染全局
局部变量:不常驻内存,会被垃圾机制回收,不会污染全局

想实现的功能:常驻内存但不污染全局,闭包可以解决这个问题

闭包: 函数嵌套函数, 内部函数会调用外部函数的变量或参数, 变量或参数不会被系统回收(不会释放内存)

闭包的写法: 函数嵌套函数,并return 里面的函数,这样就形成了闭包。

void main() {
  var b = fn();
  b(); // 124
  b(); // 125
  b(); // 126
}

fn() {
  var a = 123; /*不会污染全局 常驻内存*/
  // 方法里面签套1个方法并返回这个方法,形成闭包
  return () {
    a++;
    print(a); // a同时具有全局变量和局部变量的特点
  };
}

对象和类 (chapter09)

基本概念

Dart所有的东西都是对象,所有的对象都继承自Object类。

Dart是一门使用类和单继承的面向对象语言,所有的对象都是类的实例,并且所有的类都是Object的子类

创建1个类的2种方法:

 var p1 = new Person();  // 也可以
  Person p2 = new Person();  // 推荐使用

类中访问属性建议使用this关键字:

void main() {
  // var p1 = new Person();  // 也可以
  Person p1 = new Person();
  print(p1.name); // 张三
  p1.getInfo(); // 张三 --- 23

  p1.setInfo(37);
  p1.getInfo(); // 张三 --- 37
}

/**
 * 定义类
 */
class Person {
  String name = '张三';
  int age = 23;

  void getInfo() {
    // print('$name --- $age');
    print('${this.name} --- ${this.age}'); // 推荐使用
  }

  void setInfo(int age) {
    this.age = age;
  }
}


构造函数

默认构造函数

默认构造函数:

  // 默认构造函数
  Person() {
    print('这是构造函数的内容,这个方法在实例化的时候触发');
  }

默认构造函数传入参数可以多次实例化

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

上式可以简写为:使用this关键字

  // 默认构造函数的简写
  Person(this.name, this.age);

命名构造函数

Dart中默认构造函数只能1个,但是命名构造函数可以多个

  // 命名构造函数1
  Person.now() {
    print('我是命名构造函数');
  }

  // 命名构造函数2
  Person.setInfo(String name, int age) {
    this.name = name;
    this.age = age;
  }

调用命名构造函数:

void main() {
  var d = new DateTime.now(); // 实例化DateTime调用它的命名构造函数
  print(d); // 2021-06-23 22:55:13.563098
  print(d is String); // false
  print(d is Object); // true

  Person p1 = new Person('李四', 41); // 默认实例化类的时候调用的是默认构造函数
  p1.printInfo(); // 李四 --- 41

  Person p2 = new Person.now(); // 调用命名构造函数1
  Person p3 = new Person.setInfo('张云', 36); // 调用命名构造函数2
  p3.printInfo(); // 张云 --- 36
}

把类抽离成独立的文件

新建lib文件夹存放类文件,路径如下:demo04.dart调用Person.dart中定义的Person
在这里插入图片描述

Person.dart:

class Person {
  String name = '张三';
  int age = 23;

  // 默认构造函数的简写
  Person(this.name, this.age);

  // 命名构造函数1
  Person.now() {
    print('我是命名构造函数');
  }

  // 命名构造函数2
  Person.setInfo(String name, int age) {
    this.name = name;
    this.age = age;
  }

  void printInfo() {
    print('${this.name} --- ${this.age}'); // 推荐使用
  }
}

demo04.dart

import 'lib/Person.dart';

void main() {
  Person p3 = new Person.setInfo('张云', 36); // 调用命名构造函数2
  p3.printInfo(); // 张云 --- 36}
}

私有方法和私有属性

Dart中没有publicprivateprotected等修饰符

Dart中使用_把1个属性或者方法定义成私有,同时必须把类抽离成单独的文件

新建Animal.dart

class Animal {
  String _name;
  int age;

  Animal(this._name, this.age);

  void printInfo() {
    print('${this._name} --- ${this.age}'); // 推荐使用
  }
}

demo05.dart中访问私有属性_name就报错,但是可以访问公有属性age:

import 'lib/Animal.dart';

void main() {
  Animal a = new Animal('小狗', 3);
  // print(a._name); // ERROR
  print(a.age); // 3
}

类中定义的私有方法没法在外部直接访问,需要在类中定义1个公有方法调用类的私有方法

  // 定义类的公有方法,通过该方法可以访问私有属性_name
  String getName() {
    return this._name;
  }

  // 定义私有方法
  void _run() {
    print('这是1个私有方法');
  }

  // 定义1个公有方法访问类的私有方法
  void execRun() {
    this._run();
  }

调用类的公有方法execRun() ==> 从而调用类的私有方法_run()

import 'lib/Animal.dart';

void main() {
  Animal a = new Animal('小狗', 3);
  // print(a._name); // ERROR
  print(a.age); // 3
  print(a.getName()); // 小狗
  a.execRun(); // 这是1个私有方法
}

getter和setter

注意在Dart类中,settergetter是属性,调用的时候要遵循属性的方式

getter是没有参数列表的

class Rect {
  num height;
  num width;

  Rect(this.height, this.width);

  // num area() {
  //   return this.height * this.width;
  // }

  // 注意getter方法没有()
  num get area {
    return this.height * this.width;
  }

  // setter方法是void
  void set areaHeight(num height) {
    this.height = height;
  }
}

调用areaareaHeight:setter是属性,所以不能写成r.areaHeight(37),同时getter是没有参数列表的

  // 注意调用直接通过访问属性的方式访问area
  print('面积:${r.area}'); // 面积:40

  // 注意setter是属性,不能写成r.areaHeight(37)
  r.areaHeight = 37;
  print('高度:${r.height}');  // 高度:37

初始化列表

Dart中我们也可以在构造函数体运行之前初始化实例变量,即先赋值再执行构造函数

void main() {
  Rect r = new Rect(); // 高度:2 --- 宽度:10
}

class Rect {
  num height;
  num width;

  // 初始化列表
  Rect()
      : height = 2,
        width = 10 {
    print('高度:${this.height} --- 宽度:${this.width}');
  }

输出:可以看到赋值在构造函数执行之前

高度:2 --- 宽度:10

初始化2个变量:

  Rect()
      : height = 2,
        width = 10 {}

调用:

  Rect r = new Rect();
  print(r.getArea()); // 20

初始化1个变量:

  Rect(width) : height = 8 {
    this.width = width;
  }

调用:

  Rect r = new Rect(2);
  print(r.getArea()); // 16

类中定义了初始化列表就不能定义默认构造函数,否则报错


静态成员、操作符和类的继承(chapter10)

静态成员

Dart中的静态成员:

  • 使用static关键字来实现类级别的变量和函数
  • 静态方法不能访问非静态成员,非静态方法可以访问静态成员

静态属性和静态方法不需要实例化类,可以直接调用:即不需要new一个实例

void main() {
  Person1 p1 = new Person1();
  p1.show(); // 张三

  // 不需要实例化类,静态属性和静态方法可以直接类调用
  Person2.show(); // 李四
  print(Person2.name); // 李四
}

class Person1 {
  String name = '张三';

  void show() {
    print(name);
  }
}

// 定义静态属性和方法
class Person2 {
  static String name = '李四';

  static void show() {
    print(name);
  }
}

定义1个Person类,其中name是静态属性,printUserInfo()是静态方法;age是类属性,printInfo()是类方法,那么可以得出:

  • 非静态方法printInfo()可以调用类属性age和静态属性name
  // 非静态方法可以访问静态成员以及非静态成员
  void printInfo() {
    // 调用静态成员不要用this,因为this表示实例化的类,静态成员和静态方法是属于类的
    print(name); // 调用静态属性
    print(this.age); // 调用非静态属性
  }
  • 静态方法printUserInfo()可以调用静态属性name,但是无法访问非静态属性age和静态方法printInfo()
 // 静态方法只能访问静态成员,不能访问非静态成员
  static void printUserInfo() {
    print(name);
    // 静态方法无法访问非静态成员
    // print(this.age);  // ERROR
    // this.printInfo();  // ERROR
  }

完整代码:

void main() {
  Person p = new Person();

  // 非静态方法
  p.printInfo(); // 李四

  // 静态方法
  Person.printUserInfo(); // 李四
}

class Person {
  static String name = '李四';
  int age = 20;

  static void show() {
    print(name);
  }

  // 非静态方法可以访问静态成员以及非静态成员
  void printInfo() {
    // 调用静态成员不要用this,因为this表示实例化的类,静态成员和静态方法是属于类的
    print(name); // 调用静态属性
    print(this.age); // 调用非静态属性
  }

  // 静态方法只能访问静态成员,不能访问非静态成员
  static void printUserInfo() {
    print(name);
    // 静态方法无法访问非静态成员
    // print(this.age);  // ERROR
    // this.printInfo();  // ERROR
  }
}

输出:

李四
20
李四

对象操作符

Dart中的对象操作符:

  • ? 条件运算符 (了解)
  • as 类型转换
  • is 类型判断
  • .. 级联操作 (连缀) (记住)

条件运算符?:如果条件不成立则不执行后面的方法

   Person p;
  // p.printInfo();  // ERROR
  p?.printInfo(); // p为空则不会调用printInfo()

  Person p1 = new Person('张三', 20);
  p1?.printInfo();  // 张三 --- 20

类型判断运算符is

  Person p = new Person('张三', 20);
  if (p is Person) {
    // 判断p是不是Person类
    p.name = '李四';
  }
  p.printInfo(); // 李四 --- 20
  print(p is Object); // true

类型转换运算符as

var p1;
  p1 = '';
  print(p1 is String); // true

  p1 = new Person('张三', 20);
  // 老版本下条语句报错
  // p1.printInfo(); // 张三 --- 20

  p1 as Person; // p1从String转为Person
  print(p1 is String); // false
  print(p1 is Person); // true
  p1 = new Person('张三', 20);
  p1.printInfo(); // 张三 --- 20

级联操作符..:非常重要,经常用

  Person p1 = new Person('张三', 20);
  p1.printInfo(); // 张三 --- 20

  // 级联操作符
  p1
    ..name = '李四'
    ..age = 31
    ..printInfo(); // 李四 --- 31

继承

Dart中类的继承:

  • 子类使用extends关键字来继承父类
  • 子类会继承父类里面可见的属性和方法,但是不会继承构造函数
  • 子类能复写父类的方法 gettersetter

基本概念

void main() {
  Web w = new Web();
  w.printInfo(); // 张三 --- 20
  print(w._sex); // 男
}

class Person {
  String name = '张三';
  num age = 20;
  String _sex = '男';

  void printInfo() {
    print('${this.name} --- ${this.age}');
  }
}

// Web继承Person类
class Web extends Person {}


子类不能继承父类的构造方法

一般来说构造方法使用类名(this.属性1, this.属性2)来定义,如果父类这样定义构造函数,由于子类不能继承父类的构造函数,所以需要自定义构造函数,同时可以通过super()把参数传递给父类

void main() {
  Person p1 = new Person('李四', 20);
  p1.printInfo(); // 李四 --- 20

  Web w = new Web('张云', 36);
  w.printInfo(); // 张云 --- 36
}

class Person {
  String name;
  num age;

  Person(this.name, this.age);

  void printInfo() {
    print('${this.name} --- ${this.age}');
  }
}

// Web继承Person类
// 父类构造函数有参数,子类不能继承父类构造函数
class Web extends Person {
  // super写法类似初始化列表,表示把子类的name和age赋值给父类的name和age
  Web(String name, num age) : super(name, age) {}
}


子类定义自己的属性和方法

子类除了继承父类里面可见的属性和方法,还可以定义自己的属性和方法:

void main() {
  Person p1 = new Person('李四', 20);
  p1.printInfo(); // 李四 --- 20

  Web w = new Web('张云', 36, '男');

  // 调用父类方法
  w.printInfo(); // 张云 --- 36

  // 调用子类方法
  w.run(); // 张云 --- 36 --- 男
}

class Person {
  String name;
  num age;

  Person(this.name, this.age);

  void printInfo() {
    print('${this.name} --- ${this.age}');
  }
}

// Web继承Person类
// 父类构造函数有参数,子类不能继承父类构造函数
class Web extends Person {
  String sex;

  // super写法类似初始化列表,表示把子类的name和age赋值给父类的name和age
  Web(String name, num age, String sex) : super(name, age) {
    this.sex = sex;
  }

  run() {
    print('${this.name} --- ${this.age} --- ${this.sex}');
  }
}


子类给父类的命名构造函数传参

子类除了可以给父类的默认构造函数传参,也可以给父类的命名构造函数传参:

void main() {
  // 定义并调用父类命名构造函数
  Person p = new Person.xxx('张三', 40);
  p.printInfo(); // 张三 --- 40

  Web w = new Web('张云', 36, '男');

  // 调用父类方法
  w.printInfo(); // 张云 --- 36

  // 调用子类方法
  w.run(); // 张云 --- 36 --- 男
}

class Person {
  String name;
  num age;

  // 默认构造函数
  Person(this.name, this.age);

  // 命名构造函数
  Person.xxx(this.name, this.age);

  void printInfo() {
    print('${this.name} --- ${this.age}');
  }
}

// Web继承Person类
// 父类构造函数有参数,子类不能继承父类构造函数
class Web extends Person {
  String sex;

  // super写法类似初始化列表,表示把子类的name和age赋值给父类的name和age
  // Web(String name, num age, String sex) : super(name, age) {
  //   this.sex = sex;
  // }

  // 子类给父类的命名构造函数传参数
  Web(String name, num age, String sex) : super.xxx(name, age) {
    this.sex = sex;
  }

  run() {
    print('${this.name} --- ${this.age} --- ${this.sex}');
  }
}

子类覆写父类的方法

建议覆写父类方法的的时候增加 @override

void main() {
  Web w = new Web('张云', 36);
  w.printInfo(); // 姓名:张云 --- 年龄:36
}

class Person {
  String name;
  num age;

  Person(this.name, this.age);

  void printInfo() {
    print('${this.name} --- ${this.age}');
  }

  void work() {
    print('${this.name}在工作...');
  }
}

class Web extends Person {
  Web(String name, num age) : super(name, age);

  run() {
    print('run');
  }

  // 覆写父类的方法
  @override
  void printInfo() {
    print('姓名:${this.name} --- 年龄:${this.age}');
  }
}


子类的方法调用父类的方法

使用super关键字:

void main() {
  Web w = new Web('张云', 36);
  w.run(); // 张云在工作...
}

class Person {
  String name;
  num age;

  Person(this.name, this.age);

  void work() {
    print('${this.name}在工作...');
  }
}

class Web extends Person {
  Web(String name, num age) : super(name, age);

  run() {
    super.work(); // 子类调用父类的方法
  }
}

抽象类、多态和接口(chapter11)

抽象类

Dart中抽象类: Dart抽象类主要用于定义标准,子类可以继承抽象类,也可以实现抽象类接口

  • 抽象类通过abstract 关键字来定义

  • Dart中的抽象方法不能用abstract声明,Dart中没有方法体的方法我们称为抽象方法。

  • 如果子类继承抽象类必须得实现里面的抽象方法

  • 如果把抽象类当做接口实现的话必须得实现抽象类里面定义的所有属性和方法。

  • 抽象类不能被实例化,只有继承它的子类可以

extends抽象类 和 implements的区别:

  • 如果要复用抽象类里面的方法(用抽象类里面的公共方法),并且要用抽象方法约束自类的话我们就用extends继承抽象类

  • 如果只是把抽象类当做标准的话我们就用implements实现抽象类(重写抽象类中的方法)

案例:定义1个Animal类并要求它的子类必须包含eat方法

  • 抽象类的抽象方法是没有方法体的
  eat(); // 抽象方法,没有方法体
  • 抽象类的子类必须实现抽象类中定义的所有抽象方法
void main() {
  Dog d = new Dog();
  d.eat(); // 小狗在吃骨头
  d.run(); // 小狗在跑
}

// 定义抽象类
abstract class Animal {
  eat(); // 抽象方法,没有方法体
  run();
}

class Dog extends Animal {
  @override
  eat() {
    print('小狗在吃骨头');
  }

  @override
  run() {
    print('小狗在跑');
  }
}

抽象类无法直接被实例化,只有继承它的子类可以被实例化:

var a = new Animal(); // ERROR

完整代码:抽象类中可以定义公共方法,比如prinInfo()

void main() {
  Dog d = new Dog();
  d.eat(); // 小狗在吃骨头
  d.run(); // 小狗在跑

  // 抽象类无法直接被实例化,只有继承它的子类可以被实例化
  // var a = new Animal(); // ERROR
  d.printInfo(); // 我是1个抽象类里面的普通方法
}

// 定义抽象类
abstract class Animal {
  eat(); // 抽象方法,没有方法体
  run();

  printInfo() {
    print('我是1个抽象类里面的普通方法');
  }
}

class Dog extends Animal {
  @override
  eat() {
    print('小狗在吃骨头');
  }

  @override
  run() {
    print('小狗在跑');
  }
}

多态

通俗来说,多态就是父类定义一个方法不去实现,让继承他的子类去实现,每个字类有不同的表现。

注意子类的实例赋值给父类的引用

void main() {
  Dog d = new Dog();
  d.eat(); // 小狗在吃骨头
  d.run(); // 小狗在跑

  Cat c = new Cat();
  c.eat(); // 小猫在吃鱼
  c.run(); // 小猫在跑

  // 子类的实例赋值给父类的引用
  Animal a = new Dog();
  a.eat(); // 小狗在吃骨头
  // a.run(); // ERROR
}

// 定义抽象类
abstract class Animal {
  eat(); // 抽象方法,没有方法体
}

class Dog extends Animal {
  @override
  eat() {
    print('小狗在吃骨头');
  }

  run() {
    print('小狗在跑');
  }
}

class Cat extends Animal {
  @override
  eat() {
    print('小猫在吃鱼');
  }

  run() {
    print('小猫在跑');
  }
}

接口

Dart中的接口:

  • Dart的接口没有interface关键字定义接口,而是普通类或抽象类都可以作为接口被实现。

  • 但是dart的接口有点奇怪,如果实现的类是普通类,会将普通类和抽象中的属性的方法全部需要覆写一遍。

  • 而因为抽象类可以定义抽象方法,普通类不可以,所以一般如果要实现像Java接口那样的方式,一般会使用抽象类。 建议使用抽象类定义接口。

案例:定义一个DB库 支持 mysql mssql mongodb,其中mysql mssql mongodb三个类里面都有同样的方法

如果在接口中定义了属性类型,继承接口的类中的方法也需要一致,比如add(String data)方法:

void main() {
  MySql mySql = new MySql('10.3.80.1');
  mySql.add('2021-06-24'); // 这是MySql的add方法 --- 10.3.80.1 --- 2021-06-24
}

// 定义1个抽象类,当作接口
abstract class Db {
  String uri; // 数据库链接地址
  // 当作接口,接口就是规范和约定
  add(String data);

  save();

  delete();
}

class MySql implements Db {
  @override
  String uri;

  // 构造函数
  MySql(this.uri);

  @override
  add(String data) {
    // 注意接口定义的参数属性
    print('这是MySql的add方法 --- ${this.uri} --- $data');
  }

  @override
  delete() {
    // TODO: implement delete
    throw UnimplementedError();
  }

  @override
  save() {
    // TODO: implement save
    throw UnimplementedError();
  }
}

class MsSql implements Db {
  @override
  String uri;

  // 构造函数
  MsSql(this.uri);

  @override
  add(String data) {
    print('这是MsSql的add方法');
  }

  @override
  delete() {
    // TODO: implement delete
    throw UnimplementedError();
  }

  @override
  save() {
    // TODO: implement save
    throw UnimplementedError();
  }
}

抽离

见整理的代码chapter11/demo04.dart


一个类实现多个接口以及Mixins(chapter12)

一个类实现多个接口

1个类实现多个接口,需要实现接口里面所有的属性和方法

void main() {
  C c = new C();
  c.printA(); // printA
  c.printB(); // printA
}

abstract class A {
  printA();
}

abstract class B {
  printB();
}

class C implements A, B {
  @override
  String name;

  @override
  printA() {
    print('printA');
  }

  @override
  printB() {
    print('printA');
  }
}


Mixins

在Dart中可以使用mixins实现类似多继承的功能

因为mixins的使用条件随着Dart版本一直在变,这里以Dart 2.x为例:

  • 作为mixins的类只能继承自Object,不能继承其他类
  • 作为mixins的类不能有构造函数
  • 1个类可以mixins多个mixins
  • mixins绝不是继承,也不是接口,而是一种全新的特性

Dart中无法实现多继承,但是可以实现多接口:

class C extends A, B {...} // EROOR
class C implements A, B {...} // OK

Mixins基本使用

可以使用mixins实现类似多继承的功能,注意mixins的类必须继承自Object(如下A类和B类),继承的C类可以访问A类和B类的属性以及方法,同时A类和B类不能有构造函数

void main() {
  C c = new C();
  c.printA(); // printA
  c.printB(); // printA
  print(c.info); // this is A
}

class A {
  String info = 'this is A';
  // A(this.info);  // ERROR
  void printA() {
    print('printA');
  }
}

class B {
  void printB() {
    print('printB');
  }
}

// C类mixins了A和B
class C with A, B {}


1个类可以mixins多个mixins类

作为mixins的类只能继承自Object,不能继承其他类

// C类不能mixins A类,因为A类继承了Person类不是Object
class A extends Person {
  String info = 'this is A';
  void printA() {
    print('printA');
  }
}

如下:C类既有Person类的功能,又有A类和B类的功能,注意因为Person类定义了默认构造函数,C类也要实现自己的构造函数:

void main() {
  C c = new C('张三', 20);
  c.printA(); // printA
  c.printB(); // printA
  c.printInfo(); // 张三 --- 20
}

class Person {
  String name;
  num age;

  Person(this.name, this.age);

  printInfo() {
    print('${this.name} --- ${this.age}');
  }
}

class A {
  String info = 'this is A';

  void printA() {
    print('printA');
  }
}

class B {
  void printB() {
    print('printB');
  }
}

// C类mixins了A和B,Person可以有构造函数,A,B不能有构造函数
// C类既有Person类的功能,又有A类和B类的功能
class C extends Person with A, B {
  C(String name, num age) : super(name, age);
}

如果Person类,A类和B类都有同样的方法,因为是extend Person with A,B,所以c.run()调用的是B类的run()方法:

void main() {
  C c = new C('张三', 20);
  c.printA(); // printA
  c.printB(); // printA
  c.printInfo(); // 张三 --- 20

  // Person类、A类和B类都有run()方法,但是因为是A,B,所有C类继承的是B.run()
  c.run(); // B is running!
}

class Person {
  String name;
  num age;

  Person(this.name, this.age);

  void run() {
    print('Person is running!');
  }

  printInfo() {
    print('${this.name} --- ${this.age}');
  }
}

class A {
  String info = 'this is A';

  void printA() {
    print('printA');
  }

  void run() {
    print("A is running!");
  }
}

class B {
  void printB() {
    print('printB');
  }

  void run() {
    print("B is running!");
  }
}

// C类mixins了A和B,Person可以有构造函数,A,B不能有构造函数
// C类既有Person类的功能,又有A类和B类的功能
class C extends Person with A, B {
  C(String name, num age) : super(name, age);
}


Mixins的类型

mixins的类型就是其超类的子类型

void main() {
  C c = new C();
  print(c is C); // true
  print(c is A); // true
  print(c is B); // true
  print(c is Object); // true
}

class A {
  String info = 'this is A';

  void printA() {
    print('printA');
  }
}

class B {
  void printB() {
    print('printB');
  }
}

class C with A, B {}


泛型、泛型方法、泛型类、泛型接口(chapter13)

泛型方法

泛型解决了类、接口方法的复用性,以及对不特定数据类型的支持(l类型校验)

不指定类型放弃了类型检查,我们现在想实现的是传入什么,返回什么,比如传入number类必须返回number,同时还支持类型校验

void main() {
  // 指定了传入类型
  print(getData<String>('你好')); // 你好

  print(getData<int>(123)); // 123
}

// 不指定类型放弃了类型检查
// getData(value) {
//   return value;
// }

// 泛型方法,指定了传入类型,也指定了返回类型
T getData<T>(T value) {
  return value;
}

也可以只对传入参数进行校验,对返回参数不校验

// 只对传入参数进行校验,对返回参数不校验
getData1<T>(T value) {
  return value;
}

泛型类

前情回顾,如何创建指定长度及指定类型的List,List就是1个泛型类

 List list = List.filled(2, '');
  list[0] = "张三";
  list[1] = "李四";
  print(list); // [张三, 李四]

  // 实例化List
  List list1 = new List.filled(2, "");
  list1[0] = "张云";
  list1[1] = "董磊";
  print(list1); // [张云, 董磊]

  // 指定List传入的数据必须是String类型
  List list2 = new List<String>.filled(2, "");
  list2[0] = "Ronaldo";
  list2[1] = "Messi";
  print(list2); // [Ronaldo, Messi]

目标:定义1个泛型类MyList,既可以增加int类型数据,也可以增加string类型数据,并且还可以实现类型的校验

实例化泛型类的时候如果不指定传入的类型,可以传入任意数据;如果指定了传入的类型,只能传入指定类型的数据

void main() {
  MyList list1 = new MyList<int>();
  list1..add(1)..add(2)..add(3);
  print(list1.getList()); // [1, 2, 3]

  // 没有指定传入MyList的类型
  MyList list2 = new MyList();
  list2..add('Ronaldo')..add('Messi')..add(1);
  print(list2.getList()); // [Ronaldo, Messi, 1]

  // 指定传入MyList的类型
  MyList list3 = new MyList<String>();
  // list3..add('张云')..add('董磊')..add(1); // ERROR
  list3..add('张云')..add('董磊')..add('成竹'); 
  print(list3.getList()); // [张云, 董磊, 成竹]
}

// 定义1个泛型类
class MyList<T> {
  //定义1个List属性
  List list = <T>[];

  void add(T value) {
    this.list.add(value);
  }

  List getList() {
    return this.list;
  }
}


泛型接口

定义1个泛型接口,可以对不特定数据类型进行校验

abstract class Cache<T> {
  getByKey(String key);

  void setByKey(String key, T value);
}

案例:

  • 实现数据缓存的功能:有文件缓存和内存缓存,内存缓存和文件缓存按照接口的约束实现
  • 定义1个泛型接口,约束实现它的子类必须有getByKey(key)setByKey(key,value)
  • 要求setByKey的时候的value的类型和实例化子类的时候指定的类型一致
void main() {
  MemoryCache m1 = new MemoryCache<String>();
  // m.setByKey('index', 123);  // ERROR
  m1.setByKey('index', '首页数据'); // 我是内存缓存,把<index 首页数据>的数据写入内存中

  MemoryCache m2 = new MemoryCache<Map>();
  Map person = {"name": '张三', "age": 30};
  m2.setByKey('index', person); // 我是内存缓存,把<index {name: 张三, age: 30}>的数据写入内存中
}

// 泛型接口
abstract class Cache<T> {
  getByKey(String key);

  void setByKey(String key, T value);
}

// 文件缓存类
class FileCache<T> implements Cache<T> {
  @override
  getByKey(String key) {
    return null;
  }

  @override
  void setByKey(String key, T value) {
    print('我是文件缓存,把<${key} ${value}>的数据写入文件中');
  }
}

// 内存缓存类
class MemoryCache<T> implements Cache<T> {
  @override
  getByKey(String key) {
    return null;
  }

  @override
  void setByKey(String key, T value) {
    print('我是内存缓存,把<${key} ${value}>的数据写入内存中');
  }
}


Dart中的库(chapter14)

概念

Dart中的库主要有三种:

  • 我们自定义的库:
import 'lib/xxx.dart';
  • 系统内置库:
import 'dart:math';    
import 'dart:io'; 
import 'dart:convert';

1: 需要在自己想项目根目录新建一个pubspec.yaml
2:在pubspec.yaml文件 然后配置名称 、描述、依赖等信息
3:然后运行 pub get 获取包下载到本地
4:项目中引入库 import 'package:http/http.dart' as http; 看文档使用


自定义的库

前面实现过,不再重复:

import 'lib/Animal.dart';

void main() {
  Animal a = new Animal('小猫', 2);
  a.printInfo(); // 小猫 --- 2
}


系统内置库

输入dart:查看内置库
在这里插入图片描述

案例:调用math库、convert库和io

import 'dart:io'; // 请求服务器API接口数据
import 'dart:math';
import 'dart:convert'; // utf8转换

void main() async {
  print(min(12, 13)); // 12
  print(max(99, 23)); // 99

  var result = await _getDataFromZhihuAPI();
  print(
      result); // {"date":"20210627","stories":[{"image_hue":"0x383d24","title":"小事 · 儿子想穿裙子上学","url":"https:\/\/daily.zhihu.com\/story\/9737523","hint":"VOL.1309","ga_prefix":"062707","images":["https:\/\/pic4.zhimg.com\/v2-f2c9574721d33846c66676cf832f11aa.jpg?source=8673f162"],"type":0,"id":9737523},{"image_hue":"0xb38359","title":"童年不够幸福的人,如何通过自己的努力获得前进的力量?","url":"https:\/\/daily.zhihu.com\/story\/9737481","hint":"深刻如此 · 3 分钟阅读","ga_prefix":"062707","images":["https:\/\/pic1.zhimg.com\/v2-4a5e71a560dbbd294f64f6f242700905.jpg?source=8673f162"],"type":0,"id":9737481},{"image_hue":"0x2f3d56","title":"经济学有逻辑基础吗?","url":"https:\/\/daily.zhihu.com\/story\/9737486","hint":"司马懿 · 2 分钟阅读","ga_prefix":"062707","images":["https:\/\/pic4.zhimg.com\/v2-f7d689b0331385e5086a9a2aaef76a0b.jpg?source=8673f162"],"type":0,"id":9737486},{"image_hue":"0xb3a74f","title":"为什么科幻电影不受奥斯卡青睐?","url":"https:\/\/daily.zhihu.com\/story\/9737494","hint":"托比章 · 2 分钟阅读","ga_prefix":"062707","images":["https:\/\/pic1.zhimg.com\/v2-bb6639b5defd8dfc4417d557a1e287b4.jpg?source=8673f162"],"type":0,"id":9737494},{"image_hue":"0x807159","title":"铁陨石的纹理真的无法人造吗?","url":"https:\/\/daily.zhihu.com\/story\/9737492","hint":"Nantan · 2 分钟阅读","ga_prefix":"062707","images":["https:\/\/pic4.zhimg.com\/v2-d9d82cc1de616a35bd8ab2ddff4d256e.jpg?source=8673f162"],"type":0,"id":9737492},{"image_hue":"0xb3b37d","title":"为什么国外的建筑绘图那么好看?","url":"https:\/\/daily.zhihu.com\/story\/9737476","hint":"知乎用户 · 6 分钟阅读","ga_prefix":"062707","images":["https:\/\/pic4.zhimg.com\/v2-907f45a34d24fc3ce560e9f2cb695d6f.jpg?source=8673f162"],"type":0,"id":9737476}],"top_stories":[{"image_hue":"0x3e352b","hint":"作者 \/ 钱程","url":"https:\/\/daily.zhihu.com\/story\/9737383","image":"https:\/\/pic3.zhimg.com\/v2-af6a10a666f66d9bbee63cf0c758d6d4.jpg?source=8673f162","title":"为什么「天舟二号」带上太空的菜是「鱼香肉丝」和「宫保鸡丁」?","ga_prefix":"062407","type":0,"id":9737383},{"image_hue":"0xb38e7d","hint":"作者 \/ 壹心理","url":"https:\/\/daily.zhihu.com\/story\/9737346","image":"https:\/\/pic3.zhimg.com\/v2-dde11347e95b2eb5955fee9a5ca6f722.jpg?source=8673f162","title":"这些年来,原生家庭的影响是否被过度夸大了?","ga_prefix":"062307","type":0,"id":9737346},{"image_hue":"0x4a3446","hint":"作者 \/ 死者代言人","url":"https:\/\/daily.zhihu.com\/story\/9737316","image":"https:\/\/pic1.zhimg.com\/v2-b6b76de47904327454882b23db260907.jpg?source=8673f162","title":"高温油炸过后的尸体还能检测出 DNA 是什么原理?","ga_prefix":"062207","type":0,"id":9737316},{"image_hue":"0x0f080b","hint":"作者 \/ Pierrot","url":"https:\/\/daily.zhihu.com\/story\/9737254","image":"https:\/\/pic1.zhimg.com\/v2-40ef1af1595338a42dd8b8058acfc3c8.jpg?source=8673f162","title":"想快速入坑王家卫,应该按什么顺序来看电影?","ga_prefix":"062107","type":0,"id":9737254},{"image_hue":"0xb3947d","hint":"作者 \/ 非专业工程师","url":"https:\/\/daily.zhihu.com\/story\/9737153","image":"https:\/\/pic2.zhimg.com\/v2-3552e0357650f416fb0322026ff11179.jpg?source=8673f162","title":"地铁里的风是哪里来的?","ga_prefix":"061807","type":0,"id":9737153}]}
}

_getDataFromZhihuAPI() async {
  // 1、创建HttpClient对象
  var httpClient = new HttpClient();
  // 2、创建Uri对象
  var uri = new Uri.http('news-at.zhihu.com', '/api/3/stories/latest');
// 3、发起请求,等待请求
  var request = await httpClient.getUrl(uri);
// 4、关闭请求,等待响应
  var response = await request.close();
// 5、解码响应的内容
  return await response.transform(utf8.decoder).join();
}

async和await

这两个关键字只需要注意两点:

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

async是让方法变成异步;await是等待异步方法执行完成

因为在main()方法中使用了await,所以main()方法也必须是async方法

void main() async {
  var result = await testAsync();
  print(result); // Hello async
}

// 异步方法
testAsync() async {
  return 'Hello async';
}


Pub包管理系统中的库

  • 首先修改配置文件pubspec.yaml
name: myDartStudy
description: A simple command-line application.
# version: 1.0.0
# homepage: https://www.example.com

#environment:
#  sdk: '>=2.8.1 <3.0.0'

#dependencies:
#  path: ^1.7.0

dev_dependencies:
  pedantic: ^1.9.0
  http: ^0.12.0+2
  date_format: ^1.0.6
  • 执行dart pub get下载
  • 最后看库的文档引入库使用

以引入http库为例,注意第三方库通过package:引入

// import 'dart:convert';  // 也可以
import 'dart:convert' as convert;
import 'package:http/http.dart' as http;

案例:使用http库和date_format

import 'dart:convert' as convert;
import 'package:http/http.dart' as http;
import 'package:date_format/date_format.dart';

void main() async {
  var url =
      "http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=1";

  // Await the http get response, then decode the json-formatted responce.
  var response = await http.get(url);
  if (response.statusCode == 200) {
    var jsonResponse = convert.jsonDecode(response.body);

    print(jsonResponse);
  } else {
    print("Request failed with status: ${response.statusCode}.");
  }

  print(formatDate(
      DateTime(1989, 2, 21), [yyyy, '-', mm, '-', dd])); // 1989-02-21
}


Dart中库的重命名以及冲突如何解决

假设Person1.dartPerson2.dart含有同名称的实例类Person,那么引入的时候通过as关键字进行区分:

import 'lib/Person1.dart' as lib1;
import 'lib/Person2.dart' as lib2;

void main() {
  var p1 = lib1.Person('张三', 20);
  p1.printInfo(); // Person1:张三 --- 20

  var p2 = lib2.Person('李四', 32);
  p2.printInfo(); // Person2:李四 --- 32
}

库中方法部分导入

使用show关键字显示部分功能:

import 'lib/myMath.dart' show getName, getAge;
// import 'lib/myMath.dart' hide getName;

void main() {
  getName(); // 张三
  getAge(); // 20
}

使用hide关键字隐藏部分功能:

// import 'lib/myMath.dart' show getName, getAge;
import 'lib/myMath.dart' hide getName;

void main() {
  // getName(); // ERROR
  getAge(); // 20
  getSex(); // 男
}

延迟加载

延迟加载也称为懒加载,可以在需要的时候再进行加载。 懒加载使用deferred as关键字来指定,如下例子所示:

   import 'package:deferred/hello.dart' deferred as hello;

    // 当需要使用的时候,需要使用loadLibrary()方法来加载:
    greet() async {
      await hello.loadLibrary();
      hello.printGreeting();
    }

Dart 2.13后的新特性(chapter15)

Flutter 2.2.0(2021年5月19日发布)之后的版本都要求使用null safety(空安全)

  • ?可空类型
  • !类型断言

可空类型

可空类型:

   int a = 123; // 非空的int类型
  // a = null;  // A value of type 'Null' can't be assigned to a variable of type 'int'.
  print(a); // 123

  String? userName = '张三'; // String?表示userName是1个可空类型
  userName = null;
  print(userName); // null

  // List<String> l1=['张三','李四','王五']; // l1是非空类型
  List<String>? l1 = ['张三', '李四', '王五']; // l1是可空类型
  l1 = null;
  print(l1); // null

在方法里返回可空类型:

void main() {
  print(getData('aa')); // this is server data
  print(getData(null)); // null
}

// getData()返回值是可空类型
String? getData(apiUrl) {
  if (apiUrl != null) {
    return "this is server data";
  }
  return null;
}

类型断言

基本概念:如果str不等于null则打印str长度;如果等于null会抛出异常

  String? str = 'this is str';
  print(str.length); // 11

  str = null;
  // print(str.length); // ERROR
  // 类型断言:如果str不等于null则打印str长度;如果等于null会抛出异常
  print(str!.length); // 11

!类型断言一般和try/catch配合使用,如下:

void main() {
  printLength('this is str'); // 11
  printLength(null); // str is null
}

void printLength(String? str) {
  // print(str!.length);
  // if (str != null) {
  //   print(str.length);
  // }
  try {
    print(str!.length);
  } catch (e) {
    print('str is null');
  }
}

late关键字

late关键字主要用于延迟初始化属性

案例:Person类没有构造函数,稍后初始化类成员属性

// late关键字
void main() {
  Person p = new Person();
  p.setName('张三', 20);
  print(p.getName()); // 张三 --- 20
}

class Person {
  // String name; // non-nullable instance field 'name' must be initialized.
  // in age;// non-nullable instance field 'name' must be initialized.

  late String name; // 稍后初始化name
  late int age; // 稍后初始化age

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

  String getName() {
    return '${this.name} --- ${this.age}';
  }
}


required关键字

基本使用

老版本Dart中使用@required注解代码,新斑斑中required已经作为内置修饰符,主要用于允许根据需要标记任何命名参数(函数或类),使得它们不为空。因为可选参数中必须有个required

之前定义命名参数的函数:sex有默认值”男“

String printUserInfo(String userName, {int age, String sex = '男'}) {
  if (age != null) {
    return '姓名:$userName --- 年龄:$age --- 性别:$sex';
  }
  return '姓名:$userName --- 年龄:保密 --- 性别:$sex';
}

使用required修饰的命名参数,必须有值传入,且不允许为null

void main() {
  // print(printUserInfo('张三')); // ERROR
  // print(printUserInfo('李四', age: null, sex: null)); // ERROR
  print(printUserInfo('张三', age: 20, sex: '男')); // 姓名:张三 --- 年龄:20 --- 性别:男
}

// 如果age和sex没有默认值,增加required关键字表示age和sex必须有初始值传入,不允许null
String printUserInfo(String userName, {required int age, required String sex}) {
  if (age != null) {
    return '姓名:$userName --- 年龄:$age --- 性别:$sex';
  }
  // age和sex必须传入
  return '姓名:$userName --- 年龄:${age} --- 性别:$sex';
}

如果sex和age可以为null,则必须使用之前的?类型断言

// required关键字
void main() {
  print(printUserInfo('李四',
      age: null, sex: null)); // 姓名:李四 --- 年龄:null --- 性别:null
  print(printUserInfo('张三', age: 20, sex: '男')); // 姓名:张三 --- 年龄:20 --- 性别:男
}

// 如果age和sex没有默认值,增加required关键字表示age和sex必须有初始值传入,不允许null
String printUserInfo(String userName,
    {required int? age, required String? sex}) {
  if (age != null) {
    return '姓名:$userName --- 年龄:$age --- 性别:$sex';
  }
  // age和sex必须传入
  return '姓名:$userName --- 年龄:${age} --- 性别:$sex';
}


类中使用

案例:nameage必须传入,且可以为null

void main() {
  Person p1 = new Person(name: '张三', age: 20);
  print(p1.getUserInfo()); // 张三 --- 20

  Person p2 = new Person(name: null, age: null);
  print(p2.getUserInfo()); // null --- null
}

class Person {
  String? name;  // 可空属性
  int? age;  // 可空属性

  // 命名参数的构造函数
  // 表示name和age是必须传入的命名参数
  Person({required this.name, required this.age});

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

  String getUserInfo() {
    return '${this.name} --- ${this.age}';
  }
}

案例:name必须传入,age可以传入也可以不传入:命名参数的构造函数,name必须传入加上required修饰符,age不用加,注意属性的类型断言

void main() {
  Person p1 = new Person(name: '张三', age: 20);
  print(p1.getUserInfo()); // 张三 --- 20

  Person p2 = new Person(name: '李四');
  print(p2.getUserInfo()); // 李四 --- null
}

class Person {
  String name;
  int? age; // 可空属性

  // 命名参数的构造函数,name必须传入加上required修饰符,age不用加,注意属性的类型断言
  Person({required this.name, this.age});

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

  String getUserInfo() {
    return '${this.name} --- ${this.age}';
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值