原创
最近更新: 2021/08/30 23:16

Dart入门

Dart 语言开发文档

基本语法

Dart可以看做是C++、Java、Python和JS的混合和简化,语法较为自由,包含大量语法糖。

入口方法main

与C++和Java类似。

void main(List<String> arguments) {
  //函数体
}
//由于Dart的函数可以省略返回值,于是简化写法为
main() {
  //函数体
}

变量声明

Dart一切的变量都是对象,包括数字类型、布尔类型等。

Dart声明变量时可不初始化,不初始化时任何变量的值都是null。

动态类型

用var和dynamic关键字声明。

//如果进行初始化,编译器会自动推断变量的类型,不能再改变
var str = 'this is var';
str = 1;//报错
//var声明的变量如果不进行初始化,即为动态类型变量,可以用不同类型数据赋值
var str;
str = 'str';
str = 1;
//dynamic关键字声明的动态类型变量可以初始化
dynamic b = 20;
b = 'str';//不会报错

常量

final和const可以用来声明常量。

final和const修饰的变量必须在声明时初始化。但final是懒加载,即在使用时才加载到内存中。const则是在编译时就进行赋值。

因此,final可以用来修饰引用类型变量,const只能用来修饰基本类型变量。

运算符

算数运算符

主要是注意整除的写法,其他的和C++相同。

整除 取余
+ - * / ~/ %

赋值运算符

=是普通的赋值运算符

??=表示左边为空时进行赋值

所有算数运算符可以和赋值运算符组合成为复合赋值运算符

关系、逻辑、自增自减运算符与其他语言相同

条件语句

if条件语句、switch语句、三目运算符C语言相同

条件运算符??

左边不为空时返回左边值,否则取右边值

int b = a?? 10;//当a不为空时b=a,否则b=10

条件运算符?

左边不为空时执行语句,否则不执行。

Person p;
p?.func();//当p不为空时执行p.func()

类型判断运算符is

应当注意的是,此处判断的是实例的类型,不是指针的类型

'1234' is String;//true

类型转换运算符as

可以将一个类的实例转为该类型自身或者父类的实例

"1234" as Object;//类似于Java中的(Object)"1234"

级联操作符..

用于简洁地书写对同一个对象的多次访问。形式上类似于链式调用,但无需return this。

p..name = "李四"
 ..age  = 30
 ..printInfo();//只需要在最后写分号

库的引入

使用import语句:

//系统库的引入,用"dart:库名"
import "dart:math";

//自定义库的引入
import 'lib/Person.dart';//以本文件所在目录为起点的路径
import 'lib/Person.dart' as lib;//用as对库进行重命名防止重名,使用方法:lib.Person
import 'lib/Person.dart' show getName;//show关键字可以只引入部分方法
import 'lib/Person.dart' hide getName;//hide关键字可以不引入部分方法

要引入非系统库,在以下网址中搜索目标库名,有相关的doc与install方法等 https://pub.dev/packages


常用数据类型

数字类型int, double

int, double都是num类的子类,double类型变量可以用int类型数据赋值,反过来不行。

Dart中,数字可以直接通过点.来进行方法和属性的访问。

数值类型具有一些常用的属性和方法:

//正负号
a.sign;//大于0返回1,小于0返回-1,否则返回0
a.isNegative;//a是否小于0
//奇数偶数判断
2.isEven;//true
2.isOdd;//false
//数字类型可以是一些其他语言的非法值
int a = 0/0;//a = NaN
int a = 1/0;//a = Infinity
(0/0).isNaN;//true
a.isInfinite;//a是否是正无穷,即1/0
(10/0).isFinite;/false

布尔bool

有两个值true/false。bool类型与数值类型不互通。

字符串String

可以用单引号和双引号定义。三个引号可以定义多行字符串,和python一样。

字符串可以用+号进行拼接

字符串中用$符号可以对变量进行取值:

