1 基础
1.1 认识Dart
-
flutter SDk中已经内置了Dart, 但是如果想单独学习Dart,并且运行自己的Dart,最好安装一个DartSDk.
-
Google为Flutter选择了Dart就已经是既定的事实,无论你多么想用熟悉的语言,比如JavaScript,Java,Swift,C++等来开发flutter,至少目前都是不可以的.
-
终端 Dart test.dart aaa bbb 来运行dart文件
-
main函数是入口函数, Dart也支持模板字符串.
1.2 helloworld
main(List<String> args) {
print("hello world");
}
// 单行注释
/* */ 多行注释
/// 文档注释, 以后可以通过dartdoc将注释转成文档(文档注释支持markdown语法)
1.3 声明变量
明确的声明: String name ="tom"
类型推导:
var age = 9; 已经推导出你的类型是int, age = "aaa" 会报错
final height = 1.88 只能被赋值一次, 再height = 2 会报错
const job = 'it' 只能被赋值一次, 再 job = "dd" 会报错
final 可以开始不赋值,只能赋值一次, 可以是静态的值也可以是运行时返回来的一个值.
const 定义时必须赋值, 并且只能赋值一次, 只能是静态的值.
---------------------------------------------------
class Person{
String name = "";
Person(String name) {
this.name = name
}
}
final p1 = Person("tom")
final p2 = Person("tom")
p1和p2不是同一个对象, identical(p1, p2)这个函数可以看两个对象是否相等.
const p3 = Person("会报错");
===================================================
class Person4{
final String name; // 所有的成员变量都是final修饰的, 构造器才可以用const修饰
const Person4(this.name);
}
const p4 = Person4("不会报错"); // 常量构造函数
const p5 = Person("不会报错"); // p5和p4是同一个对象
1.4 Number类型
- int, double
(1) 字符串转数字:
var a = int.parse("111");
var b = double.parse("234.06");
(2) 数字转字符串:
var a1 = 333;
var a2 = 22.456
var t = a1.toString();
var m = a2.toString();
3.1415.toStringAsFixed(2); // 四舍五入,保留2位有效数字
(3) int常用方法
num.isEven; // 是否奇数
num.isOdd; // 是否偶数
num.isEmpty; // 是否为空
num.abs(); // 决定值
num.toDouble(); // 转换为浮点型
(4) double常用属性
num.toInt(); // 向下取整
num.floor(); // 向下取整
num.truncate(); // 丢弃数值的小数部分 向下取整
num.floorToDouble(); // 向下取整再转为浮点型
num.truncateToDouble(); // 丢弃小数部分 并转为浮点型
num.round(); // 四舍五入取整
num.ceil(); // 向上取整
num.ceilToDouble(); // 向上取整再转为浮点型
10.remainder(4); // 10对4取余为2
(5) 12.gcd(18); // 最大公约数, 6
(6) 155000.toStringAsExponential(3); // 科学计数法
(7) 10.compareTo(12); // 比较, 相同返回0 前边大返回1 后大返-1
(9) num.runtimeType; // 运行时的类型
(10) num.isFinite; // 是否为有限值
(11) num.isInfinite; // 是否为无限值
(12) num.isNaN; // 是否为非数值
(13) num.isNegative; // 是否为负数
(14) num.sign; // 获取值的符号
(15) num.bitLength; // 获取当前int类型值需要的最小数 int独有属性
1.5 布尔类型
- true false
- flutter 没有非空即真,没有非0即真
- if(flag) 这个flag必须是一个静态的布尔类型的值
- isBool.toString(); 转字符串
1.6 String类型
Dart的字符串是不可变类型, 所有的方法都不会改变原来字符串, 而是返回一个新的字符串.
定义:
var s1 = "ddd"
var s2 = 'aaa'
var s1 = """ddd"""
var s1 = ''' ttt
www '''; // 可以换行
(1)字符串和表达式拼接
var name = "tom"
var message = " my name is ${name} , 类型是 ${name.runtimeType}"
也可以用 s1 + s2 进行拼接
(2) 使用r不会被转义
r'hello \n Tom';
(3) 字符串空格拼接
String str = '单引号空格字符串' '拼接' '1234';
(4) 字符串换行拼接
String str = '单引号'
'换行' '空格'
'拼接';
(5) str.isEmpty 是否为空 str.isNotEmpty是否不为空
(6) str.startsWith('http'); // 判断是否以http开始
(7) str.endWith('http'); // 判断是否以http结束
(8) str.contains('http'); // 判断是否包含
(9) 字符串截取
str.substring(0,3);
(10) 打印某个字符 首次出现的下标
str.indexOf('t');
(11) 打印某个字符 末次出现的下标
str.lastIndexOf('t');
(12) str.toLowerCase(); // 转小写
(13) str.toUpperCase(); // 转小写
(14) str.trim(); // 清空格
(15) str.trimLeft(); // 清除左边空格
(16) str.trimRight(); // 清除右边空格
(17) str.split("-"); // 分隔字符串
(18) str.replaceAll('old', 'new'); // 替换
(19) 正则表达式
RegExp(r'正则表达式')
1.7 List列表 数组
(1)
var list1 = ["abc", "cba", "nba"]; // 使用var声明会自动推断类型
var list1 = <String>['1234','456'];
List<String> list1= ['qwer'];
var list1 = List.filled(5, ' '); // 定义固定长度的数组
(2)
list1.add("mba"); // 数组增加元素
list1.addAll([4,5,6]); // 数组拼接
list1.insert(3,4); // 数组插入元素
list1.insertAll(9,[4,5]); // 数组整体插入9为下标
(3)
list1.remove(value); // 删除元素
list1.removeAt(index); // 通过下标删除元素
list1.removeLast(); // 删除最后一个元素
list1.removeRange(2,4); // 范围删除,传入区间下标
list1.removeWhere((item) => item>4);
list1.clear(); // 删除所有元素
(4)
list1.setRange(2,5,[1,1,1,1]); // 修改区间为指定的数值 2,5是下标 包左不包右, 注意第三个参数元素不够用的问题
list1.setAll(2,[1,1,1,1]); // 修改某索引后的值, 第二个参数数组内有几个值就改变几个, 注意越界第二个参数元素多的问题
(5)
list1.fillRange(2,5,8); // 将指定区间的每个元素都 替换成某个值
list1.replaceRange(2,5,[7,7,7,7,7,7]); // 将指定区间的每个元素都 替换成参数数组内的值
(6)
其他类型转List
final temp = <int,String> {1:'aaa', 2:'ccc',3:'ddd'};
final klist = temp.keys.toList(growable: false);
final klist = temp.values.toList(growable: false);
数组转换为字符串
list1.join('=');
(7)
list1.indexOf(5); // 查找元素是5的下标值 (首次)
list1.lastIndexOf(5); // 查找元素是5的下标值 (末次)
list1.sublist(3,7); // 截取区间值,返回数组, 不改变原来的数组list1
list1.getRange(3,5); // 截取区间值返回迭代器
list1.any((value) => value > 5); // 判断数组内是否有满足条件的值, 返回布尔
list1.contains(6); // 判断是否包含, 返回布尔
list1.firstWhere((value) => value > 3); // 获取满足条件的第一个元素
list1.lastWhere((value) => value > 3); // 获取满足条件的最后一个元素
list1.indexWhere((value) => value>3); // 获取满足条件的第一个元素的索引
list1.lastIndexWhere((value) => value>3); // 获取满足条件的最后一个元素的索引
// 查找是否存在满足条件的唯一值,存在返回这个元素,不存在执行第二个函数, 存在多个会报错
list1.singleWhere((item)=>item>7 , orElse:() {print("不存在"); return -1;});
list1.singleWhere( (item) => item>50 , orElse:() => '不存在' ) );
(8)
list1.length; // 数据长度
list1.reversed; // 数组逆序
list1.isEmpty; // 数组是否为空 isNotEmpty
list1.first; // 获取第一个元素
list1.toSet(); // 数组改变类型 会去重
(9) 数组遍历
list1.forEach( (item){print(item);});
list1.map( (item) => item>3 );
for( var i = 0; i< list1.length; i++) { }
for (var value in list) {}
list.reduce( (value, item) ) => value + 1;
list.sort(); // 数组排序
f11.forEach((item) => { print(item) }); 在dart的箭头函数中 函数体只能有一行 并且不能写结束的分号; 大括号可以省略.
1.8 集合Set ???
元素不重复, 无法通过下标取值
var set = {"abc", "cba", "nba"};
var set = <int> {2,3,4,5};
var set1 = new Set();
set1.add(33);
set1.addAll([77,65,53,99]); // 添加多个
// 以下两步实现数组去重
var temp = Set<String>.from(list1); // 列表转集合
var temp = list1.toSet(); // 列表转集合
temp.toList(); // 集合转列表
// 特殊操作
var set1 = {1,2,3,4,5};
var set2 = {4,5,6,7,8,9};
set1.intersection(set2); // 求交集
set1.union(set2); // 求并集
set1.difference(set2); // 求差集,set1有set2没有
set1.first; // 返回第一个元素
set1.last; // 返回最后一个元素
1.9 映射Map
(1)
// 创建
var map = {'tt': '字典', 'll': 数组}; // 使用var关键字声明参数,会自动推断类型.
Map<String, int> map = {'key1': 111, 'key2': 123};
// 先声明后添加
Map map = {};
map['key1'] = 66;
// of创建方法
map1 = Map.of(map);
// fromEntries 创建方法
var map1 = Map.fromEntries(map.entries);
// identity创建方法, 创建一个严格的map
var map1 = Map.identity();
map1['ziDian'] = '字典';
print('打印==$map1'); //打印=={ziDian: 字典}
// unmodifiable创建方法 创建一个不可修改、基于哈希值的Map,包含other所有的项
var map = {'ziDian': '字典', 'shuZhu': '数组'};
var map1 = Map.unmodifiable(map);
print("打印==$map1"); //打印=={ziDian: 字典, shuZhu: 数组}
// fromIterables创建方法
List<String> keys = ['ziDian', 'shuZhu'];
List<String> values = ['字典', '数组'];
var map = Map.fromIterables(keys, values);
print("打印==$map"); //打印=={ziDian: 字典, shuZhu: 数组}
(2)
map.length; // 字典长度
map.isEmpty; // 是否为空
map.runtimeType; // 运行时类型
map.hashCode; // 打印哈希值
var map = {'ziDian': '字典', 'shuZhu': '数组'};
print("打印==${map.hashCode}"); //打印==986719720
map.keys.toList(); // 转字符串
map.values.toList(); // 转字符串
map['tt']; // 根据key取值和赋值
(3)
// 添加元素 添加元素(如果key已存在,则是更新value)
var map = {'ziDian': '字典', 'shuZhu': '数组'};
map['ziFu'] = '字符';
map['ziDian'] = '其他';
print('打印==$map'); //打印=={ziDian: 其他, shuZhu: 数组, ziFu: 字符}
var map = {'ziDian': '字典', 'shuZhu': '数组'};
map['string'] = '字符串';
map.addAll({'int': '整型'});
print("打印==$map"); //打印=={ziDian: 字典, shuZhu: 数组, string: 字符串, int: 整型}
// putIfAbsent方法 添加 (如果key不存在,则添加,否则,不添加)
var map = {'ziDian': '字典', 'shuZhu': '数组'};
map.putIfAbsent('ziDian', () => '其他');
print('打印==$map'); //打印=={ziDian: 字典, shuZhu: 数组}
// 是否包含指定的key
var map = {'ziDian': '字典', 'shuZhu': '数组'};
print("打印==${map.containsKey('ziDian')}"); //打印==true
// 是否包含指定的value
var map = {'ziDian': '字典', 'shuZhu': '数组'};
print("打印==${map.containsValue('字典')}"); //打印==true
// 遍历
var map = {'ziDian': '字典', 'shuZhu': '数组'};
map.forEach((key, value) {
print('key = $key, value = $value');
});
// 转成字符串
var map = {'ziDian': '字典', 'shuZhu': '数组'};
print("打印==${map.toString()}"); //打印=={ziDian: 字典, shuZhu: 数组}
// 删除
var map = {'ziDian': '字典', 'shuZhu': '数组'};
print("打印==${map.remove('ziDian')}"); //打印==字典
print("打印==${map.remove('code')}"); //打印==null
// 清空
var map = {'ziDian': '字典', 'shuZhu': '数组'};
print("打印==${map.clear()}"); //打印=={}
//
//
//
//
1.10 Runes符文 (数据类型) ???
- Runes对象是一个32位字符对象.它可以把文字转成符号表情或特定的文字.
- 例如 '\u{1f44d}' 转换成
1.11 Symbol 反射 ???
- 是使用#号开头的标识符
1.12 dynamic
- 动态数据类型
1.13 运算符
= 赋值
/ 除
~/ 除取整
% 除取模
类型判断运算符 is is!
条件访问属性?. 如果有才去访问, var obj; print(obj?.length);
??= 判空赋值, 是判断name自己是否有值,如果没值 则给name自己赋个值
var name = "tom";
name ??= "jack"; // 如果name为null的时候就 让name的值为jack
?? 判空赋值
var name = null;
var temp = name ?? "jack" // 如果name为null 则把jack赋值给temp
级联运算符:
main(List<String> args) {
var p = Person();
p.name = "tom";
p.run();
p.eat();
// 级联运算符
var p = Person()
..name = "tom"
..eat()
..run();
}
class Person {
String name;
void run() {
print("running");
}
void eat() {
print("running");
}
}
2. 条件语句和函数
2.1 if和else
2.2 for循环
for(var i = 0; i<5;i++) {
print(i);
}
for(var name in arr) {
print(name);
}
2.3 函数
2.3.1 函数基础
dart中没有函数的重载
(1)声明:
// 直接声明
int sum(int n1, int n2) {
return n1 + n2
}
// 返回值类型可以省略, 开发中不推荐
sum(int n1, int n2) {
return n1 + n2
}
// 匿名函数
// 箭头函数
// 立即执行函数
((int age) {print(age);})(17);
(2)参数:
void say1(Strign name) { // 必选参数
print("")
}
void say2(Strign name, [int age=12, double height = 1.9]) { // 位置可选参数
print("")
}
void say3(Strign name, {int age=12, double height=1.9}) { // 命名参数
print("")
}
say3("tom", age: 5, height: 1); // 调用的时候必须给相应的实参命名
--------------------
只有可选参数才可以有默认值
(3) 作用域和闭包
(4) 异步函数
JavaScript中,异步调用通过promise来实现, async函数返回一个promise, await用于等待Promise
然而在Dart中, 异步调用通过Future来实现, async函数返回一个Future, await用于等待Future
2.3.2 函数是一等公民
- 在很多语言中,函数并不能作为一等公民来使用, 比如Java/OC, 这种限制让编程不够灵活,所以现在的编程语言基本都支持函数作为一等公民来使用,Dart也支持.
- 这个意味着你可以将函数赋值给一个变量,也可以将函数作为另外一个函数的参数或者返回这来使用.
(1) 函数做为另外一个函数的参数
void test(Function foo) {
foo(23);
}
int bar(a) {
print("函数被调用")
return 66;
}
------------------------
test(bar);
test((a) {
print("匿名函数被调用");
return 10
});
箭头函数,函数体只有一行代码
test((a) => {print("箭头函数被调用")})
----------------------------
void test(Function foo) {
foo(23);
}
// 真实开发一般很少 直接用Function, 而是指名具体类型(函数签名)
void test2(int foo(int num1, num2)) {
foo(20, 30);
}
test2((num1, num2) {
return num1+num2
})
--------------------------------------
// 函数签名 int foo(int num1, num2) 这种方式不好阅读, 用下边这种方式
typedef Calculate = int Function(int num1, num2); // 就相当于让Calculate等于我们这个函数签名
void test3(Calculate foo) {
foo();
}
(2) 函数做返回值
Calculate demo() {
return (n,m) {
return n+m;
}
}
3. 类和对象
- Dart是一个面向对象的语言,面向对象中非常重要的概念就是类,类产生了对象.
- dart中可以实现一个类,implement
- dart没有关键字来定义接口的,默认情况下所有的class都是一个隐式的接口
- 抽象类做继承比较多一点
- 接口是用来做实现的
3.1 类的定义
在Dart中,定义类用class关键字
类通常有两部分组成: 成员(member) 和 方法(method)
var p1 = Person(); // 我们现在调用的Person类调用的是一个默认的构造函数
class Person {
String name;
int age;
}
--------------------
(2)
一旦自己写了构造函数默认的构造函数就会被覆盖掉
// var p2 = Person2(); // 此行会报错
var p2 = Person2('tom',2);
var p3 = Person2.abc('jack', 19, 1.88);
class Person2 {
String name;
int age;
double height;
Person2(String name, int age, {double height}) {
this.name = name;
this.age = age;
this.height = height;
}
Person2(this.name, this.age, {this.height}); // 这是上边这种的简写
// 命名构造函数( 相当于函数的重载 )
Person2.abc(this.name, this.age, this.height);
Person2.abc2(Map<String, dynamic> m){
this.name = m["name"];
this.age = m["age"];
this.height = m["height"];
};
@override
String toString() { // 重写toString方法
return "名字${this.name}";
}
}
默认情况下所有的类都继承自Object
Object obj = "tom"; // 父类的引用指向子类的对象
obj.substring(1); // 会报错
dynamic obj2 = "tom";
obj2.substring(1); // 不会报错, dynamic 会根据赋的值类型 来动态的确定类型.
-----------------------------------------------------------------------
(3)初始化列表 (调用时机是在构造函数体执行之前 去执行)
class Person3 {
final String name;
final int age = 19; // fainal定义的必须要赋初始值
Person3(this.name) {
}
}
class Person4 {
final String name;
final int age; // fainal定义的必须要赋初始值
Person4(this.name)
{ // 在执行这个大括号的时候他表示的是 ,初始化列表已经执行完毕了,
this.age = 10; // 会报错
}
}
class Person5 {
final String name;
final int age; // fainal定义的必须要赋初始值
Person5(this.name,{int age}):this.age = age ?? 10
{
}
// Person(this.name, {this.age = 10}); // 这种写法也是可以的, 这个只能是赋值操作
}
--------------------------------------------
(4)重定向构造函数
class Person6 {
String name;
int age;
// Person(this.name): age=0;
Person6(String name): this._internal(name, 0); // 构造函数的重定向
Person6._internal(this.name, this.age);
}
---------------------------------
(5) 常量构造函数
const p1 = const Person("tom");
const p2 = const Person("tom"); // 使用常量构造函数声明对象,要用const关键字 否则无效, 还是不同的对象
print(identical(p1,p2)); // 指向同一块内存空间
class Person7 {
final String name;
final int age = 12;
const Person7(this.name);
}
(6) 工厂构造函数 ???
Dart提供了factory关键字,默认不需写return this, 工厂构造行数需要自己手动返回一个对象,最大的特点就是可以手动的返回一个对象.
需求: 传入的color是相同的,返回同一个对象. 传入的name是相同的,返回同一个对象
class Person8 {
String name;
String color;
static final Map<String, Person> _nameCache = {};
static final Map<String, Person> _colorCache = {};
factory Person8.mmm(String name) {
if(_nameCache.containsKey(name)) {
return _nameCache[name];
}
else {
final p = Person(name, "default");
_nameCache[name] = p;
return p;
}
}
factory Person8.ccc(String color) {
if(_colorCache.containsKey(color)) {
return _colorCache[color];
}
else {
final p = Person("default", color);
_colorCache[color] = p;
return p;
}
}
Person8(this.name, this.color);
}
3.2 访问修饰 和 setter,getter 和 static
(1)
Dart与TypeScript不同,没有访问修饰符(public, protected, private)
Dart的类中,默认的访问修饰符是public, 如果以_开头则表示私有
(2)
Dart没有public provide...等关键字, 就是在变量之前价格下划线, 表示在当前包里边可以访问
<p align=left></p>
class Person9 {
final String name;
set setName(String name) { // 方法名后需要小括号, 使用的时候不需要小括号
this.name = name;
}
String get getName { // 方法名后不需要小括号了, 使用的时候不需要小括号
return name;
}
}
(3) static关键字用来指定静态成员
通过static修饰的属性是静态属性
通过static修饰的方法是静态方法
静态成员可以通过类名直接访问(不需要实例化), 因静态方法可以不实例化就可以使用 所以静态方法中不能使用this, 静态方法中只能访问静态属性.
非静态的方法可以访问静态属性
在外部非静态方法不用能类名点调用
在外部不能通过实例对象访问静态属性
3.3 元数据
元数据以@开头, 可以给代码标记一些额外的信息
元数据可以用在 库,类,构造器,函数,字段,参数或变量声明的前边
(1)@override (重写)
某个方法前边添加该注解后, 表示重写了父类中的同名方法
(2)@required (必填)
可以通过@required来注解Dart中的命名参数,用来指示它是必填参数
(3)@deprecated (弃用)
若某个类 某个方法加上该注解之后, 表示此方法此类不再建议使用
3.4 继承 和 抽象类 和接口
- 类的继承只有单继承, 继承的时候必须实现抽象方法
- Dart 接口可以多实现 implements, 实现的时候要实现所有方法
- 如果原来的类实现已经有实现了,可以用混入with
(1)
子类通过extends关键字继承父类, 继承后,子类可以使用父类中可见的内容
子类可以通过super关键字来引用父类中的可见内容
子类对象调用say() 如果类中已经有了say()方法,就会调用自己的say(), 如果没有再去调用父类的say()
如果子类定义的say()方法,父类中已经有了, 那么建议前边加上@override, 不加也行.
子类构造函数 必须给父类构造函数传值 Son(String job) : super(job);
(2) abstract
抽象类可以没有方法的实现, 也可以有方法的实现.
抽象类不能实例化 ,继承抽象类必须实现抽象方法.
external 可以将方法的声明和实现分开写, 方法的实现可以用patch关键字.
抽象方法必须定义在抽象类中, 抽象类中也可以定义普通方法.
(3) implements接口
(4) Mixin 混入是一段公共代码.混入有两种声明方式: ???
将类当作混入 class Abc{ }
被Mixin的类只能继承自Object, 不能继承其他类.
被Mixin的类不能有构造函数
使用mixin关键字直接声明 mixin Cdd{ }
混入可以提高代码复用的效率,普通类可以通过with来使用混入, 可以使用多个混入
3.5 泛型
- 泛型是在函数,类,接口中 指定宽泛数据类型的语法
- 泛型可以作用在不同的语法上, 作用在函数上叫泛型函数.
- 通常写在尖括号中
(1) 泛型函数
返回类型 函数名 <泛型> (参数类型 参数) { }
String getStr<T> (T num) { return num.toString; }
getData<int>(12);
(2) 泛型类
(3) 泛型接口
3.8 库的定义 ???
- library关键子, 通常在定义库时,我们可以使用library关键字给库起一个名字.
- 但目前我们发现,库的名字并不影响导入,因为import语句用的是字符串URL
library math;
- part 关键字,在之前我们使用student.dart作为演练的时候,只是将该文件作为一个库. 在开发中,如果一个库文件太大,用part拆分, 现在官网已经不建议用这个part关键字了. 用export代替