[Flutter]flutter基础之Dart语言基础(四)

一、运算符重载

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 事先提供的库,包含了一些文件或其他内容的处理方法,如 iomath 等文件,引入方式如下:

import 'dart:math';

main(){
  num maxNumber = max(10, 20);
  print(maxNumber);
}

max 为 系统 math 库提供的比较两个数字并返回较大数字的函数,引入库后可直接使用。引入格式为:import 'dart:文件名'

3. 引入第三方库

第三方库为其他开发者或公司等写好的具有一定功能的函数库,可以在 Dart 提供的官方平台寻找需要的库,地址为:https://pub.dev 。比如我们前面使用过的 meta.dart 文件,可以在网站中直接搜 meta 即可。结果如下图:

2020224_12_15.png

点击 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 中,没有关键字 publicprotectedprivate 。如果标识符以下划线(_)开头,则表示该标识符私有属性,只能在定义标识符的库中使用(定义标识符的当前.dart 文件)。

5. 指定库前缀

当引入的不同库中存在相同的类名、方法名或其他相同名称时,有时会出现冲突的问题,在 Dart 中,通过指定一个或多个库前缀的方式解决,使用 as 关键字。如 common.dartcommon1.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. 部分引入库

当一个库中包含很多解决问题的方法时,引入库但只会用到其中很少一部分内容或不想用某些方法内容,可以使用 showhide 关键字进行指定。在 common.dart 文件中添加方法 printInfocommon.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 中, 使用 asyncawait 来做异步编程,也可以使用 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 中有耗时操作 )。所以 asyncawait 需要一起使用,需要之后执行的处理使用 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");
}

使用 Futurethen 方式处理函数回调,如下:

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. 流处理

如果数据以流的方式进行处理,有两种方式,一种是使用 asyncawait 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 基础语法部分完毕。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容