String str = 'qwe';
int a = 10;
String ss = '$str$a';//'qwe10'
//如果变量是对象的属性或者方法,则需要用大括号来标注
Person p;
String ss = '${p.name}';
字符串的类型转换
String str = 10.toString();//int转字符串
String str = 10.1.toString();//double转字符串
int a = int.parse("10");//字符串转为int
double a = int.parse("10.1");//字符串转为double

列表List

List类型类似于python的列表,默认存储类型是dynamic类型,可以存储不同类型的数据,用中括号标识。

List l = [1, 1.2, "wer"];
print(l);//打印:[1, 1.2, wer]
//可以用尖括号(泛型)指定存放数据的类型,如果类型不匹配会报错
List<int> l = <int>[1, 22, 333];
List<int> l = [1, 22, 333];
//其他的创建方法
var l = new List();//在Dart2.X中可用,最新版本中不可用
var l = List.filled(length, fill);//创建一个含有length个fill元素的列表,该方法创建的列表长度不可变,不可调用add方法

//使用下标访问元素
var e = l[0];
//add方法可以添加元素
l.add("qwe");//添加元素的类型要和指定的类型匹配
//length属性可以获取长度
int len = l.length;
//也可以直接修改length值。如果长度缩短了,那么多余的值会无法访问,如果长度增加了,新增的值为null
l.length = 2;
//判断是否为空
l.isEmpty;//l.isNotEmpty;
//获取列表的倒序,返回一个Iterable。可以用toList()转为List
List l2 = l.reversed.toList();
//查找元素,返回下标,查找不到返回-1
int idx = l.indexOf(element);

//map方法,用于对元素的遍历处理,传入一个匿名函数作为参数,返回一个Iterator
//作为参数的函数的参数是列表元素,返回处理后的元素
var newList = oldList.map((value){
    return value*2;
});

集合Set

用大括号标识,不包含重复元素。

字典Map

Map类型类似于python的字典,可以以键值对方式存储不同类型的数据,用大括号标识。

Map m = {1:'qwe',2:1.2};
print(m);//打印:{1: qwe, 2: 1.2}
var m = new Map();
p["name"] = "shangsan";

还有一些不常用的类型,如Rune、Symbol等。


函数

函数是属于Function类型的对象。

函数可以作为另一个函数的参数,称为回调函数(CallBack)。

Dart中函数的声明和定义语法类似C,但是更自由:

  • 如果声明了返回值和参数类型,必须按声明的要求调用。

  • 可以省略参数类型,此时传入的参数是动态类型。

  • 可以不显式声明返回值类型,此时默认返回dynamic(任意)类型的值。

  • 也可以没有return语句,此时函数返回一个null值。

可选参数

如果函数的某个参数不是必须的,可用中括号括起来,函数调用时可以不传入这个参数。

可选参数可以设定一个默认值,如果不设定,默认值为null。

命名参数

可以用大括号将某些参数括起来,此时调用方法传入参数时必须用冒号:指明传入参数的形参名字。命名参数也是可选参数,可以设定默认值。

匿名函数

var fn = (){
	//函数体
}

箭头函数/lambda表达式

用于函数体只有一行语句时使用,是一种简化的匿名函数写法。箭头右边只能包含一行语句,语句的结果即为返回值。

var fn = (参数名) => 一行语句;

自执行函数

来自JS的语法,是在定义完成之后立即执行的函数,需要将方法的定义用括号包起来。

((int n){       //括号包裹形参
    return n;
})(1);          //定义完后用括号代入参数

((){            //无参数的自执行函数
    print(1);
}());           //表示参数的括号也可以放在定义方法的括号里面

闭包

来自JS的语法,作用是在一个作用域之外访问作用域内部的变量,使其常驻内存,具有类似静态变量的性质。

一般用定义在函数内部的函数来实现,因为内部函数可以访问外部函数的作用域。

func1(){
    int n = 1;

    func2(){
        print(n++);
    }
    return func2;//由于func2可以访问func1的局部变量,返回func2
}

var fn = func1();//func2可以访问func1的局部变量,fn也可以
fn();//打印:1
fn();//打印:2

