Dart诞生于2011年10月10日,是一种“结构化的Web编程”语言,应用于Web、服务器、移动应用等领域的开发。
1 概念
- Dart程序有统一的程序入口,即main()函数入口,与Java、C或C++语言相似。
// 入口方法
// main() {
// print("入口方法");
// }
// 表示main方法没有返回值
void main() {
// 打印信息
print('入口方法');
}
- 一切皆对象,所有对象都是类的实例,所有对象都继承自内置的Object类,类似于Java语言。
- Dart语言是弱数据类型,指定类型非必须,指定数据类型和编译时的常量,可提高运行速度。
- Dart没有public、protected和private的概念,私有特性通过变量或函数加上下划线来表示。
- Dart工具可以检查出warning警告信息和errors错误信息。
- 警告信息表明代码可能不工作,但是不会妨碍程序运行。
- 错误信息可能是编译时的错误,也可能是运行时的错误。
- 编译时的错误将阻止程序运行,运行时的错误会以exception异常方式呈现。
// 注释方法一
/* 注释方法二 */
///注释方法三
main() {
print('注释方法');
}
1-1 关键字
- 限制类保留字:await、yield。
- 上下文关键字:async、hide、on、show、sync。
- 保留字
- assert、break、case、default、class、throw、true、new、continue、var。
- switch、catch、else、extends、false、final、enum、for、null、finally、if。
- return、super、void、rethrow、while、const、this、try、with、do、is、in。
- 内置标识符
- abstract、get、covariant、deferred、dynamic、factory、export、mixin、as、Function。
- operator、set、interface、external、typedef、library、import、static、part、implements。
1-2 Dart中的库
- 自定义的库:
import 'lib/xxx.dart';
。 - 系统内置库:
import 'dart:convert';
。dart:io
:文件读写I/O相关操作的库。dart:svg
:事件和动画的矢量图支持。dart:core
:内建类型、对象及核心功能。dart:html
:网页开发使用到的库,使用频率高。dart:math
:数字常量及函数,提供随机数算法。dart:async
:异步编程支持,提供Future和Stream类。dart:convert
:不同类型之间的字符编码、解码支持。dart:collection
:对dart:core
内置库提供更多的集合支持。
- Pub包管理系统中的第三方库:https://pub.dev/。
import 'dart:math';
main() {
print('最小值:' + min(25, 27).toString());
print('最大值:' + max(25, 27).toString());
}
(1) 实现请求数据
import 'dart:io';
import 'dart:convert';
void main() async {
var result = await getDataFromAPI();
print(result);
}
// API接口:http://news-at.zhihu.com/api/3/stories/latest
getDataFromAPI() async {
// 创建HTTPClient对象
var httpClient = new HttpClient();
// 创建URI对象
var uri = new Uri.http('news-at.zhihu.com', '/api/3/stories/latest');
// 发起请求,等待响应
var request = await httpClient.getUrl(uri);
// 关闭请求,等待响应
var response = await request.close();
// 解码响应的内容
return await response.transform(utf8.decoder).join();
}
(2) async和await
- 只有async方法才能使用await关键字调用方法。
- 如果调用别的async方法必须使用await关键字。
- async是让方法变成异步,await是等待异步方法执行完成。
void main() async {
var result = await testAsync();
print(result);
}
// 异步方法
testAsync() async {
return '异步方法';
}
(3) 导入第三方库
- 项目根目录下新建一个
pubspec.yaml
文件,在文件中配置名称、描述、依赖等信息。 - 命令窗口进入
pubspec.yaml
文件路径下,输入dart pub get
回车获取包下载到本地。
name: xxx
description: pubtest
environment:
sdk: '>=2.10.0 <3.0.0'
dependencies:
http: ^0.13.5
date_format: ^2.0.7
- 项目中引入库需使用import命令,例如:
import 'package:http/http.dart' as http;
。
import 'dart:convert' as convert;
import 'package:http/http.dart' as http;
import 'package:date_format/date_format.dart';
main() async {
var url = Uri.http('news-at.zhihu.com', '/api/3/stories/latest');
// 等待HTTP响应,然后对Json格式的响应进行解码
var response = await http.get(url);
if (response.statusCode == 200) {
var jsonResponse = convert.jsonDecode(response.body);
print(jsonResponse);
} else {
print('请求失败,状态为:${response.statusCode}');
}
print(formatDate(DateTime(2023, 01, 10), [yyyy, '-', mm, '-', dd]));
}
(4) 库的冲突问题
- 创建一个
lib
库,该库目录中的Person1.dart
文件内容如下。
class Person {
String name;
int age;
// 默认构造函数的简写
Person(this.name, this.age);
Person.setInfo(String name, int age) {
this.name = name;
this.age = age;
}
void printInfo() {
print('Person1:${this.name}---${this.age}');
}
}
lib
库目录中的Person2.dart
文件内容如下。
class Person {
String name;
int age;
// 默认构造函数的简写
Person(this.name, this.age);
Person.setInfo(String name, int age) {
this.name = name;
this.age = age;
}
void printInfo() {
print('Person2:${this.name}---${this.age}');
}
}
Person1.dart
和Person2.dart
中都有Person类(重名了),重命名其中一个库。
import 'lib/Person1.dart';
import 'lib/Person2.dart' as lib;
main() {
Person p1 = new Person('张三', 21);
p1.printInfo();
lib.Person p2 = new lib.Person('李四', 22);
p2.printInfo();
}
(5) 引入部分功能
lib
库目录中新建一个MyMath.dart
文件,内容如下。
void getName() {
print('张三');
}
void getAge() {
print(21);
}
void getSex() {
print('男');
}
- 使用hide关键字隐藏库中不需要的部分,show关键字仅导入库中需要的部分。
// 隐藏不需要的部分,使用hide关键字
import 'lib/MyMath.dart' hide getAge;
// 只导入需要的部分,使用show关键字
import 'lib/MyMath.dart' show getName;
void main() {
getName();
// 已隐藏,会报错
// getAge();
}
(6) 延迟加载(懒加载)*
- 懒加载:可以在需要时再进行加载,最大好处即减少APP的启动时间,使用时用loadLibrary()方法加载。
- 使用deferred as关键字来指定,例如:
import 'package:deferred/hello.dart' deferred as hello;
。
// 好像没这个库,引入会报错
import 'package:deferred/hello.dart' deferred as hello;
greet() async {
await hello.loadLibrary();
hello.printGreeting();
}
1-3 变量与常量
- Dart是脚本类语言,可以不预先定义变量类型,它会自动推导类型。
- 可以使用var关键字来声明变量,也可以通过类型关键字来定义变量。
- 变量命名规则
- 变量名必须由数字、字母、下划线
_
和美元符$
组成。 - 标识符开头不能是数字,标识符不能是保留字和关键字。
- 变量名区分大小写,尽量见名知义,变量名用名词,方法名用动词。
- 变量名必须由数字、字母、下划线
main() {
// 使用var关键字定义变量
var str1 = 'var关键字定义变量';
print(str1);
var num1 = 123;
print('var关键字定义变量:$num1');
// 通过类型关键字定义变量
String str2 = '类型关键字定义变量';
print(str2);
int num2 = 12345;
print('类型关键字定义变量:$num2');
}
- 常量:定义的变量不变,使用final或const关键字定义。
- final是惰性初始化,即在运行时第一次使用前才初始化。
main() {
String str = '变量值1';
// 变量可以改变值
str = '变量值2';
print('定义变量:$str');
// const关键字定义常量
const PI1 = 3.14159;
// 常量不可以改变值,改变值将报错
// PI1 = 3.14159267;
print('const关键字定义常量:$PI1');
// final关键字定义常量
final PI2 = 3.14159;
// PI2 = 3.14159253;
print('final关键字定义常量:$PI2');
}
- final的值只能被设定一次,是一个运行时常量,const则是一个编译时常量。
main() {
// final关键字是运行时常量
final time1 = new DateTime.now();
print(time1);
// const关键字是编译时常量
// const time2 = new DateTime.now();
// print(time2);
}
2 运算符
- 位运算符:
&
(与)、|
(或)、^
(异或)、~expr
(一元位补码)、<<
(左移)、>>
(右移)。 - 算术运算符:
+
、-
、*
、/
、%
、~/
(返回一整数值的除法)、-expr
(一元减号即负号)。 - 关系运算符:
==
(等)、!=
(不等)、>
(大于)、<
(小于)、>=
(大于等于)、<=
(小于等于)。 - 赋值运算符:
=
、??=
、+=
、-=
、*=
、/=
、%=
、~/=
、<<=
、>>=
、&=
、^=
、|=
。 - 逻辑运算符:
!expr
(反转表达式)、||
(逻辑或)、&&
(逻辑与),只两个值。 - 级联运算符:
..
(可对同一对象执行系列操作,严格说级联符号并非运算符)。 - 条件表达式:
condition ? expr1 : expr2
(三元表达式)、expr1 ?? expr2
。 - 类型判定运算符:
is
(相应类型)、is!
(非相应类型)、as
(转换为指定类型)。
2-1 位运算符
^
(异或):两个输入相同时为0,不同为1。- 都是通过操作位的移动来达到运算的目的。
main() {
// 位运算符
final v = 0x77; // 16进制,10进制为119,2进制为0111 0111
final b = 0xee; // 16进制,10进制为238,2进制为1110 1110
print((v & b).toRadixString(2));
print((v | b).toRadixString(2));
print((v ^ b).toRadixString(2));
print((~ v).toRadixString(2));
print((v << 1).toRadixString(2));
print((v >> 2).toRadixString(2));
print((v &~ b).toRadixString(2)); // 与非&~,转为2进制
}
2-2 算术运算符
main() {
// 算术运算符
var a = 3;
var b = 6;
var c = 9;
print('加:' + (a + c).toString());
print('减:' + (a - b).toString());
print('乘:' + (a * b).toString());
print('除:' + (c / b).toString());
print('取余:' + (c % b).toString());
print('取整:' + (c ~/ b).toString());
var d = b - a;
print('赋值:' + d.toString());
}
- 还支持前缀递增
++var
、前缀递减--var
、后缀递增var++
、后缀递减var--
运算符。
main() {
var a = 3;
var x1, x2, y1, y2;
// 在x1获得值前自增a
x1 = ++a;
print('$a --- $x1');
// 在y1获得值后自增a
y1 = a++;
print('$a --- $y1');
// 在x2获得值前自减a
x2 = --a;
print('$a --- $x2');
// 在y2获得值后自减a
y2 = a--;
print('$a --- $y2');
}
2-3 关系运算符
- 测试两个对象是否表示相同的事务,使用
==
运算符。
main() {
// 关系运算符
int a = 5;
int b = 3;
print(a != b);
print(a > b);
print(a >= b);
print(a == b);
print(a < b);
print(a <= b);
// 用于条件判断
if(a > b) {
print('a大于b');
} else {
print('a小于b');
}
}
- 确定两个对象是否完全相同,则用
identical()
函数。
main() {
int a = 5;
// 判断对象是否完全相同
int b = 5;
print(identical(a, b));
}
2-4 赋值运算符
- 使用
=
运算符从右向左进行赋值操作,当变量为空时,可以使用??=
运算符进行赋值。
main() {
// 赋值运算符,从右向左
var v = 1;
print(v);
// 如果z为空,则将值分配给z,否则z保持不变
var z;
z ??= v;
print(z);
}
- 复合赋值运算符:
+=
、-=
、*=
、/=
、%=
、~/=
、<<=
、>>=
、&=
、^=
、|=
。
main() {
// 复合赋值运算符
var a = 12;
a += 10;
print(a);
var b = 4;
b *= 3;
print(b);
}
2-5 逻辑运算符
main() {
// 逻辑运算符
bool flag1 = true;
print(!flag1);
// 逻辑或||
bool flag2 = false;
bool flag3 = false;
print(flag2 || flag3);
// 逻辑与&&
print(flag1 && flag2);
}
- 逻辑运算符也可以反转布尔表达式,或组合布尔表达式。
main() {
// 组合表达式
int age = 21;
String sex = '男';
if(age == 23 && sex == '男') {
print('$age --- $sex');
} else {
print('不打印');
}
}
2-6 级联运算符
- 使用
..
表示,可对同一对象执行一系列操作,严格说级联的双点符号并非运算符。 - 只是Dart语法的一部分,类似于JavaScript中Promise的then处理,目的是简化代码。
class Person {
String name;
num age;
Person(this.name, this.age);
void printInfo() {
print('${this.name} --- ${this.age}');
}
}
main() {
Person p = new Person('张三', 21);
p.printInfo();
p.name = '李四';
p.age = 22;
p.printInfo();
p
..name = '王五'
..age = 23
..printInfo();
}
2-7 条件表达式
condition ? expr1 : expr2
:条件为真返回expr1,否则返回expr2(三元表达式)。
main() {
// if...else
bool flag1 = true;
if(flag1) {
print('正确');
} else {
print('错误');
}
// condition ? expr1 : expr2
var flag2 = true;
String result = flag2 ? '正确' : '错误';
print(result);
}
expr1 ?? expr2
:如果expr1为非空,返回expr1的值,否则计算并且返回expr2的值。
main() {
// expr1 ?? expr2
var x;
var y = x ?? 10;
print(y);
}
2-8 类型判定运算符
is
:当对象是相应类型时,返回true。is!
:对象不是相应类型时,返回true。as
:将一个对象转换为指定类型,前提是能够转换,转换之前可先用is进行判断。
main() {
// 类型判定运算符
var a = 123;
if(a is int) {
print('a是int类型');
} else {
print('a是其他类型');
}
}
3 数据类型
- 常用的数据类型
- 数字(Number)
int
关键字(整型)double
关键字(浮点型)
- 布尔(Boolean):
bool
关键字。 - 字典(Map):键和值可以是任何类型的对象。
- 数组(List):Dart中是列表对象,大多数人称之为列表。
- 集合(Set):Dart中Set是一个元素唯一并且无序的集合。
- 字符串(String):
String
关键字,开发过程中大量使用。
- 数字(Number)
- 不常用的数据类型
- Runes:表示字符串中的UTF-32编码字符。
- Symbol:表示在Dart程序中声明的运算符或标识符。
3-1 数字
- int整型和double浮点型,都是Number类型的子类,int类型不能包含小数点。
- 包含
+
、-
、*
、/
、%
以及位移操作等,常用的方法有abs、ceil和floor。
main() {
// int,必须是整型
int num1 = 123;
print('整型:$num1');
// double,既可以是整型,也可以是浮点型
double num2 = 123;
double num3 = 123.5;
print('浮点型:$num2');
print('浮点型:$num3');
// 运算符操作
var sum = num1 + num2;
print('运算符操作:$sum');
}
3-2 布尔
- 只有字面量true和false是布尔类型,这两个对象都是编译时常量。
main() {
// 布尔类型
bool flag1 = true;
print('布尔类型:$flag1');
bool flag2 = false;
print('布尔类型:$flag2');
}
- Dart是强布尔类型检查,只有bool类型的值是true才被认为是true。
main() {
// 条件判断语句
var flag = true;
if(flag) {
print('布尔类型:true');
} else {
print('布尔类型:false');
}
var a = 123;
var b = 456;
if(a == b) {
print('条件判断:a == b');
} else {
print('条件判断:a != b');
}
}
3-3 字典
- 将key和value关联在一起,也就是键值对,且key必须是唯一的。
main() {
// Map定义方式一
var person1 = {'姓名': '张三', '年龄': 21, '工作': '程序员'};
print('姓名:${person1['姓名']}');
print('方式一:$person1');
// Map定义方式二:构造函数Map()
var person2 = new Map();
person2['姓名'] = '李四';
person2['年龄'] = 22;
person2['工作'] = '设计师';
print('方式二:$person2');
}
- 可以为Map对象添加新的键值对,通过length获取键值对的数量。
main() {
var person = {'姓名': '张三', '年龄': 21};
// 添加新的键值对
person['性别'] = '男';
print(person);
// 使用length获取键值对的数量
print('length获取到的person键值对数量:${person.length}');
}
- 常用属性:keys(获取key值)、values(获取value值)、isEmpty、isNotEmpty。
main() {
// 常用属性
Map person = {'姓名': '张三', '年龄': 21, '性别': '男'};
print('键:${person.keys}');
print('值:${person.values}');
print('是否为空:${person.isEmpty}');
print('是否不空:${person.isNotEmpty}');
}
- 常用方法:remove()、addAll()(映射拼接)、containsValue(查看映射内的值)。
main() {
// 常用方法
Map person = {'姓名': '张三', '年龄': 21};
person.addAll({'性别': '男'});
print(person);
person.remove('性别');
print(person);
print('是否包含“张三”:${person.containsValue('张三')}');
}
3-4 数组
- 具有一系列相同类型的数据,类似于JavaScript的Array数组对象。
main() {
// List定义方式一
var l1 = ['张三', 21, true];
print('List定义方式一:$l1');
print('\tList的长度:${l1.length}');
print('\tList的位置:${l1[0]}');
// List定义方式二:指定类型
var l2 = <int>[21, 23, 25, 27, 29];
var l3 = <String>['张三', '李四', '王五', '赵六'];
print('List定义方式二:$l2');
print('List定义方式三:$l3');
// List定义方式三:增加数据
var l4 = [];
print('List定义方式三:$l4');
print('\tList的长度:${l4.length}');
l4.add('孙七');
l4.add('周八');
l4.add('吴九');
l4.add('郑十');
l4.add(30);
print('List定义方式三:$l4');
print('\tList的长度:${l4.length}');
// List定义方式四:新版中已去除,在Android Studio中仍然可用
var l5 = new List();
l5.add('张三');
l5.add('李四');
l5.add('王五');
l5.add('赵六');
l5.add('孙七');
print('List定义方式四:$l5');
}
- List对象的第一个元素索引是0,最后一个元素索引是list.length-1。
main() {
// 创建一个固定长度的集合
var l1 = List.filled(3, '');
print('固定长度的集合:$l1');
l1[0] = '张三';
l1[1] = '李四';
l1[2] = '王五';
print('固定长度的集合:$l1');
// 已固定长度,添加Unhandled exception报错
// l1.add('赵六');
// print('固定长度的集合:$l1');
// 也无法通过修改长度进行改变
// l1.length = 5;
// 该定义方式可以改变集合的长度
var l2 = ['孙七', '周八'];
l2.length = 0;
print('改变集合的长度:$l2');
var l3 = List<String>.filled(3, '');
l3[0] = '字典';
l3[1] = '布尔';
l3[2] = '数组';
print('改变集合的长度:$l3');
}
- 常用属性:length(长度)、reversed(翻转)、isEmpty(为空)、isNotEmpty(不为空)。
main() {
// 常见属性
var list = ['苹果', '西瓜'];
list.add('葡萄');
print('列表:$list');
print('长度:${list.length}');
print('不空:${list.isNotEmpty}');
print('为空:${list.isEmpty}');
// 翻转后再转换成List
print('列表:${list.reversed}');
}
- 常用方法
- add(增加)、addAll(拼接数组)、remove(删除)、removeAt(删除)、fillRange(修改)。
- indexOf:查找,若查找不到则返回-1。
insert(index, value)
:指定位置插入。insertAll(index, list)
:指定位置插入List。join()
:List转换成字符串。split()
:字符串转换成List。toList()
:其他类型转换成List。
main() {
// 常用方法
List l1 = ['桃子'];
l1.add('香蕉');
l1.addAll(['葡萄', '樱桃']);
print('列表:$l1');
// 查找不到则返回-1
print('查找:${l1.indexOf('苹果')}');
l1.remove('葡萄');
print('删除“葡萄”:$l1');
l1.removeAt(1);
print('删除“香蕉”:$l1');
// 将位置(1-2]之间的数据改为'西瓜'
l1.fillRange(1, 2, '西瓜');
print('替换“樱桃”:$l1');
l1.insert(1, '杏子');
print('插入:$l1');
l1.insertAll(1, ['苹果', '龙眼']);
print('插入:$l1');
var str = l1.join('、');
print('转换:$str');
var l2 = str.split('、');
print('转换:$l2');
}
3-5 集合
- 最主要的功能就是去除数组中存在的重复内容。
- 无顺序且不能重复的集合,不能通过索引获取值。
main() {
// 给数组去重
var s = new Set();
s.add('苹果');
s.add('苹果');
s.add('苹果');
print(s);
print(s.toList());
}
3-6 字符串
- 字符串类型,使用单引号、双引号或三引号定义,开发中大量使用。
main() {
// 字符串定义的几种方式
var str1 = '字符串定义方式一';
var str2 = "字符串定义方式二";
print(str1);
print('$str2\n');
String str3 = '字符串定义方式三';
String str4 = "字符串定义方式四";
print(str3);
print('$str4\n');
String str5 = '''
字符串定义方式五
字符串定义方式五
''';
String str6 = """
字符串定义方式六
字符串定义方式六
""";
print(str5);
print(str6);
}
- 通过两种方式进行字符串的拼接:使用
$
符号,或+
符号进行操作。
main() {
// 字符串的拼接
String str1 = '你好';
String str2 = '张三';
print('拼接一:$str1,$str2');
print('拼接二:' + str1 + ',' + str2);
}
3-7 数据类型判断
- 使用is关键字来判断数据类型。
main() {
// is关键字判断数据类型
var str = '12345';
if(str is String) {
print('String类型');
} else if(str is int) {
print('int类型');
} else {
print('除String和int的其他类型');
}
}
3-8 数据类型转换
- 使用
parse()
将String类型转换成Number类型,toString()
将Number类型转换成String类型。
main() {
// String转Number:parse()
String str1 = '123';
var myNum1 = int.parse(str1);
print(myNum1 is int);
String str2 = '123.3';
var myNum2 = double.parse(str2);
print(myNum2 is double);
// Number转String:toString()
var myNum3 = 123;
var str3 = myNum3.toString();
print(str3 is String);
}
- 为了防止转换过程中报错,可以使用try…catch语句。
main() {
// 防止报错,使用try...catch语句
String price = '';
try {
var myNum = double.parse(price);
print(myNum);
print(myNum is double);
} catch(err) {
print(0);
}
}
- isEmpty可以用来判断字符串是否为空。
main() {
// 其他类型转Boolean类型
var str = 'Hi~';
if(str.isEmpty) {
print('为空');
} else {
print('不空');
}
var myNum1;
if(myNum1 == null) {
print('为空');
} else {
print('不空');
}
var myNum2 = 0/0;
if(myNum2.isNaN) {
print('非数');
} else {
print(myNum2);
}
}
4 流程语句
if...else...
语句、switch...case
语句。for
循环、while
循环、do...while
循环。break
跳出一层循环、continue
跳出本次循环。forEach
、map
、where
、any
、every
循环。
4-1 if语句
main() {
// if...else...语句
String today = '星期一';
if(today == '星期一') {
print('今天星期一');
} else {
print('美好的一天');
}
}
4-2 switch
main() {
// switch...case语句
String today = '星期一';
switch(today) {
case '星期一':
print('今天星期一');
break;
default:
print('美好的一天');
break;
}
}
4-3 for和while
- for循环、while循环、do…while循环。
- 第一次循环条件不成立的情况,while循环不执行,do…while循环执行一次。
main() {
// for循环
for(int x=1; x<=3; x++) {
print('for循环:$x');
}
// while循环
int y = 1;
while(y <= 3) {
print('while循环:$y');
y++;
}
// do...while循环
int z = 1;
int sum = 0;
do {
sum += z;
z++;
} while(z <= 3);
print('do...while循环的和:$sum');
}
4-4 跳出一层循环
- break用于跳出循环,如果有多层循环,则只能跳出一层循环。
main() {
// break跳出循环
for(var i=1; i<=5; i++) {
if(i == 4) {
break;
}
print(i);
}
}
4-5 跳出本次循环
- continue用来跳出本次循环。
main() {
// continue跳出本次循环
for(var i=1; i<=3; i++) {
if(i == 2) {
continue;
}
print(i);
}
}
4-6 其他循环语句
- forEach:循环遍历List。
main() {
List mylist = ['樱桃', '西瓜', '香蕉'];
for(var i=0; i<mylist.length; i++) {
print('方法一:${mylist[i]}');
}
for(var item in mylist) {
print('方法二:$item');
}
// forEach
mylist.forEach((value) {
print('forEach:$value');
});
}
- map:用于修改集合中的数据。
main() {
// map
List mylist = [1, 2, 3];
var doubleList = mylist.map((value) {
return value * 2;
});
print(doubleList.toList());
}
- where:返回满足条件的数据。
main() {
// where
List mylist = [1, 3, 5, 7, 9];
var newList = mylist.where((value) {
return value >= 5;
});
print(newList.toList());
}
- any:只要集合中有满足条件的数据就返回true。
main() {
// any
List mylist = [1, 3, 5, 7, 9];
var anyList = mylist.any((value) {
return value >= 5;
});
print(anyList);
}
- every:集合中的每个数据都满足条件才返回true,否则返回false。
main() {
// every
List mylist = [1, 3, 5, 7, 9];
var everyList = mylist.every((value) {
return value >= 5;
});
print(everyList);
// 循环方法同样适用于set
var s = new Set();
s.addAll([11111, 22222, 33333]);
s.forEach((value) => print(value));
// 循环方法同样适用于map
var person = {'姓名': '张三', '工作': '测试'};
person.forEach((key, value) {
print('$key --- $value');
});
}
5 函数说明
- 函数即方法,内置的方法,例如:
print()
。 - 自定义方法,格式:
返回类型 方法名称(参数1, 参数2, ..., 参数n) { }
。
// 全局作用域
void printInfo() {
print('全局作用域');
printGlobal() {
print('局部作用域');
}
// 局部作用域只在局部调用
printGlobal();
}
void main() {
print('内置方法');
// 调用方法
printInfo();
// 全局作用域
int getNum() {
var myNum = 12345678;
return myNum;
}
// 调用方法
var n = getNum();
print(n);
}
5-1 参数
main() {
// 定义一个方法,求1到这个数的所有数之和
int sumNum(int n) {
var sum = 0;
for(var i=1; i<=n; i++) {
sum += i;
}
return sum;
}
var n = sumNum(10);
print(n);
// 定义一个方法,然后打印用户信息
// 方法传参:形参
String printUserInfo(String username, int age) {
return '姓名:$username --- 年龄:$age';
}
// 方法传参:实参
print(printUserInfo('张三', 21));
}
(1) 可选参数
main() {
// 定义一个方法,该方法带可选参数:[Data type Variable]
String printUserInfo(String username, [int age]) {
if(age != null) {
return '姓名:$username --- 年龄:$age';
}
return '姓名:$username --- 年龄:保密';
}
// 方法传参:实参
print(printUserInfo('张三', 21));
}
(2) 默认参数
main() {
// 定义一个方法,该方法带默认参数:[Data type Variable = 'Default']
String printUserInfo(String username, [int age = 21]) {
if(age != null) {
return '姓名:$username --- 年龄:$age';
}
return '姓名:$username --- 年龄:保密';
}
// 方法传参:实参
print(printUserInfo('张三'));
print(printUserInfo('李四', 22));
}
(3) 命名参数
main() {
// 定义一个方法,该方法带命名参数:{Data type Variable = 'Default'}
String printUserInfo(String username, {int age}) {
if(age != null) {
return '姓名:$username --- 年龄:$age';
}
return '姓名:$username --- 年龄:保密';
}
// 方法传参:实参
print(printUserInfo('张三', age: 21));
// 实现一个方法,该方法将另一个方法当作参数传入
fn1() {
print('将fn1作为一个参数传入fn2中');
}
fn2(fName) {
fName();
}
fn2(fn1);
}
5-2 箭头函数
main() {
// 箭头函数:forEach打印List里的数据
List list = ['樱桃', '香蕉', '苹果'];
list.forEach((element) => print(element));
list.forEach((element) => {print(element)});
// 修改List里的数据,让数组中大于2的值乘以2
List listNum = [4, 1, 2, 3, 4];
var newList = listNum.map((e) {
if(e > 2) {
return e * 2;
}
return e;
});
print(newList.toList());
var newListNum = listNum.map((e) => e > 2 ? e * 2 : e);
print(newListNum.toList());
}
5-3 相互调用
main() {
// 定义一个方法,使用isEvenNumber判断一个数是否为偶数
bool isEvenNumber(int n) {
if(n % 2 == 0) {
return true;
}
return false;
}
// 定义一个方法,打印1-n以内的所有偶数
printNum(int n) {
for(var i=1; i<=n; i++) {
if(isEvenNumber(i)) {
print(i);
}
}
}
// 函数的相互调用
printNum(9);
}
5-4 匿名函数
main() {
// 匿名函数
var printNumber = (){
print(123);
};
printNumber();
}
5-5 方法递归
main() {
// 方法递归:条件满足的情况下在方法内调用自身,求1-100的和
var sum = 0;
fn(int n) {
sum += n;
if(n == 1) {
return;
}
fn(n - 1);
}
fn(100);
print(sum);
}
5-6 闭包操作
- 全局变量特点:常驻内存,污染全局。
- 局部变量特点:不常驻内存,支持垃圾机制回收,不污染全局。
- 要想实现常驻内存,又不会污染全局,闭包可以解决这些问题。
- 写法:函数嵌套函数,并return里面的函数,这样就形成了闭包。
- 说明:内部函数调用外部函数的变量或参数,且这些变量或参数不会被系统回收。
main() {
// 闭包
fn() {
var a = 123;
return() {
a++;
print(a);
};
}
var b = fn();
b();
b();
b();
}
5-7 自执行方法
main() {
// 自执行方法:即不需要调用,自动执行的方法
(() {
print('自执行方法一');
})();
((String n) {
print(n);
})('自执行方法二');
}
6 面向对象
- 面向对象编程OOP的三个基本特征
- 封装:把客观事物封装成抽象的类,并把自己的部分属性和方法提供给其他对象。
- 继承:可使用现有类的功能,并在无需重新编写原来类的情况下对功能进行扩展。
- 多态:允许将子类类型的指针赋值给父类类型的指针,同一个函数调用会有不同的执行效果。
6-1 类的创建
- Dart中一切皆对象,所有的对象都继承自Object类,一个类通常由属性和方法组成。
// Person类的创建
class Person {
// 类的属性
String name = '张三';
int age = 21;
// 类的方法
void getInfo() {
print('$name --- $age');
print('${this.name} --- ${this.age}');
}
void setInfo(int age) {
this.age = age;
}
}
void main() {
// 实例化Person类
var p1 = new Person();
// 调用属性
print(p1.name);
// 调用方法
p1.getInfo();
Person p2 = new Person();
// print(p2.name);
p2.setInfo(22);
p2.getInfo();
}
6-2 构造函数
- Dart中的4种构造函数
- 普通构造函数:
ClassName(...)
,分为无参构造函数和有参构造函数。 - 命名构造函数:
ClassName.identifier(...)
,支持一个类实现多个构造函数,不能重载。 - 常量构造函数:
const ClassName(...)
,如果生成类的对象不变,可以定义常量构造函数。 - 工厂构造函数:
factory ClassName(...)
,factory关键字可以放在类名函数和命名函数前。
(1) 普通构造函数
- 如果不声明构造函数,则Dart会提供一个默认的无参构造函数。
- 若自定义有参构造函数,那么默认的无参构造函数就不存在了。
class Person {
String name = '张三';
int age = 21;
// 构造函数,该方法在实例化时触发
Person() {
print('构造函数');
}
void printInfo() {
print('${this.name} --- ${this.age}');
}
}
void main() {
// 实例化
Person p = new Person();
// p.printInfo();
}
- 实现的功能:在构造函数中,可以动态地给类指定属性。
class Person {
// 有参构造函数,提供语法糖简化
String name;
int age;
// 自定义构造函数
// Person(String name, int age) {
// this.name = name;
// this.age = age;
// }
// 构造函数的简写
Person(this.name, this.age);
void printInfo() {
print('${this.name} --- ${this.age}');
}
}
void main() {
// 实例化
Person p1 = new Person('张三', 21);
p1.printInfo();
Person p2 = new Person('李四', 22);
p2.printInfo();
}
(2) 命名构造函数
class Person {
String name;
int age;
// 命名构造函数
Person.now() {
print('命名构造函数');
}
Person.setInfo(String name, int age) {
this.name = name;
this.age = age;
}
void printInfo() {
print('${this.name} --- ${this.age}');
}
}
void main() {
// 实例化DateTime调用它的命名构造函数
var d = new DateTime.now();
print(d);
Person p1 = new Person.now();
Person p2 = new Person.setInfo('张三', 21);
p2.printInfo();
}
6-3 类模块化
- 将创建的类放在单独的文件夹下,例如:Person类,放在/lib/Person.dart中。
class Person {
String name;
int age;
Person(this.name, this.age);
Person.now() {
print('命名构造函数');
}
Person.setInfo(String name, int age) {
this.name = name;
this.age = age;
}
void printInfo() {
print('${this.name} --- ${this.age}');
}
}
- 在另一个文件中调用该Person类,则需要导入该类的类文件名。
import './lib/Person.dart';
void main() {
Person p = new Person.setInfo('张三', 21);
p.printInfo();
}
(1) 私有属性
- Dart中没有public、private protected等访问修饰符,但可使用
_
将属性和方法私有化。 - 自定义私有属性,或自定义私有方法时,必须将其单独抽类到一个文件中,这样才有效果。
- 例如:Animal类,定义name为私有属性,将创建的Animal类放在/lib/AnimalProperties.dart中。
class Animal {
// 私有属性name
String _name;
int age;
Animal(this._name, this.age);
void printInfo() {
print('${this._name} --- ${this.age}');
}
// 公有方法
String getName() {
// 私有属性name只在当前类中使用
return this._name;
}
}
- name私有属性只在当前的Animal类中使用,此时main()函数无法访问Animal类中的私有属性name。
- name私有属性在Animal类的getName()公有方法中,可通过访问公有方法间接地使用私有属性name。
import './lib/AnimalProperties.dart';
void main() {
Animal a = new Animal('小狗', 3);
// 无法访问私有属性
// print(a._name);
print(a.age);
// 访问公有方法来访问私有属性
print(a.getName());
}
(2) 私有方法
- 例如:Animal类,定义run()为私有方法,将创建的Animal类放在/lib/AnimalMethods.dart中。
class Animal {
String name;
int age;
Animal(this.name, this.age);
// 私有方法
void _run() {
print('私有方法');
}
// 公有方法
execRun() {
// 类里面方法的相互调用
this._run();
}
}
- run()私有方法在Animal类的execRun()公有方法中,可通过访问公有方法间接地访问私有方法run()。
import './lib/AnimalMethods.dart';
void main() {
Animal a = new Animal('小狗', 3);
// 间接地调用私有方法
a.execRun();
}
(3) getter与setter
class Rect {
num height;
num width;
Rect(this.height, this.width);
get area {
return this.height * this.width;
}
set areaHeight(value) {
this.height = value;
}
}
void main() {
Rect r = new Rect(10, 3);
// 通过访问属性来访问area
print('面积:${r.area}');
r.areaHeight = 6;
print('面积:${r.area}');
}
(4) 类的初始化列表
- 可以在构造函数体运行之前初始化实例变量。
class Rect {
int height;
int width;
// 初始化实例变量
Rect(): height = 30, width = 10 {
print('长为:${this.height}\n宽为:${this.width}');
}
getArea() {
return this.height * this.width;
}
}
void main() {
Rect r = new Rect();
print('面积:${r.getArea()}');
}
(5) 类中的静态成员
- 使用static关键字来实现类级别的变量和函数。
- 静态属性与静态方法直接通过类名访问,而非类的实例访问。
class Person {
static String name = '张三';
static void show() {
print(name);
}
}
main() {
// var p = new Person();
// p.show();
print(Person.name);
// 调用静态方法
Person.show();
}
- 非静态方法可以访问静态成员、非静态成员,以及静态方法。
- 静态方法可以访问静态成员和静态方法,不能访问非静态成员和非静态方法。
class Person {
static String name = '张三';
int age = 21;
static void show() {
print(name);
}
void printInfo() {
// 访问非静态属性
print(this.age);
// 访问静态属性
print(name);
// 调用静态方法
show();
}
static void printUserInfo() {
// 访问静态属性
print(name);
// 调用静态方法
show();
// 无法访问非静态属性
// print(age);
// print(this.age);
// 无法访问非静态方法
// printInfo();
// this.printInfo();
}
}
main() {
Person p = new Person();
p.printInfo();
Person.printUserInfo();
}
6-4 类的继承
- 子类使用extends关键词来继承父类,子类能复写父类的方法。
- 子类会继承父类里面可见的属性和方法,但不会继承构造函数。
class Person {
String name = '张三';
num age = 21;
void printInfo() {
print('${this.name} --- ${this.age}');
}
}
// 可以访问Person中共有的属性和方法
class Web extends Person {}
main() {
// 实例化Web类
Web w = new Web();
print(w.name);
w.printInfo();
}
(1) super关键词
class Person {
String name;
num age;
Person(this.name, this.age);
void printInfo() {
print('${this.name} --- ${this.age}');
}
}
class Web extends Person {
// super表示实例化子类时,将传过来的name和age参数赋值给父类Person
Web(String name, num age): super(name, age) {}
}
main() {
Web w = new Web('张三', 21);
w.printInfo();
}
(2) 子类扩展属性和方法
class Person {
String name;
num age;
Person(this.name, this.age);
void printInfo() {
print('${this.name} --- ${this.age}');
}
}
class Web extends Person {
// 定义子类自己的属性
String sex;
Web(String name, num age, String sex): super(name, age) {
this.sex = sex;
}
// 定义子类自己的方法
run() {
print('${this.name} --- ${this.age} --- ${this.sex}');
}
}
main() {
Web w = new Web('张三', 21, '男');
w.printInfo();
w.run();
}
(3) 给命名构造函数传参
class Person {
String name;
num age;
Person(this.name, this.age);
// 命名构造函数
Person.construct(this.name, this.age);
void printInfo() {
print('${this.name} --- ${this.age}');
}
}
class Web extends Person {
String sex;
Web(String name, num age, String sex): super.construct(name, age) {
this.sex = sex;
}
run() {
print('${this.name} --- ${this.age} --- ${this.sex}');
}
}
main() {
Web w = new Web('张三', 21, '男');
w.printInfo();
w.run();
}
(4) 子类覆写父类的方法
class Person {
String name;
num age;
Person(this.name, this.age);
void printInfo() {
print('${this.name} --- ${this.age}');
}
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}');
}
work() {
print('${this.name}的日常工作任务是写代码');
}
}
main() {
// 实例化子类
Web w = new Web('张三', 21);
w.printInfo();
w.work();
}
(5) 子类中调用父类方法
class Person {
String name;
num age;
Person(this.name, this.age);
void printInfo() {
print('${this.name} --- ${this.age}');
}
work() {
print('${this.name}在工作');
}
}
class Web extends Person {
Web(String name, num age): super(name, age);
// 子类中扩展方法
run() {
print('程序执行中');
// 子类调用父类方法
super.work();
}
void printInfo() {
print('姓名:${this.name} --- 年龄:${this.age}');
}
}
main() {
// 实例化子类
Web w = new Web('张三', 21);
w.printInfo();
w.run();
}
6-5 抽象类与泛型
- 抽象类主要通过abstract关键词用于定义标准,子类可继承抽象类,也可实现抽象类接口。
- 抽象类不能被实例化,只有继承它的子类可以。
- 如果子类继承抽象类,必须实现它的抽象方法。
- 没有方法体的方法称之为抽象方法,不能用abstract关键词来声明。
- 若把抽象类当接口实现,须先实现抽象类中定义的所有属性和方法。
- extends抽象类和implements的区别
- 如果只是把抽象类当做标准,那么就用implements关键词来实现抽象类。
- 若要复用抽象类中的方法且用抽象方法约束子类,用extends继承抽象类。
// 定义一个Animal类,要求它的子类必须包含eat方法
abstract class Animal {
// 抽象方法
eat();
// 非抽象方法
printInfo() {
print('抽象类中的普通方法');
}
}
class Dog extends Animal {
eat() {
print('小狗在吃美味的骨头');
}
}
main() {
Dog d = new Dog();
d.eat();
d.printInfo();
// 抽象类没法直接被实例化,只有继承它的子类才能被实例化
// var a = new Animal();
}
(1) 多态
- 多态就是父类定义一个方法不去实现,让继承它的子类去实现,每个子类有不同的表现。
abstract class Animal {
// 抽象方法
eat();
}
class Dog extends Animal {
eat() {
print('小狗在吃美味的骨头');
}
}
class Cat extends Animal {
eat() {
print('小猫在吃可爱的老鼠');
}
}
main() {
Dog d = new Dog();
d.eat();
Cat c = new Cat();
c.eat();
}
- 允许将子类类型的指针赋值给父类类型的指针,同一个函数调用会有不同的执行效果。
abstract class Animal {
// 抽象方法
eat();
}
class Dog extends Animal {
eat() {
print('小狗在吃美味的骨头');
}
run() {
print('程序正在执行过程中');
}
}
class Cat extends Animal {
eat() {
print('小猫在吃可爱的老鼠');
}
run() {
print('程序正在执行过程中');
}
}
main() {
// 子类的实例赋值给父类的引用
Animal d = new Dog();
d.eat();
Animal c = new Cat();
c.eat();
}
(2) 接口
- Dart的接口没用interface关键字定义,普通类或抽象类都可作为接口,使用implements关键字实现。
- 如果实现接口的类是普通类,那么Dart会将普通类和抽象类里面属性的方法全部覆写一遍。
- 抽象类可定义抽象方法,普通类不可,要实现类似于Java接口的方式,一般都会用抽象类。
- 建议使用抽象类定义接口。
// 接口,即约定或叫规范,定义一个DB库,支持MySQL
abstract class DB {
// 数据库的链接地址
String uri;
add(String data);
edit();
delete();
}
class MySQL implements DB {
String uri;
MySQL(this.uri);
add(String data) {
print('这是MySQL的add方法:$data');
}
edit() {
return null;
}
delete() {
return null;
}
}
main() {
MySQL mysql = new MySQL('127.0.0.1');
mysql.add('123456789');
}
- 抽类步骤一:lib目录新建一个DB.dart文件。
// 接口,即约定或叫规范,DB.dart文件
abstract class DB {
// 数据库的链接地址
String uri;
add(String data);
edit();
delete();
}
- 抽类步骤二:lib目录新建一个MySQL.dart文件,引入DB.dart文件。
import 'DB.dart';
// MySQL.dart文件
class MySQL implements DB {
String uri;
MySQL(this.uri);
add(String data) {
print('这是MySQL的add方法:$data');
}
edit() {
return null;
}
delete() {
return null;
}
}
- 抽类步骤三:创建一个Test.dart文件,用于引入MySQL.dart文件。
import 'lib/MySQL.dart';
// Test.dart文件
main() {
MySQL mysql = new MySQL('127.0.0.1');
mysql.add('123456789');
}
(3) 多接口
// 一个类实现多个接口
abstract class A {
String name;
printA();
}
abstract class B {
printB();
}
class C implements A, B {
String name;
printA() {
print('打印A');
}
printB() {
print('打印B');
}
}
void main() {
C c = new C();
c.printA();
c.printB();
}
(4) mixins
- mixins即混入,指在类中混入其他功能,在Dart中可以使用mixins实现类似多继承的功能。
- 使用的条件伴随着Dart版本的变化一直在改变,这里用的是Dart 2.x中使用mixins的条件。
- 一个类可以mixins多个mixins类,mixins绝不是继承,也不是接口,而是一种全新的特性。
class A {
String info = '这是A';
void printA() {
print('A');
}
}
class B {
void printB() {
print('B');
}
}
class C with A, B {}
void main() {
var c = new C();
c.printA();
c.printB();
print(c.info);
}
- 作为mixins的类只能继承自Object,不能继承其他类。
class Person {
printInfo() {
print('Person类');
}
}
class A extends Person {
String info = '这是A';
void printA() {
print('A');
}
}
class B {
void printB() {
print('B');
}
}
// 这里A类继承了Person类,C类mixinsA类时会报错
// class C with A, B {}
void main() {}
- 作为mixins的类不能有构造函数。
class A {
String info = '这是A';
void printA() {
print('A');
}
}
class B {
// 写构造函数会报错
// B() {};
void printB() {
print('B');
}
}
class C with A, B {}
void main() {
var c = new C();
c.printA();
c.printB();
print(c.info);
}
- 继承与mixins一起写的方法。
class Person {
String name;
Object age;
Person(this.name, this.age);
printInfo() {
print('${this.name} --- ${this.age}');
}
}
class A {
String info = '这是A';
void printA() {
print('A');
}
}
class B {
void printB() {
print('B');
}
}
// C类具有Person类、A类和B类的功能
// class C with Person, A, B {}
// 此时Person类中可以有构造函数
class C extends Person with A, B {
C(String name, Object age): super(name, age);
}
void main() {
var c = new C('张三', 21);
c.printInfo();
}
- 注意
with A, B
或with B, A
的顺序,打印出来的内容不一样,后面替换前面。
class Person {
String name;
Object age;
Person(this.name, this.age);
printInfo() {
print('${this.name} --- ${this.age}');
}
// 继承与mixins中有同样的方法,with A, B或with B, A的顺序同下
void run() {
print('Person类执行中');
}
}
class A {
String info = '这是A';
void printA() {
print('A');
}
void run() {
print('A执行中');
}
}
class B {
void printB() {
print('B');
}
void run() {
print('B执行中');
}
}
// 注意with A, B或with B, A的顺序,打印内容不一样,后面替换前面
class C extends Person with A, B {
C(String name, Object age): super(name, age);
}
void main() {
var c = new C('张三', 21);
c.run();
}
- mixins的实例类型就是其超类的子类型。
// ignore_for_file: unnecessary_type_check
class A {
String info = '这是A';
void printA() {
print('A');
}
}
class B {
void printB() {
print('B');
}
}
class C with A, B {}
void main() {
var c = new C();
print(c is A);
print(c is B);
print(c is C);
var a = new A();
print(a is Object);
}
(5) 泛型方法
- 泛型即解决类、接口、方法的复用性,以及对不特定数据类型的支持(类型校验)。
// 只能返回String类型的数据
String getData1(String value) {
return value;
}
// 同时支持返回String类型和int类型,代码冗余
String getData2(String value) {
return value;
}
int getData3(int value) {
return value;
}
// 同时返回String类型和number类型,不指定类型可以解决问题
getData4(value) {
return value;
}
// 不指定类型放弃了类型检查,需要实现的是传入什么就返回什么
T getData5<T>(T value) {
return value;
}
void main() {
print(getData5(21));
print(getData5('张三'));
getData5<int>(22);
getData5<String>('李四');
getData5<int>(23);
getData5<String>('王五');
}
(6) 泛型类定义
class MyList1 {
List list = <int>[];
void add(int value) {
this.list.add(value);
}
List getList() {
return list;
}
}
// 定义泛型类
class MyList2<T> {
List list = <T>[];
void add(T value) {
this.list.add(value);
}
List getList() {
return list;
}
}
main() {
// 创建固定长度的List,这里使用到了泛型
List l1 = new List<String>.filled(2, '');
l1[0] = '121';
l1[1] = '张三';
print(l1);
// new关键词可以省略
MyList1 l2 = MyList1();
l2.add(123);
l2.add(321);
print(l2.getList());
MyList2 l3 = MyList2();
l3.add(125);
l3.add('李四');
print(l3.getList());
MyList2 l4 = MyList2<String>();
l4.add('王五');
// 传int会报错
l4.add('135');
print(l4.getList());
}
(7) 泛型接口定义
- 实现数据缓存的功能:文件缓存和内存缓存,这两种功能按接口约束实现。
- 定义一个泛型接口
- 约束实现子类必须有
getByKey(Key)
和setByKey(key, value)
。 - 要求setByKey的value类型和实例化子类时指定的类型需保持一致。
- 约束实现子类必须有
// 定义一个接口
abstract class Cache<T> {
// 定义两个抽象方法
getByKey(String key);
void setByKey(String key, T value);
}
// 定义两个类FileCache和MemoryCache
class FileCache<T> implements Cache<T> {
getByKey(String key) {
return null;
}
void setByKey(String key, T value) {
print('我是文件缓存,把key=${key}、value=${value}的数据写入到了文件中');
}
}
class MemoryCache<T> implements Cache<T> {
getByKey(String key) {
return null;
}
void setByKey(String key, T value) {
print('我是内存缓存,把key=${key}、value=${value}的数据写入到了内存中');
}
}
void main() {
MemoryCache m1 = new MemoryCache<String>();
m1.setByKey('index', '首页数据');
MemoryCache m2 = new MemoryCache<Map>();
m2.setByKey('index', {'姓名': '张三', '工作': '码农'});
}