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
不能被赋值double
,double
可以被赋值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
String
转Number
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
}
Number
转String
// 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); // {香蕉, 苹果}
Set
转List
:
// 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
keys
、values
、isEmpty
和isNotEmpty
属性:
// 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()
使用for
和for-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);
}
List
和Maps
使用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中没有public
、private
、protected
等修饰符
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类中,setter
和getter
是属性,调用的时候要遵循属性的方式
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;
}
}
调用area
和areaHeight
: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
关键字来继承父类 - 子类会继承父类里面可见的属性和方法,但是不会继承构造函数
- 子类能复写父类的方法
getter
和setter
基本概念
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';
- Pub包管理系统中的库:
https://pub.dev/packages
https://pub.flutter-io.cn/packages
https://pub.dartlang.org/flutter/
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.dart
和Person2.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';
}
类中使用
案例:name
和age
必须传入,且可以为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}';
}
}