这段代码实现了在func1函数外部访问函数内部的变量n,只要fn变量存在,n就存在。理论上说这可以通过将变量n定义成全局变量来实现,但是这样会造成数据段和命名空间的污染。

闭包并不会导致内存泄漏,因为相关内存可以通过销毁fn变量来回收。

异步操作

async和await关键字用于编写异步代码。常用于I/O操作。

  • async用于修饰函数,表示这是一个异步函数,函数的返回值会被转为Future<dynamic>对象

    • 应当注意的是此处的Future<dynamic>对象不能给T类型对象赋值,需要使用await关键字进行转换。
  • await用于修饰语句。

    • async异步函数在运行过程中,遇到await关键字时会挂起并跳出该函数,执行函数下面的语句。

    • await修饰的语句会在主干程序之外异步执行,执行完毕之后返回,再继续执行函数体内后面的语句。

    • await关键字可以将Future<dynamic>对象在执行完毕之后转为T类型对象,常用于一般的赋值语句。修饰非Future类型的对象时不进行类型转换。

    • await关键字必须在async函数内部使用,单独使用await会报错。

String data;

func() async{
  data = await '1234';
  print("func: " + data + "\t" + DateTime.now().microsecondsSinceEpoch.toString());
}

void main (List<String> arguments) {
  func();//如果这一行加上await,就是顺序执行
  print("main: " + data.toString() + "\t" + DateTime.now().microsecondsSinceEpoch.toString());
}
/*结果:乱序执行
main: null	1625023567609939
func: 1234	1625023567625560
*/

总而言之,async意味着函数本身的异步执行和函数体内部的顺序执行。await保证了函数体内部代码的执行顺序,但函数体内部和函数外部的代码可能是乱序执行的。


面向对象

类的定义与实例化与Java相同。实例化一个对象时,可以省略new运算符

默认构造函数和含参构造函数与Java相同。

class Person{
    String name;
    int age;

    //含参构造函数的一个简要写法,可以直接赋值
    Person(this.name, this.age);
    //初始化列表
    Person(a, b):name=a, age=b{}//和C++的初始化列表语法稍有不同

    //命名构造函数:构造函数的一种,返回一个实例,但是方法名可以自定义
    Person.newPerson(){}//通过调用Person.newPerson()来获取对象

    //getter,用get关键字来声明
    get name{return this.name};//不需要返回值和参数
    //可以自定义get的内容
    get info{return "${this.name}, ${this.age}";}//用p.info来调用
    //setter,用set关键字来声明
    set newName(String name){this.name = name;}//用p.newName = 'new';来调用
    //getter和setter是语法糖,但是没必要
}

权限控制

Dart中没有权限控制的关键字,但可以**在变量名和方法名的开头加下划线_**将变量和方法定义成私有。私有变量只能在定义类的文件中访问。

static关键字

用于标识静态属性和静态方法,与C++相同。

继承

用extends标识,与Java相同。extends只支持单继承。用super关键字访问父类相关成员。

class Person extends Animal{
    Person(arg1, arg2) : super(arg1, arg2){}//用super()调用父类构造函数
    Person() : super.xxx(){}//也可调用父类的命名构造函数
}

mixins特性

可以用with关键字 class A with B, C,...实现多继承,如果方法重名,后面的会覆盖前面的,此时super会指向最后一个父类。

mixins的类只能继承自Object类,不能有构造函数。

mixins与extends同时使用时,super会指向mixins的最后一个父类。

方法的覆写

与Java的重载相同,可以用@override标识。

方法重载后,无论父类是不是抽象类,同名方法都会被子类的方法覆盖,可以通过super来调用父类的相关方法。

抽象类

用abstract标识,和C++相同。抽象类不能被实例化。

抽象类可以被继承,也可以通过implements进行接口的实现(事实上非抽象类也可以)。

抽象方法的声明:不写方法体。抽象方法必须被非抽象子类实现。

多态

抽象父类对象指针指向子类实例,即为多态。由于非抽象类的方法被重写之后也会被覆盖,所以多态不一定只在抽象类和其子类之间实现。

泛型

和Java相同。

评论区