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相同。
评论区