一、Dart运算符
1.1. 除法、整除、取模运算
void main(List<String> args) {
var num = 7;
print(num / 3); // 除法操作, 结果2.3333..
print(num ~/ 3); // 整除操作, 结果2;
print(num % 3); // // 取模操作, 结果1;
}
1.2. ??=赋值操作
赋值运算符 ??=
当变量为null时,使用后面的内容进行赋值。
当变量有值时,使用自己原来的值。
// ??=
// 当原来的变量有值时,那么??= 不执行
// 当原来的变量为null时,那么将值赋值给这个变量
var name1 = "why";
name1 ??= "lilei";
print(name1);
var name2 = null;
name2 ??= "lilei";
print(name2);
1.3. 条件运算符 ??
条件运算符:expr1 ?? expr2
如果expr1是null,则返回expr2的结果;
如果expr1不是null,直接使用expr1的结果
// ??
// ?? 前面的数据有值,那么就使用??前面的数据
// ?? 前面的数据为null,那么就使用后面的值
var name1 = "why";
var name2 = name1 ?? "lilei";
print(name2);
var name3 = null;
var name4 = name3 ?? "lilei";
print(name4);
1.4. 级联语法:..
某些时候,我们希望对一个对象进行连续的操作,这个时候可以使用级联语法
void main(List<String> args) {
final p1 = Person();
p1.name = 'why';
p1.run();
p1.eat();
final p2 = Person()
..name = 'why'
..run()
..eat();
}
class Person {
String? name;
void run() {
print("$name runing");
}
void eat() {
print("$name eating");
}
}
1.5. 流程控制
1.5.1 if esle
使用isEmpty来检测是否为空,但是isEmpty不能用来判断null会报错。要判断是否为null,直接 a == null就好了
String? name = "sdsd";
print(name?.isEmpty);
print(name?.isNotEmpty);
print(name?.length);
if (name != null && name.isNotEmpty) {
print("name is $name");
} else {
print("name is null");
}
1.5.2 循环操作
- 基本的for循环
for (var i = 0; i < 5; i++) {
print(i);
}
- for - in
List<int> numbers = [1, 2, 3, 2, 1];
for (var num in numbers) {
print(num);
}
var numSet = numbers.toSet();
for (var num in numSet) {
print(num);
}
Map<String, Object> info = {"name": "why", "age": 18, "height": 1.88};
for (var item in info.entries) {
print('$item, ${item.key}, ${item.value}');
}
- do - while & while
var i = 10;
do {
print(i);
i += 1;
} while (i > 10 && i < 20);
while (i > 0) {
print(i);
i -= 1;
}
- break & continue
for (var i = 0; i < 10; i++) {
if (i > 0 && i < 5) {
continue;
}
print(i);
if (i == 7) {
break;
}
}
- switch - case
var direction = "south";
switch (direction) {
case 'east':
print("东面");
break;
case "south":
print("南面");
break;
case "west":
print("西面");
break;
case "north":
print("北面");
break;
default:
print("其他方向");
}
二、类和对象
2.1. 类的定义
在Dart中,定义类用class关键字。
类通常有两部分组成:成员(member)和方法(method)。
定义类的伪代码如下:
class 类名 {
类型 成员名;
返回值类型 方法名(参数列表) {
方法体
}
}
class Person {
String? name;
void eat() {
print('$name eat');
}
}
注意: 我们在方法中使用属性(成员/实例变量)时,并没有加this;
Dart的开发风格中,在方法中通常使用属性时,会省略this,但是有命名冲突时,this不能省略;
// 1.创建类的对象
// 从Dart2开始,new关键字可以省略。
var p = new Person(); // 直接使用Person()也可以创建
// 2.给对象的属性赋值
p.name = 'why';
// 3.调用对象的方法
p.eat();
注意:从Dart2开始,new关键字可以省略。
2.2. 类的构造方法
2.2.1 普通构造方法
当通过类创建一个对象时,会调用这个类的构造方法。
当类中没有明确指定构造方法时,将默认拥有一个无参的构造方法。
定义自己的构造方法:
注意:
当有了自己的构造方法时,默认的构造方法将会失效,不能使用
当然,你可能希望明确的写一个默认的构造方法,但是会和我们自定义的构造方法冲突
这是因为Dart本身不支持函数的重载(名称相同, 参数不同的方式)
class Person {
String? name;
int? age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
}
另外,在实现构造方法时,通常做的事情就是通过参数给属性赋值
为了简化这一过程, Dart提供了一种更加简洁的语法糖形式.
上面的构造方法可以优化成下面的写法:
Person(String name, int age) {
this.name = name;
this.age = age;
}
// 等同于
Person(this.name, this.age);
void main(List<String> args) {
var p1 = Person1("why", age: 18, height: 1.88);
var p2 = Person2('why', 18, 1.88);
}
class Person1 {
String? name;
int? age;
double? height;
// 必须参数 name,命名可选参数 age height
Person1(this.name, {this.age, this.height});
}
class Person2 {
String? name;
int? age;
double? height;
// 必须参数 name,位置可选参数 age height
Person2(this.name, [this.age, this.height]);
}
2.2.2 命名构造方法
但是在开发中, 我们确实希望实现更多的构造方法,怎么办呢?
因为不支持方法(函数)的重载,所以我们没办法创建相同名称的构造方法。
我们需要使用命名构造方法
void main(List<String> args) {
var p1 = Person1('why');
print(p1);
var p2 = Person1.withNameAndAge('why', 18);
print(p2);
}
class Person1 {
String? name;
int? age;
double? height;
Person1(this.name);
Person1.withNameAndAge(this.name, this.age);
// 重写toString方法
@override
String toString() {
return "$name - $age - $height";
}
}
在之后的开发中, 我们也可以利用命名构造方法,提供更加便捷的创建对象方式:
比如开发中,我们需要经常将一个Map转成对象,可以提供如下的构造方法
void main(List<String> args) {
Map<String, dynamic> map = {"name": "why", "age": 18, "height": 1.88};
var p = Person2.formMap(map);
print(p);
}
class Person2 {
String? name;
int? age;
double? height;
Person2.formMap(Map<String, dynamic> map) {
this.name = map["name"];
this.age = map["age"];
this.height = map["height"];
}
// 重写toString 方法
@override
String toString() {
return "my name is $name, age is $age, height is $height";
}
}
dynamic 和 Object 区别
Object调用方法时,编译时会报错
dynamic调用方法时,编译时不报错,但运行时会存在安全隐患
// dynamic 明确声明
dynamic obj1 = "name";
print("${obj1.runtimeType}"); // String
print(obj1.substring(1)); // 编译 不报错
// Object
Object obj2 = "name";
print("${obj2.runtimeType}"); // String
print(obj2.substring(1)); // 编译 会报错
2.2.3 初始化列表
类成员变量赋初始值
class Person {
String? name;
int? age;
double? height;
Person(String? name, {int? age, double? height}) {
this.name = name;
this.age = age ?? 18;
this.height = height ?? 1.88;
}
}
但如果成员变量是用final修饰,上述赋值会报错
// 错误写法 错误 错误
class Person {
final String? name;
final int? age;
final double? height;
Person(String? name, {int? age, double? height}) {
this.name = name; // 报错 'name' can't be used as a setter because it's final.
this.age = age ?? 18; // 报错 'age' can't be used as a setter because it's final.
this.height = height ?? 1.88; // 报错 'height' can't be used as a setter because it's final.
}
}
原因:final 修饰的是一个常量。可以在运行前赋值或运行期间赋值。对于类,在运行期间只会执行Person(String? name, {int? age, double? height})这段语句,并不会执行后续{}中的语句。也就是说在运行期间final修饰的变量没有赋值
在构造函数中,对final修饰的成员变量赋值
class Person {
final String? name;
final int? age;
final double? height;
Person(this.name, {this.age = 10, this.height = 1.88});
}
或者
class Person {
final String? name;
final int? age;
final double? height;
Person(this.name, {int? age, double? height})
: this.age = age ?? 10,
this.height = height ?? 1.88;
}
我们重新定义一个类Point, 传入x/y,可以得到它们的距离distance:
class Point {
final int x;
final int y;
final double distance;
// 错误写法
// Point(this.x, this.y) {
// distance = sqrt(x * x + y * y);
// }
// 正确写法
Point(this.x, this.y) : this.distance = sqrt(x * x + y * y);
}
2.2.4 重定向构造函数
在某些情况下, 我们希望在一个构造方法中去调用另外一个构造方法, 这个时候可以使用重定向构造方法:
在一个构造函数中,去调用另外一个构造函数(注意:是在冒号后面使用this调用)
class Person {
final String? name;
final int? age;
Person(String? name): this._internal(name, 10);
Person._internal(this.name, this.age);
}
2.2.5 常量构造方法
在某些情况下,传入相同值时,我们希望返回同一个对象,这个时候,可以使用常量构造方法.
默认情况下,创建对象时,即使传入相同的参数,创建出来的也不是同一个对象
如果将构造方法前加const进行修饰,那么可以保证同一个参数,创建出来的对象是相同的,这样的构造方法就称之为常量构造方法。
void main(List<String> args) {
const p1 = Person('why');
const p2 = Person('why');
print(identical(p1, p2)); // true 是同一个对象
final p3 = Person('why');
final p4 = Person("why");
print(identical(p3, p4)); // false 不是同一个对象
var p5 = const Person('why');
var p6 = const Person('why');
print(identical(p5, p6)); // true 是同一个对象
}
class Person {
final String? name;
const Person(this.name);
}
注意1:拥有常量构造方法的类中,所有的成员变量必须是final修饰的.
注意二: 为了可以通过常量构造方法,创建出相同的对象,不再使用 new关键字,而是使用const关键字
如果是将结果赋值给const修饰的标识符时,const可以省略
2.2.6 工厂构造方法
Dart提供了factory关键字, 用于通过工厂去获取对象
普通的构造函数:会自动返回创建出来的对象,不能手动返回
工厂构造函数最大的特点:可以手动返回一个对象
void main(List<String> args) {
var p1 = Person.withName("why");
var p2 = Person.withName("why");
print(identical(p1, p2));
var p3 = Person.withColor("red");
var p4 = Person.withColor("red");
print(identical(p3, p4));
}
class Person {
String? name;
String? color;
static final Map<String, Person> _nameCache = {};
// 普通的构造函数:会自动返回创建出来的对象,不能手动返回
// 工厂构造函数最大的特点:可以手动返回一个对象
factory Person.withName(String name) {
if (_nameCache.containsKey(name)) {
return _nameCache[name]!;
} else {
var p = Person(name, "defaultColor");
_nameCache[name] = p;
return p;
}
}
static final Map<String, Person> _colorCache = {};
factory Person.withColor(String color) {
if (_colorCache.containsKey(color)) {
return _colorCache[color]!;
} else {
var p = Person("defaultName", color);
_colorCache[color] = p;
return p;
}
}
Person(this.name, this.color);
}
2.3. setter和getter
默认情况下,Dart中类定义的属性是可以直接被外界访问的。
但是某些情况下,我们希望监控这个类的属性被访问的过程,这个时候就可以使用setter和getter了
直接访问属性,不走get/set方法
void main(List<String> args) {
final p = Person('why');
// 直接访问属性
p.name = "wang";
print(p.name);
// 通过getter和setter访问
p.setName = "lilei";
p.setName = 'why';
print(p.getName);
}
class Person {
String? name;
set setName(String name) {
this.name = name;
print("person set name");
}
String? get getName {
print("person get name");
return name;
}
Person(this.name);
}
2.4. 类的继承
Dart中的继承使用extends关键字,子类中使用super来访问父类
- 父类中的所有成员变量和方法都会被继承,,但是构造方法除外
void main(List<String> args) {
final p = Person();
p.age = 18;
p.run();
}
class Animal {
int? age;
void run() {
print("running");
}
}
class Person extends Animal {
}
- 子类可以拥有自己的成员变量, 并且可以对父类的方法进行重写
void main(List<String> args) {
final p = Person();
p.name = "why";
p.run(); // 打印 Person Runing
}
class Animal {
int? age;
void run() {
print("Animal Running");
}
}
class Person extends Animal {
String? name;
@override
void run() {
print("Person Runing");
}
}
-
子类中可以调用父类的构造方法,对某些属性进行初始化:
- 子类的构造方法在执行前,将隐含调用父类的无参默认构造方法(没有参数且与类同名的构造方法)
- 如果父类没有无参默认构造方法,则子类的构造方法必须在初始化列表中通过super显式调用父类的某个构造方法
class Animal {
int? age;
Animal(this.age);
}
class Person extends Animal {
String? name;
//Person(this.name, int age) : super(age);
Person(String name, int age) : name = name, super(age);
}
2.5. 抽象类
我们知道,继承是多态使用的前提。
所以在定义很多通用的调用接口时, 我们通常会让调用者传入父类,通过多态来实现更加灵活的调用方式。
但是,父类本身可能并不需要对某些方法进行具体的实现,所以父类中定义的方法,,我们可以定义为抽象方法。
什么是 抽象方法? 在Dart中没有具体实现的方法(没有方法体),就是抽象方法
抽象方法,必须存在于抽象类中
抽象类是使用abstract声明的类
下面的代码中, Shape类就是一个抽象类, 其中包含一个抽象方法.
abstract class Shape {
// 定义一个抽象方法
getArea();
}
// 继承抽象类后,必须实现抽象类中的抽象方法
class Circle extends Shape {
double? r;
Circle(this.r);
@override
getArea() {
return r! * r! * 3.14;
}
}
// 继承抽象类后,必须实现抽象类中的抽象方法
class Reactangle extends Shape {
double? w;
double? h;
Reactangle(this.w, this.h);
@override
getArea() {
return w! * h!;
}
}
注意事项:
注意一:抽象类不能直接通过普通构造函数(Shape())实例化,但可以通过工厂构造函数实例化
注意二:抽象类中的抽象方法必须被子类实现, 抽象类中的已经被实现方法, 可以不被子类重写
2.6. 隐式接口
Dart中的接口比较特殊, 没有一个专门的关键字来声明接口.
默认情况下,定义的每个类都相当于默认也声明了一个接口,可以由其他的类来实现(因为Dart不支持多继承)
当将一个类做接口使用时,那么实现这个接口的类,必须实现这个接口中的所有方法
在开发中,我们通常将用于给别人实现的类声明为抽象类:
class Runner {
void running() {}
}
class Flyer {
void flying() {}
}
// 当将一个类做接口使用时,那么实现这个接口的类,必须实现这个接口中的所有方法
class Superman implements Runner, Flyer {
@override
void running() {}
@override
void flying() {}
}
abstract class Runner {
void running() {}
}
abstract class Flyer {
void flying();
}
// 当将一个类做接口使用时,那么实现这个接口的类,必须实现这个接口中的所有方法
class Superman implements Runner, Flyer {
@override
void running() {}
@override
void flying() {}
}
2.7. Mixin混入
在通过implements实现某个类时,类中所有的方法都必须被重新实现(无论这个类原来是否已经实现过该方法)。
但是某些情况下,一个类可能希望直接复用之前类的原有实现方案,怎么做呢?
使用继承吗?但是Dart只支持单继承,那么意味着你只能复用一个类的实现。
Dart提供了另外一种方案: Mixin混入的方式
除了可以通过class定义类之外,也可以通过mixin关键字来定义一个类。
只是通过mixin定义的类用于被其他类混入使用,通过with关键字来进行混入
void main(List<String> args) {
final p = SuperMan();
p.running();
p.eating();
p.flying();
}
mixin Runner {
void running();
}
mixin Flyer {
void flying() {}
}
class Animal {
eating() {}
}
// 混入的语法:1.定义可混入的类时,不能使用class,要使用mixin 2.with进行混入
class SuperMan extends Animal with Runner, Flyer {
// Runner 没有实现 running 方法,此处需要实现
@override
void running() {}
}
2.8. 类成员和方法
在Dart中我们使用static关键字来定义类成员,类方法
void main(List<String> args) {
final p = Person();
p.name = "why";
Person.courseTime = "80";
p.eating();
Person.gotoCourse();
}
class Person {
// 成员变量
String? name;
// 类属性(静态属性)
static String? courseTime;
// 对象方法
void eating() {}
// 类方法(静态方法)
static void gotoCourse() {}
}
2.9. 枚举
枚举在开发中也非常常见, 枚举也是一种特殊的类, 通常用于表示固定数量的常量值
9.1. 枚举的定义
枚举使用enum关键字来进行定义:
enum Colors {
red,
green,
blue,
}
2.9.2 枚举的属性
枚举类型中有两个比较常见的属性:
index: 用于表示每个枚举常量的索引, 从0开始.
values: 包含每个枚举值的List.
void main(List<String> args) {
print(Colors.red.index);
print(Colors.values);
}
enum Colors {
red,
green,
blue,
}
枚举类型的注意事项:
注意一: 您不能子类化、混合或实现枚举。
注意二: 不能显式实例化一个枚举
三、泛型
3.1. 为什么要使用泛型
在 API 文档中你会发现基础数组类型的实际类型是 List<E> 。 <…> 符号将 List 标记为 泛型 (或 参数化) 类型。 这种类型具有形式化的参数。 通常情况下,使用一个字母来代表类型参数, 例如 E, T, S, K, 和 V 等。
泛型:通俗的理解: 就是解决类的接口, 方法, 复用性, 以及对不特定数据类型的支持(类型校验),
在通俗一点,就是对于类型的约束.
3.2 List和Map的泛型
List使用时的泛型写法:
// 创建List的方式
var name1 = ['why', 'james', 111];
print(name1.runtimeType); // List<Object>
// 限制类型
var name2 = <String>['why', 'james', 111]; // 最后一个报错
List<String> name3 = ['why', 'james', 111]; // 最后一个报错
Map使用时的泛型写法:
void main(List<String> args) {
// 创建Map的方式
var info = {1: 'one', 'name': 'why', 'age': 18};
print(info.runtimeType); // _InternalLinkedHashMap<Object, Object>
var info2 = <String,String>{'name': 'why', 'age': 18}; // / 18不能放在value中
Map<String,String> = {'name': 'why', 'age': 18}; // / 18不能放在value中
}
3.3 类定义的泛型
如果我们需要定义一个类, 用于存储位置信息Location, 但是并不确定使用者希望使用的是int类型,还是double类型, 甚至是一个字符串, 这个时候如何定义呢?
一种方案是使用Object类型, 但是在之后使用时, 非常不方便
另一种方案就是使用泛型
Location类的定义: Object方式
void main(List<String> args) {
Location l1 = Location(10, "20.5");
print(l1.y.runtimeType); // 类型是 String,但执行String类型的方法会报错
print(l1.y.substring(1)); // 编译报错
}
class Location {
Object? x;
Object? y;
Location(this.x, this.y);
}
Location类的定义: 泛型方式
void main(List<String> args) {
Location l1 = Location<int>(10, 20);
print(l1.x.runtimeType); // int
Location l2 = Location<String>("x", "xyx");
print(l2.y.runtimeType); // String
print(l2.y.substring(1)); // yx 不报错
Location l3 = Location(10, "1000");
print(l3.x.runtimeType); // int
print(l3.y.runtimeType); // String
print(l3.y.substring(1)); // 000 不报错
}
class Location<T> {
T? x;
T? y;
Location(this.x, this.y);
}
如果我们希望类型只能是num类型, 怎么做呢?
void main(List<String> args) {
var l1 = Location<int>(10, 10);
print(l1.x.runtimeType); // int
var l2 = Location<String>("10", "20"); // 错误的写法, 类型必须继承自num
var l3 = Location(10, "20"); // 错误的写法, 类型必须继承自num
}
class Location<T extends num> {
T? x;
T? y;
Location(this.x, this.y);
}
3.4 泛型方法的定义
最初,Dart仅仅在类中支持泛型。后来一种称为泛型方法的新语法允许在方法和函数中使用类型参数
void main(List<String> args) {
var names = ["why", "wwq"];
var first = getFirst(names);
print('$first,${first.runtimeType}'); // why,String
}
T getFirst<T>(List<T> ts) {
return ts[0];
}
四、库的使用
在Dart中,你可以导入一个库来使用它所提供的功能。
库的使用可以使代码的重用性得到提高,并且可以更好的组合代码。
Dart中任何一个dart文件都是一个库,即使你没有用关键字library声明
4.1. 库的导入
import语句用来导入一个库,后面跟一个字符串形式的Uri来指定表示要引用的库,语法如下:
import '库所在的uri';
- 常见的库URI有三种不同的形式
来自dart标准版,比如dart:io、dart:html、dart:math、dart:core(但是这个可以省略)
//dart:前缀表示Dart的标准库,如dart:io、dart:html、dart:math
import 'dart:io';
使用相对路径导入的库,通常指自己项目中定义的其他dart文件
//当然,你也可以用相对路径或绝对路径的dart文件来引用
import 'lib/student/student.dart';
Pub包管理工具管理的一些库,包括自己的配置以及一些第三方的库,通常使用前缀package
//Pub包管理系统中有很多功能强大、实用的库,可以使用前缀 package:
import 'package:flutter/material.dart';
- 库文件中内容的显示和隐藏
如果希望只导入库中某些内容,或者刻意隐藏库里面某些内容,可以使用show和hide关键字
show关键字:可以显示某个成员(屏蔽其他)
hide关键字:可以隐藏某个成员(显示其他)
import 'lib/student/student.dart' show Student, Person;
import 'lib/student/student.dart' hide Person;
-
库中内容和当前文件中的名字冲突
当各个库有命名冲突的时候,可以使用as关键字来使用命名空间
import 'lib/student/student.dart' as Stu;
Stu.Student s = new Stu.Student();
4.2 库的定义
- library关键字
通常在定义库时,我们可以使用library关键字给库起一个名字。
但目前我发现,库的名字并不影响导入,因为import语句用的是字符串URI
library math;
- part 关键字
在开发中,如果一个库文件太大,将所有内容保存到一个文件夹是不太合理的,我们有可能希望将这个库进行拆分,这个时候就可以使用part关键字了
目前官方不推荐使用part 抽取,建议使用export
mathUtils.dart文件
part of "utils.dart";
int sum(int num1, int num2) {
return num1 + num2;
}
dateUtils.dart文件
part of "utils.dart";
String dateFormat(DateTime date) {
return "2020-12-12";
}
utils.dart文件
part "mathUtils.dart";
part "dateUtils.dart";
test_libary.dart文件
import "lib/utils.dart";
main(List<String> args) {
print(sum(10, 20));
print(dateFormat(DateTime.now()));
}
- export 关键字
官方不推荐使用part关键字,那如果库非常大,如何进行管理呢?
将每一个dart文件作为库文件,使用export关键字在某个库文件中单独导入
mathUtils.dart文件
int sum(int num1, int num2) {
return num1 + num2;
}
dateUtils.dart文件
String dateFormat(DateTime date) {
return "2020-12-12";
}
utils.dart文件
library utils;
export "mathUtils.dart";
export "dateUtils.dart";
test_libary.dart文件
import "lib/utils.dart";
main(List<String> args) {
print(sum(10, 20));
print(dateFormat(DateTime.now()));
}
4.3 导入第三方库
1. 在dart公开库网站上搜索需要导入的库
2. 在pubspec.yaml 中 将相关库信息添加进去
name: mylib
description: a dart library.
environment:
sdk: ">=2.16.1 <3.0.0"
dependencies:
http: ^0.13.4 // 三方库 http
html: ^0.15.0 // 三方库 http
3. 拉取三方库
- cd 到pubspec.yaml的目录
- 执行dart pub get 指令
4. 导入三方库
import 'package:http/http.dart' as http;
import 'package:html/dom.dart' as html;