一、运算符重载
Dart 支持运算符重载,使用 operator
关键字,语法格式为:operator 运算符
,如下,重载加法运算符:
main(){
Vector v1 = Vector(1, 2);
Vector v2 = Vector(2, 3);
Vector v3 = v1 + v2;
print("${v3.x} ${v3.y}"); //输出 3 5
}
class Vector {
int x, y;
Vector(this.x, this.y);
Vector operator +(Vector v) {
return Vector(x+v.x, y+v.y);
}
}
一、模块的使用
有点规模的工程一般都会有很多个文件和模块组成,甚至简单的程序也不会是单个文件组成的。在 Dart 中,每一个 .dart
文件就是一个模块,称为库。可以将不同的功能分装在不同的库中,以便在不同的文件中使用同一库的功能函数,减少代码量,可以使用 import
关键字进行文件导入。
在 Dart 中,库(模块)类型分为三种:自定义库、系统库(Dart 提供的库文件)、第三方库。
1. 引入自定义库
引入自定义库(自己创建的 .dart
文件)的方式如下,如在当前文件夹新建一个 common 文件夹为公共类文件夹,在文件夹中新建 common.dart
文件,内容如下:
void description(){
print("描述内容");
}
在 main
方法所在文件引入 common.dart
文件并使用方式如下:
import 'common/common.dart';
main(){
description();
}
通过 import
导入文件后,即可使用文件中的方法等内容。当然也可以在自定义库内新建其他 class
等。引入的格式为 import '路径/文件名.dart'
。
2. 引入系统库
系统库是由 Dart 事先提供的库,包含了一些文件或其他内容的处理方法,如 io
、math
等文件,引入方式如下:
import 'dart:math';
main(){
num maxNumber = max(10, 20);
print(maxNumber);
}
max
为 系统 math
库提供的比较两个数字并返回较大数字的函数,引入库后可直接使用。引入格式为:import 'dart:文件名'
。
3. 引入第三方库
第三方库为其他开发者或公司等写好的具有一定功能的函数库,可以在 Dart 提供的官方平台寻找需要的库,地址为:https://pub.dev 。比如我们前面使用过的 meta.dart
文件,可以在网站中直接搜 meta 即可。结果如下图:
点击 Installing ,进入安装说明页,如下图:
2020224_12_15.jpg本页列出了包的安装方式,可以通过命令行和配置文件方式引入,这里介绍下配置文件引入的方法。对于 Flutter 项目,创建工程后在文件的顶层目录会存在一个名为 pubspec.yaml
的配置文件。在没有此文件的工程中可以手动在顶级目录添加此文件,里面包含一些包的依赖和其他信息,具体可以参考:https://dart.dev/tools/pub/pubspec 。只需将按照上图的步骤1,将包名和对应的版本号添加到 dependencies
即可。编译器会自动下载所需依赖包,开发者只需在工程文件中,通过 package
方式引入即可,如没有自动下载依赖,可以查看官方的具体信息,进入项目所在文件目录,使用 pub get
命令下载即可。 使用如下方式导入:
import 'package:meta/meta.dart';
4. 私有属性
对于私有属性,前面文章简单介绍过。在 Dart 中,没有关键字 public
、protected
、private
。如果标识符以下划线(_
)开头,则表示该标识符私有属性,只能在定义标识符的库中使用(定义标识符的当前.dart
文件)。
5. 指定库前缀
当引入的不同库中存在相同的类名、方法名或其他相同名称时,有时会出现冲突的问题,在 Dart 中,通过指定一个或多个库前缀的方式解决,使用 as
关键字。如 common.dart
与 common1.dart
中同时存在名为 description
的方法,内容分别如下:
//common.dart
void description(){
print("common的描述内容");
}
//common1.dart
void description(){
print("common1的描述内容");
}
在 main
方法所在文件进行引入:
import 'common/common.dart';
import 'common/common1.dart';
main(){
// description(); 此时调用会抛出两个库文件存在同一方法的异常
}
此时,需要至少指定其中一个库的前缀或部分引入的方式解决问题,使用前缀如下:
import 'common/common.dart';
import 'common/common1.dart' as com1;
main(){
description();
com1.description();
}
这里为 common1.dart
指定了 com1
前缀,调用方法或其他内容时,需要加上前缀进行调用即可。类似于其他语言的命名空间概念。
6. 部分引入库
当一个库中包含很多解决问题的方法时,引入库但只会用到其中很少一部分内容或不想用某些方法内容,可以使用 show
或 hide
关键字进行指定。在 common.dart
文件中添加方法 printInfo
,common.dart
内容如下:
void description(){
print("common的描述内容");
}
void printInfo() {
print("打印指定信息");
}
使用 show
关键字指定只导入 printInfo
方法如下:
import 'common/common.dart' show printInfo;
main(){
// description(); //调用description会抛出异常
printInfo(); //输出 打印指定信息
}
使用 hide
关键字指定不导入部分如下:
import 'common/common.dart' hide printInfo;
main(){
description();
// printInfo(); //调用会报未定义错误
}
show
多个可用逗号做分隔。另外,Dart 中还支持惰性加载库,使用关键字 deferred
,但是 Flutter 并不支持延迟,所以不多做介绍,有兴趣的朋友可以自行了解。
二、泛型
第一篇文章介绍过泛型的基本使用,使用泛型是为了解决类型安全问题以及重复代码的问题。泛型,也就是通用类型,在前面的文章中使用过泛型。例如,当创建一个只能包含字符串的列表(List
)时,如不通过泛型执行数据类型,则创建的列表为动态类型,如下:
main(){
List lst = List();
print(lst.runtimeType); //输出 List<dynamic>
}
如果列表是为了特定的问题而创建的只能包含字符串的类型(可能需要对列表内的数据进行特定的字符串处理),此时如果数组内添加了非字符串类型数据,调用字符串处理方法就会出现异常,所以需要使用泛型对添加的数据类型做限定,如下:
main(){
List lst = List<String>();
print(lst.runtimeType); //输出 List<String>
}
此时如果列表添加其他类型数据就会抛出异常,如法进行添加。
也可以使用泛型来减少重复代码,如下:
main(){
var student1 = StudentInt(1);
var student2 = StudentString("hike");
student1.study(); //输出 1 号学生在学习
student2.study(); //输出 hike 在学习
}
class StudentInt {
int stuId;
StudentInt(this.stuId);
void study(){
print("$stuId 号学生在学习");
}
}
class StudentString {
String name;
StudentString(this.name);
void study(){
print("$name 在学习");
}
}
上述代码,分别为通过学号和学生姓名来创建学生,创建了两个类,通过泛型修改如下:
main(){
var student1 = Student(1);
var student2 = Student("hike");
student1.study(); //输出 1 号学生在学习
student2.study(); //输出 hike 在学习
}
class Student<T> {
T stuIdentifier;
Student(this.stuIdentifier);
void study(){
if(stuIdentifier is num) {
print("$stuIdentifier 号学生在学习");
}else if(stuIdentifier is String) {
print("$stuIdentifier 在学习");
}
}
}
通过泛型 <T>
可以有效减少代码的重复量,通过传入不同的参数类型区分不同的执行结果。其中 T
为标识符,为类型占位,类型为传入数据的数据类型,在运行时确定。
泛型的另一个用途是用来约束范围,可以用来用在继承类类型的限制,如下:
main(){;
var student = Coach<Student>(new Student("学生 在讲话"));
student.coachMethod(); //学生在讲话
}
class Coach<T extends Person> {
T coachData;
Coach(this.coachData);
coachMethod() {
coachData.say();
}
}
class Student extends Person {
Student(String name) : super(name);
}
class Teacher extends Person {
Teacher(name) : super(name);
}
class Person {
String profession;
Person(this.profession);
void say(){
print("$profession 在讲话");
}
}
这里定义了辅导类(Coach
),泛型约束了传入的类必须是继承自 Person
类,main
中的定义方法又约束了具体的类的类型,如果传入其他类进行初始化操作则会报错。
也可以在函数中使用泛型,如下:
main(){;
print(lastString([1, 2, 3]));
}
T lastString<T>(List<T> lst) {
if(lst.length <= 0){
print("长度有误");
return null;
}
return lst.first;
}
T lastString<T>(List<T> lst)
中,第一个 T
代表返回值类型,第二个 <T>
表示可以在函数的多个地方使用类型为 T
的参数,如果不加此参数会报错,第三个为列表中的参数类型。
三、异步编程
在程序设计中,为了不影响用户的使用,都会对耗时的任务做异步处理,以防止对用户界面或功能造成假死、卡顿等现象。在 Dart 中, 使用 async
和 await
来做异步编程,也可以使用 Future
API。
1. async 与 await
await
必须在 async
函数中使用。比如,定义如下函数:
getNetworkData() {
// var data = await "获取的网络数据"; //次处代码会报错,await 必须使用在 async 函数中
}
对上述代码做如下修改:
main(){;
getNetworkData();
print("执行完毕");
}
getNetworkData() async {
var data = await "获取的网络数据";
print("数据:$data");
}
输出结果:
执行完毕
数据:获取的网络数据
如果去掉 await
关键字修饰,做如下修改:
main(){;
getNetworkData();
print("执行完毕");
}
getNetworkData() async {
var data = "获取的网络数据";
print("数据:$data");
}
输出结果:
数据:获取的网络数据
执行完毕
可以发现当去掉 await
修饰后,函数的执行过程即为调用过程,顺序执行(即便在 getNetworkData
中有耗时操作 )。所以 async
与 await
需要一起使用,需要之后执行的处理使用 await
修饰。异步操作,不会等待操作执行完毕,而是继续执行后面的任务。
可以同时使用多个 await
,如下:
getNetworkData() async {
var data = await "获取的网络数据";
print("数据:$data");
var proData = await "处理数据";
print("处理:$proData");
}
2. 回调
当从网络获取到数据后,需要对数据进行本地处理后进行UI渲染,Dart 中可以使用回调函数的处理方式处理此问题,如下:
main(){;
getNetworkData(renderUI);
print("执行完毕");
}
getNetworkData(methodCallBack) async {
var data = await "获取的网络数据";
methodCallBack(data);
}
renderUI(var data){
print("$data 进行UI渲染");
}
2. Future API
对于被 async
修饰的异步函数,其返回值类型为 Future
,无论是否显式执行返回值类型。对于没有返回值的异步函数,应使用 Feture<void>
作为返回值类型。如下:
Future<void> getNetworkData() async {
var data = await "获取的网络数据";
print("数据:$data");
}
使用 Future
的 then
方式处理函数回调,如下:
main(){
var future = getNetworkData();
future.then((data){;
renderUI(data);
});
}
getNetworkData() async {
var data = await "获取的网络数据";
return data;
}
renderUI(var data){
print("$data 进行UI渲染");
}
官方建议,如果能使用 await
执行异步操作尽量使用此方式。Future 的 then
方法主要用于当有多个异步任务有依赖关系时使用。
关于更多 Future API的使用,可见官方文档:https://dart.dev/guides/libraries/library-tour#dartasync---asynchronous-programming
3. 流处理
如果数据以流的方式进行处理,有两种方式,一种是使用 async
和 await for
处理,另外一种是使用 stream
API。详细见官方文档:https://dart.dev/guides/libraries/library-tour#stream
四、可调用类
可调用类的作用是使类的实例可以像函数一样使用,需要在类中实现 call()
方法,如下:
main(){
var student = Student();
var des = student("hike", 11);
print(des);
}
class Student {
call(String name, int age) {
return "$age 的学生 $name 在学习!";
}
}
五. Typedefs
在当前的 Dart 中,可以为函数指定别名(其他类型暂时不可以),使用 typedef
关键字。Dart 中的此功能是一种函数匹配,就官网的例子来说看似有点鸡肋,看如下例子:
main(){
var mAdd = MethodAdd(addFunction);
var result= mAdd.func(1, 2);
print(result); //输出 3
print(mAdd.func is Function); //输出 true
}
class MethodAdd {
Function func;
MethodAdd(this.func);
}
num addFunction(num a, num b){
return a + b;
}
定义一个 Function
类型参数作为类 MethodAdd
的构造函数参数,传入 addFunction
方法做加法运算并得出结果。使用 typedef
定义别名方式如下:
typedef Add = num Function(num a, num b);
main(){
var mAdd = MethodAdd(addFunction);
var result= mAdd.func(1, 2);
print(result); //输出 3
print(mAdd.func is Function); //输出 true
print(mAdd.func is Add); //输出 true
}
class MethodAdd {
Add func;
MethodAdd(this.func);
}
num addFunction(num a, num b){
return a + b;
}
上述代码最终的区别就在于最后对类型的具体判断,未使用 typedef
定义别名时,仅能判断传入的是一个函数,使用别名后,因为定义了具体类型(这里是加法),可以具体判断到加法,传入其他类型,也是如此,也就是提供了一种类型检查机制。
六、Metadata
Dart 中,使用元数据来提供有关代码的其他信息。元数据注解使用字符 @
开头,其后是对编译时常量的引用。Dart 中提供的批注有:@deprecated
、@override
、@proxy
。
@override
前面说过,用来指示其标识的代码为重写父类的代码。
@deprecated
表示被标识的代码被弃用,应使用其他替代代码,但依然可以执行,直到下次版本更新。
main(){
Student student = Student();
student.studyData();
student.study();
}
class Student extends Person{
@override
void eat() {
print("学生吃");
}
@deprecated
void studyData() {
study(); //studyData 将被弃用,使用study方法代替
}
void study() {
print("学习");
}
}
class Person {
void eat(){
print("吃");
}
}
也可以自定义元数据注解,新建一个名为 customAnnotation.dart
的文件,创建自定义注解并不一定需要单独创建一个文件,这里只是个例子,如一个注解只在本类使用,在类中创建即可(不知道是否有意义,但是这样做是可以的)。如果需要创建一个共享的,在多个列中都使用的注解,则单独创建一个 .dart
文件。我们创建的文件内容如下:
library studyDescription;
/**
* 这是对学习的描述
*/
const StudyDes stuDes = StudyDes();
class StudyDes {
const StudyDes();
}
这是一个用来描述学习的注解(只是例子,没有任何意义,这是根据官方注解的样子仿写的)。第一行使用 library
指令声明这是一个正式的 Dart 库(无此声明也可以),名字为 studyDescription
。注释格式为文档注释,创建的注解为 stuDes
。定义注解的类的构造函数必须为常量构造函数,关于常量构造函数的说明上文有过说明。 使用方式如下:
import 'customAnnotation.dart';
main(){
Student student = Student();
student.study();
}
class Student{
@stuDes
void study() {
print("学习");
}
}
在编译器中,当鼠标放在 import 'customAnnotation.dart';
上静止不动时,会显示 library
定义的正式库的名字,鼠标放在 stuDes
上会提示注解的注释信息。这个注解是无参数的,也可以定义有参数的,与定义类的方式没什么区别,这里不再赘述。
元数据可以出现在库(library
),类(class
),typedef
,类型参数(type parameter
),构造函数(constructor
),工厂(factory
),函数(function
),字段(field
),参数(parameter
)或变量声明(variable declaration
)之前,也可以出现在导入或导出指令之前。您可以在运行时使用反射来检索元数据
至此,Dart 基础语法部分完毕。
网友评论