Dart 语法学习目录
第一节: Dart 语法了解,认识变量,常量,数据类型
第二节: Dart 操作符(运算符)
第三节: Dart 中常用集合 List/Map
第四节: Dart 函数使用,声明函数/匿名函数/可选参数/作用域/闭包
第五节: Dart 面向对象/构造函数/静态属性
第六节: Dart中抽象类abstract/接口implements/混入Mixin
第七节: Dart中泛型/泛型定义与使用
第八节: Dart 中的库/自定义库/内置库/第三方库
1. 面向对象介绍
1.1 面向对象编程的认识
面向对象编程(OOP)的三个特征是: 封装,继承,多态
- 封装: 封装是对象和类概念的主要特征, 封装,把客观事物封装成抽象的类,并且把自己的部分属性和方法提供给其他对象
- 继承:面向对象编程语言的主要功能就是继承, 继承是指一种能力
- 多态: 允许将子类的指针赋值给父类类型的指针,同一个函数调用会有不同的执行效果
1.2 Dart的面向对象认识
- Dart 是一个面向对象编程语言,同时支持基于
mixin
的继承机制。 - 每个对象都是一个类的实例,所有的类都继承于
Object
. - 基于 Mixin 的继承 意味着每个类(Object 除外) 都只有一个超类,一个类的代码可以在其他多个类继承中重复使用。即一个类可以继承自多个父类。
- 使用关键字
calss
声明一个类 - 使用关键字
new
创建一个对象,new
可以省略。 - 对象的由函数和数据(即方法和实例变量)组成。
- 方法的调用要通过对象来完成: 调用的方法可以访问其对象的其他函数和数据。
2. 创建使用类
2.1 定义类
通过class定义类,并在类中定义属性和方法
示例:
class Person{
// 定义属性
String name = "小明";
int age = 10;
// 定义方法
void getInfo(){
print("姓名:${this.name}--年纪:${this.age}")
}
}
2.2 使用类
void main(){
// 注意:student是Person的实例,所以变量的类型就是 Person类
Person student = new Person();
print(student); // Instance of 'Person' Person类的实例
// 调用实例的方法
student.getInfo(); // 姓名:小明--年纪:10
}
3. 默认构造函数
我们会发现,上一个例子中我们定义的方法,必须在我们手动调用的时候才会执行, 其实我们也可以定义类似于JS ES6 类中的constructor 函数这样的在实例化时默认调用的构造函数
默认构造函数的名称必须与类名相同
如果未显式定义默认构造函数,会默认一个空的构造函数。
默认构造函数没有参数,并且会调用超类的没有参数的构造函数。
3.1 未显示定义默认构造函数
class Person{
String name = "小明";
int age = 18;
void getInfo(){
print("name:$name---age: $age");
}
}
3.2 定义默认构造函数
在通过这类实例化对象时会默认调用这个构造函数
// 定义类
class Person{
// 定义属性
String name = "小明";
int age = 10;
// 默认调用的构造函数
Person(){
print("我是在类实例化时默认调用的函数");
}
// 定义方法
void getInfo(){
print("姓名:${this.name}--年纪:${this.age}");
}
}
// 入口函数
void main(){
// 实例化类
Person student = new Person();
/*
打印结果:
我是在类实例化时默认调用的函数
*/
}
上面的例子虽然在实例化时执行了默认的构造函数, 但是我们会发现实例化对象的属性是没有变化的,
所以我们可以在默认构造函数中根据实例化时传递的参数动态改变实例对象的属性
3.3 默认构造函数传参
类在实例化是传入的动态参数会在默认构造函数执行时形参所接收.
其中
this
关键字指当前的实例。
示例:
// 定义类
class Person{
// 定义属性
String name;
int age;
// 默认调用的构造函数
Person(String name,int age){
this.name = name;
this.age = age;
}
// 定义方法
void getInfo(){
print("姓名:${this.name}--年纪:${this.age}");
}
}
void main(){
// 第一次实例化
Person student = new Person("小明",8);
student.getInfo(); // 姓名:小明--年纪:8
// 第二次实例化
Person student2 = new Person("小红",10);
student2.getInfo(); //姓名:小红--年纪:10
}
3.4 默认构造函数传参简写
其实默认的构造函数,如果只是修改实例属性的话,我们还可以进行简写
class Person{
// 定义属性
String name;
int age;
// 默认调用的构造函数
// 直接将实例化传递过来的实参直接复制给实例属性
// 简写方式
Person(this.name,this.age);
// 定义方法
void getInfo(){
print("姓名:${this.name}--年纪:${this.age}");
}
}
void main(){
Person student = new Person("小明",8);
student.getInfo(); // 姓名:小明--年纪:8
Person student2 = new Person("小红",10);
student2.getInfo(); //姓名:小红--年纪:10
}
3.5 实例化之前给属性赋值
在默认构造函数简写的情况下,接受实参的小括号后面添加:
就可以在实例化执行默认构造函数之前给实例属性赋值;
可以在实例化之前给属性赋值,但是没什么意义
class Person{
// 定义属性
String name;
int age;
// 默认调用的构造函数
// 实例化之前给尚属性赋值,没什么意义
Person():name="默认",age=18{
print("姓名:${this.name}--年纪:${this.age}");
}
// 定义方法
void getInfo(){
print("姓名:${this.name}--年纪:${this.age}");
}
}
void main(){
Person student = new Person(); // 姓名:默认--年纪:18
}
注意:
4. 命名构造函数,
命名构造函数,是可以在实例化时通过类调用的方法
说明:
- 使用命名构造函数可为一个类实现多个构造函数
- 也可以使用命名构造函数来更清晰的表明函数意图
- 构造函数不能够被继承, 这意味着父类的命名构造函数不会被子类继承
调用的方法:类名.命名构造函数
class Person{
// 定义属性
String name;
int age;
// 默认调用的构造函数
Person(this.name,this.age);
// 命名构造函数
Person.paly(this.name){
print("${this.name}在打游戏");
}
// 定义方法
void getInfo(){
print("姓名:${this.name}--年纪:${this.age}");
}
}
void main(){
// 通过类来调用的方法
Person.paly("小明"); // 小明在打游戏
}
可以把命名构造函数当成静态方法来理解
5. 常量构造函数
常量构函数就是希望每次创建的对象都是同一个对象赋值给不同的变量.
说明:
- 如果该类生成的对象是固定不变的, 那么就可以把这些对象定义为编译时常量。
- 需要定义一个
const
构造函数, 并且声明所有实例变量为final
。 - 要使用常量构造函数只需要用
const
替代new
即可,也可以省略const
。
示例:
class Person{
final name = "小明";
final age = 18;
const Person();
void getInfo(){
print("name:$name---age: $age");
}
}
void main(){
// 通过new 也可以创建一个对象,但不是常量构造函数
Person student= new Person();
student.getInfo(); // name:小明---age: 18
Person student2= const Person();
student3.getInfo();
Person student3 = const Person();
student3.getInfo(); // name:小明---age: 18
// 可以通过identical方法来比较创建的对象是不是同一个对象
print(identical(student, student2)); // false
print(identical(student2, student3)); // true
}
通过结果我们可以发现, 通过new
关键字创建出来的对象和通过const
创建出来的对象不是同一个
但是两个const
创建出来的对象是同一个对象.
6. 可以 将类抽离为单独的文件
可以将类抽离为单独的文件, 然后通过import 引入, 这样模块化的开发方式利于多协同开发
// 抽离的类
// lib/Person.dart
class Person{
// 定义属性
String name;
int age;
// 默认调用的构造函数
Person(this.name,this.age);
// 命名构造函数
Person.paly(this.name){
print("${this.name}在打游戏");
}
// 定义方法
void getInfo(){
print("姓名:${this.name}--年纪:${this.age}");
}
}
引入类
// main.dart
// 引入模块的文件
import "lib/Person.dart";
void main(){
// 然后就可以使用抽离的类了
Person.paly("小明"); // 小明在打游戏
Person student = new Person("小明", 8);
student.getInfo(); //姓名:小明--年纪:8
}
7. 私有属性私有方法
dart中定义私有属性或方法通过_
来定义
但是要注意, 类必须抽离成单独的文件私有属性和方法才可以使用 ,否则没有效果
7.1 定义类和使用类在同一文件里
定义类和使用类在同一文件里, 私有属性没用
class Person{
// 定义属性
// _name 为私有属性
String _name;
int age;
// 默认调用的构造函数
Person(this._name,this.age);
// 定义方法
// _getInfo为私有方法
void _getInfo(){
print("姓名:${this._name}--年纪:${this.age}");
}
}
void main(){
Person student = new Person("小明", 8);
student._getInfo(); //姓名:小明--年纪:8
}
7.2 类的创建和调用在不同的文件
// 定义类
// lib/Person.dart
class Person{
// 定义属性
// 定义私有属性_name
String _name;
int age;
// 默认调用的构造函数
Person(this._name,this.age);
// 私有方法
void _getInfo(){
print("姓名:${this._name}--年纪:${this.age}");
}
// 实例方法
// 通过实例方法getInfo调用私有方法_getInfo
// 也就是说, 私有属性和私有方法,
void getInfo(){
this._getInfo();
}
}
调用类, 私有属性方法只能通过实例方法去调用获取
void main(){
Person student = new Person("小明", 8);
// student._getInfo(); // 实例调用私有方法报错
// print(student._name); // 实例调用私有属性报错
// print(Person._name); // 类调用私有属性也报错
print(student.age); // 8
// 私有属性和私有方法只能在实例的方法中调用
student.getInfo(); // 姓名:小明--年纪:8
}
8. 类中getter 和setter修饰符
getter和setter定义的函数名不能更属性重名, 因为getter,setter定义的函数是计算属性, 也就是是函数名会是属性, 函数的返回值会自动计算为属性的值;
说明:
-
Getter
和Setter
是用于对象属性读和写的特殊函数。 - 每个实例变量都有一个隐式
Getter
,如果变量不是final,通常情况下还会有一个Setter
- 使用
get
和set
关键字实现 Getter 和 Setter ,能够为实例创建新属性。 - 计算属性的值是通过计算而来,本身不存储值。
- 计算属性赋值,其实是通过计算转换到其他实例变量。
实例:
class Person{
// 定义属性
String _name;
int age;
// 默认调用的构造函数
Person(this._name,this.age);
// 获取计算属性
get getInfo{
return "${this._name}:${this.age}";
}
// 设置计算属性
set setName(value){
this._name = value;
}
}
void main(){
Person student = new Person("小明", 8);
// 操作计算属性
print(student.getInfo); // 小明:8
student.setName = "小红";
print(student.getInfo); // 小红:8
}
9. 静态属性和静态方法
dart中的静态成员
- 使用static 关键字来实现类级别的变量和函数
- 静态方法不能访问非静态成员, 非静态方法可以访问静态成员
- 静态方法不能在类的实例上使用, 因此无法访问 this(this值得是类的实例)
- 静态变量对于类级别的状态是非常有用的。
- 静态变量在被使用的时候才被初始化。
实例
class Person{
// 静态属性
static String name ="小明";
// 非静态属性
int age;
// 默认调用的构造函数
Person(name,this.age);
// 静态方法
static void getInfo(){
print(name);
// 静态方法不能调用非静态属性或方法
// 静态方法中不能使用this
// print("${name}:${this.age}"); // 报错
// this.setName(); // 报错
}
// 非静态方法
void setName(){
// 非静态方法可以调用静态属性或方法,调用的使用不需要通过this调用
print("${name}:${this.age}");
getInfo();
}
}
void main(){
Person student = new Person("小明", 8);
// 使用类调用静态方法,
Person.getInfo(); // 小明
// 实例调用非静态方法, 非静态方法可以调用静态方法
student.setName();
/*
* 小明:8
* 小明
* */
}
10. 对象的操作符
- ? 条件运算符(了解)
- as 类型转换
- is 类型判断
- .. 级联操作(链式调用)
10.1 条件运算符,
判断是以对象是存在,存在就调用方法, 不存在就不调用方法
class Person{
// 属性
String name ="小明";
int age = 18;
void getInfo(){
print("${name}:${this.age}");
}
}
void main(){
// 这么调用没有任何问题
// Person student = new Person();
// student.getInfo();
// 如果没有实例化呢
// Person student ;
// 没有实例化就调用方法会报错
// student.getInfo();
// 所以我们要先判断在调用
Person student ;
// 如果student 有这个方法就调用没有,也不会报错
student?.getInfo();
}
10.2 类型转换
类型转换使用关键字 as
class Person{
// 属性
String name ="小明";
int age = 18;
void getInfo(){
print("${name}:${this.age}");
}
}
void main(){
var student;
student = '';
student = new Person() ;
// 现在这么调用没有什么问题, 以前会报错
// 就是student 不能识别自己是字符串类型还是Person类型
// student.getInfo();
// 所以以前需要类型转换
(student as Person).getInfo()
}
10.3 类型判断
类型判断使用is
class Person{
// 属性
String name ="小明";
int age = 18;
void getInfo(){
print("${name}:${this.age}");
}
}
void main(){
var student;
student = '';
student = new Person() ;
// 判断是不是Person类型
print(student is Person); // true
}
其他类型的判断
void main(){
var str = "";
print(str is String); // true
int num = 123;
print(num is int); // true
bool boolean = true;
print(boolean is bool); // true
List list = ['a','b'];
print(list is List); // true
Map map = {"name":"aa"};
print(map is Map); // true
}
10.4 联级调用
就是可以通过..联级一次给实例添加多个属性
class Person{
// 属性
String name ="小明";
int age = 18;
String sex = "男";
void getInfo(){
print("${this.name}:${this.age}:${this.sex}");
}
}
void main(){
Person student = new Person() ;
student.getInfo(); // 小明:18:男
// 修改属性值
// student.name = "小红";
// student.age = 14;
// student.sex = "女";
// student.getInfo(); // 小红:14:女
// 修改多个属性值可以使用联级调用
// 结果完全一样
student..name = "小红"
..age = 14
..sex = "女";
student.getInfo(); // 小红:14:女
}
11. Dart中类的继承
子类使用extents关键词来继承父类
子类会继承父类里面可见的属性和方法, 但不会继承构造函数
子类能覆写父类的方法
11.1 继承父类
子类使用extents关键字继承父类
// 父类
class Person{
// 属性
String name ="小明";
int age = 18;
// 方法
void getInfo(){
print("${this.name}:${this.age}");
}
}
// 子类
// 子类通过extends 继承父类
class Student extends Person{
}
void main(){
// 通过子类实例化对象
Student student = new Student();
// 继承父类的属性
print(student.name); // 小明
// 继承了父类的方法
student.getInfo(); // 小明:18
}
11.2 使用关键字super来给父类的构造函数传参
实例化子类给父类传值
class Person{
// 属性
String name ;
int age ;
// 构造函数
Person(this.name, this.age);
// 方法
void getInfo(){
print("${this.name}:${this.age}");
}
}
// 子类
// 如果父类的属性是需要通过构造函数传参的
// 那么子类在继承的时候就需要通过super来给父类传参
class Student extends Person{
// 子类先后去实例化时传递过来的实参
// 子类获取实参后再将数据通过super 传递给父类的构造函数
Student(String name,int age):super(name,age);
}
void main(){
// 通过子类实例化对象
Student student = new Student("小明", 18);
// 继承父类的属性
print(student.name); // 小明
// 继承了父类的方法
student.getInfo(); // 小明:18
}
同时我们也可以扩展自己的属性和方法
// 父类
class Person{
// 属性
String name ;
int age ;
// 构造函数
Person(this.name, this.age);
// 方法
void getInfo(){
print("${this.name}:${this.age}");
}
}
// 子类
class Student extends Person{
// 扩展子类自己的属性
String sex;
Student(String name,int age,String sex):super(name,age){
this.sex = sex;
}
// 扩展子类自己的方法
void getStudentInfo(){
print("${this.name}:${this.age}:${this.sex}");
}
}
void main(){
// 通过子类实例化对象
Student student = new Student("小明", 18,"男");
// 继承父类的属性
print(student.name); // 小明
// 继承了父类的方法
student.getInfo(); // 小明:18
// 调用子类自己的方法
student.getStudentInfo(); // 小明:18:男
}
还可以给父类里的命名构造函数传参
// 父类
class Person{
// 属性
String name ;
int age ;
// 构造函数
Person(this.name, this.age);
// 父类的命名构造函数
Person.sayHello(String name){
print("${name} 说你好");
}
// 方法
void getInfo(){
print("${this.name}:${this.age}");
}
}
// 子类
class Student extends Person{
// 扩展子类自己的属性
String sex;
Student(String name,int age,String sex):super(name,age){
this.sex = sex;
}
// 给父类的命名构造函数传参
Student.sayHello(String name):super.sayHello(name);
// 扩展子类自己的方法
void getStudentInfo(){
print("${this.name}:${this.age}:${this.sex}");
}
}
void main(){
// 通过子类实例化对象
Student student = new Student("小明", 18,"男");
// 调用子类的命名构造函数
Student.sayHello("哈哈"); // 哈哈 说你好
}
11.3 覆写父类方法
所谓的复写,就是子类定义一个更父类同名的方法, 当子类调用这个同名方法的时候,优先使用子类自己的方法, 看起来就像把父类的同名方法覆盖了
覆写父类的方法时建议使用关键字: @override(可写可不写)
// 父类
class Person{
// 属性
String name = "小明";
int age = 18 ;
// 方法
void getInfo(){
print("${this.name}:${this.age}");
}
}
// 子类
class Student extends Person{
// 覆写父类的方法
@override // 覆写的时候加上这个,不写也可以,一般写上会比较好,知道这个方法是覆写方法
void getInfo(){
print("覆写了父类的方法");
}
}
void main(){
// 通过子类实例化对象
Student student = new Student();
student.getInfo(); // 覆写了父类的方法
}
11.4 子类扩展父类的方法
子类方法里调用父类的方法
对父类的方法进行扩展
// 父类
class Person{
// 属性
String name = "小明";
int age = 18 ;
// 方法
void getInfo(){
print("${this.name}:${this.age}");
}
}
// 子类
class Student extends Person{
// 调用父类方法, 扩展父类方法的功能
void sayHello(){
// 在子类的方法里调用父类的方法
super.getInfo();
// 扩展父类方法的功能
print("${this.name}说你们好");
}
}
void main(){
// 通过子类实例化对象
Student student = new Student();
student.sayHello(); // 扩展父类功能的子类方法
/*
小明:18
小明说你们好
*/
}