Dart语言学习——基本语法

配置环境和"hello world" demo

前面已经配置过flutter的环境了,能够在控制台直接使用flutter来创建和编译运行一个flutter项目。其实flutter sdk已经包含了dart sdk,其在fluuter的bin/cache目前下,名叫dart-sdk,在dart-sdk目录下也有一个bin目录,这个bin目录中就有一个dart命令命令,将dart-sdk/bin配置到环境变量的path就能在任意位置使用dart命令。

最佳配置应该是跟flutter的配置方法一样,先在环境变量配置文件中配置一个DART_HOME,然后再将dart-sdk的bin给连接到path上去。

export DART_HOME=$FLUTTER_HOME/bin/cache/dart-sdk 
export path=$path:$DART_HOME/bin 

这样的好处再明显不过了,如果flutter的位置变了,只需要将FLUTTER_HOME修改就行,DART_HOME和path所引用的都只是相对路径,不需要修改。将dart-sdk的bin配置到path后重启一下控制台程序,就可以发现dart命令了,执行一下dart --version,可以如果没有报找不到命令,并且正确输出了dart的版本号,就说明配置好了。

还是用世界性的编程题目——"hello world"来测一下,创建一个目录,在这个目录下创建一个test.dart文件,在其中写入hello world的dart程序。

// demo dart 
void main() { 
print('hello world!'); 
} 

回到控制台,执行命令`dart test.dart`,可见控制台输出了"hello world!"

Dart的变量数据类型

  看了dart的代码,发现它其实也是一门类C的语言,所有的代码都是由变量与方法组成,程序控制结构也包括了顺序结构,判断分支结构与循环结构。

Dart用var来声明一个变量,这跟JS很像,并且还可以自动类型推断,比如var number = 20;就是声明了一个int的变量,并且赋值了20,当然也可以给变量指定类型,比如String name = "Benson"。Dart跟Java和C/C++一样,每行的最后以一个分号结尾,不加分号会编译失败,同样,注释也是使用双左斜杠"//"。

Dart还支持动态类型,一般来说当一个变量声明时就确定了它的类型了,除了像Java这种面向对象语言可以将变量声明为Object类型的,那这个类型就可以用来赋值为任何对象了,包括基本数据类型,因为Java对所有基本数据类型都给也了其包装类型,如double的包装类型是Double。而Dart直接支持动态类型,只需要使用dynamic来声明一个变量即可。如下demo

// dynamic type demo 
main() { 
dynamic t = "Benson"; 
print(t); 
t = 20; 
print(t); 
} 

这段代码是可以编译并正常运行的,执行dart命令来编译并运行,会输出

Benson 
20 

Dart跟python一样,是门完全面向对象语言,任何变量或常量都是对象,因为所有的变量的默认值都是null

几乎所有的语言都支持了常量,那Dart有什么理由不支持呢?Dart同时支持final和const,其中final表示只可赋值一次,而const表示引用的是个字面量,在编译期就决定了它的真实值。

Dart支持的内置类型也非常丰富,主要包括以下各类

数值型:int和double

字符串:String,字符串可以用单引号'',也可以用双引号"",和其它语言一样,双引号可以内嵌表达式,表达式以$开头,而单引号的字符串不支持。

逻辑值:bool,只有true和false两个取值。

数组类型:List,Dart用范型List来表示数组,同时也可以当成列表来使用。

集合类型:Set,跟其他语言一样,其中的元素值只能有一个,不可重复。

Map:key-value映射,使用方式跟JS中的JSON类型类型

字符类型:Runes

函数

Dart语言的函数跟Java的特别相似,只是Dart的函数可以不需要声明函数的类型和参数的类型,当然也是可以声明的。Dart也跟Kotlin一样,当函数的实现只有一行代码时,可直接用表达式来写,例如

bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null; 

Dart语言的函数还支持命名可选参数,定义时如下

void enableFlags({bool bold, bool hidden}) {...} 

而使用时,则是

enableFlags(bold: true, hidden: false); 

当某些命名参数必填时,可用@required注解来修饰参数定义

Dart函数的位置可选参数使用[]来定义,如

String say(String from, String msg, [String device]) {...} 

对于默认参数,在定义可选参数时,可直接在定义函数时,使用=来给出默认值,例如

void enableFlags({bool bold = false, bool hidden = true}) {...} 

或者位置可选参数的默认值

String say(String from, String msg, [String device = "none"]) {...} 

Dart语言作为比Java更面向对象的语言,和JS一样,将函数作为了一等对象,也就是所有的函数都是对象类型,且可以赋值给变量,并且Dart还支持匿名函数或者说是Lambda

main() { 
var list = ["test1", "test2", "test3"]; 
list.forEach((item) { 
print("item is $item"); 
}); 
} 

这几行代码就演示了list.forEarch函数的参数就是一个函数,而这个参数在赋值时传入了一个lambda表达式。

Dart的逻辑控制跟Java几乎一模一样,都有if-else,for循环,while,do-while循环。Dart的异常处理跟Java也非常类似,只是写法不太一样,抛异常使用throw指令,可以抛一个任意对象,而catch指令则需要用on来指定异常的类型,如下

try { 
doSomeThing(); 
} on Exception catch(e) { 
print("...$e"); 
} 

同样的,Dart可以使用finally来处理try的任意情况,这跟Java是一样的,不论是否有抛出异常,都会执行finally块中的内容。

类与接口

  Dart中的类的概念跟Java也几乎一样,每个类都继承自Object类,且每个类都有且只有一个父类,每个类可以有n个子类。Dart对象的创建跟方法一样,可以省略new关键词,并且传构造参数时,不仅可以像Java那样按位置传,还可以使用命名参数来传,如下两种构造方法是等价的。

class Point { 
num x, y; 
Point(this.x, this.y); 
} 
var p1 = Point(1, 2); 
var p2 = Point({'x':1, 'y': 2}); 

Dart可以使用对象的runtimeType属性来返回这个对象的Type对象,也就是它的对象类型。

关于Dart中如何来创建一个类,这跟Java也是非常相似的,

class Point { 
num x; 
num y; 
num z = 0; 
Point(num x, num y) { 
this.x = x; 
this.y = y; 
} 
} 

由于Dart的所有变量默认值都是null,因此类中的属性默认值也都是null,当构造器传入参数对应其属性的话,则构造器可省略赋值语句,变成

class Point { 
num x, y; 
Point(this.x, this.y); 
} 

Dart还有一个叫作命名构造函数,其作用类似于Java的create生成方法,如下

class Point { 
num x; 
num y; 
Point(this.x, this.y); 
Point.fromJson(Map<String, num> map) { 
this.x = map["x"]; 
this.y = map["y"]; 
} 

Dart的继承也使用extends关键词,子类也默认不继承父类的构造函数,如要调用父类的构造函数,则需要在子类的构造函数中显式地调用父类的构造函数

class Point { 
num x; 
num y; 
Point(this.x, this.y); 
Point.fromJson(Map<String, num> map) { 
this.x = map["x"]; 
this.y = map["y"]; 
} 
} 
class PointV3 extends Point { 
num z; 
PointV3(num x, num y, num z): super(x, y) { 
this.z = z; 
} 
} 
main() { 
var p1 = Point(1, 2); 
print("x=${p1.x},y=${p1.y}"); 
var p2 = Point.fromJson({"x":3, "y":4}); 
print("x=${p2.x},y=${p2.y}"); 
var p3 = PointV3(1, 2, 3); 
print("x=${p3.x},y=${p3.y},z=${p3.z}"); 
} 

Dart也支持抽象类和抽象方法,只不过抽象类中的抽象方法不再需要在方法前面声明abstract关键词了,只需要在类名前加abstract关键词,

abstract class Person { 
say(String str); 
} 
class DXB extends Person { 
say(String str) { 
print("xiaobing say:$str"); 
} 
} 
main() { 
var dxb = DXB(); 
dxb.say("Hello"); 
} 

Dart同样支持接口,但支持方式比较奇葩,Dart让每个类都隐匿定义了一个接口,这个接口中包含了这个类所有的成员及其实现的接口。简单点理解就是如果类A要实现B的接口,而不是继承B的实现,就可以把B当成接口来implements,这样所有类都可以成为接口了,但前提是如果使用了implements,就不是使用的继承,也就是说需要在实现类中实现接口类中定义的所有方法

// person 类。 隐式接口里面包含了 greet() 方法声明。 
class Person { 
// 包含在接口里,但只在当前库中可见。 
final _name; 
// 不包含在接口里,因为这是一个构造函数。 
Person(this._name); 
// 包含在接口里。 
String greet(String who) => 'Hello, $who. I am $_name.'; 
} 
// person 接口的实现。 
class Impostor implements Person { 
get _name => ''; 
String greet(String who) => 'Hi $who. Do you know who I am?'; 
} 
String greetBob(Person person) => person.greet('Bob'); 
void main() { 
print(greetBob(Person('Kathy'))); 
print(greetBob(Impostor())); 
} 

和其他语言一样,Dart也使用enum关键词来支持枚举类型,其实我一直都觉得所有的面向对象语言的枚举设计都很鸡肋,要说枚举支持switch,那int也是支持的,所有地方的枚举都可以用int来代替,只能说编译期检查值范围是枚举唯一的用处了,但其实Java在Android中也是支持用注解来定义值范围的,比如用注解来标注传入的值必须是资源id类型或者特定字符串范围。

Dart同样也支持静态成员,静态成员属于类的,也用static来修饰。

Dart的范型

前面有提到Dart的集合类型,如Set、List和Map,这些集合类型都是带有范型参数的,与Java的范型有区别的是,Dart的范型是"固化的",简单点理解就是Dart的范型参数能够在支持期检查到,而Java的范型参数在编译期就会被擦除,最典型的现象就是Dart可以使用names is List<String>来判断集合的具体类型,而Java这样写直接就会报语法错误,只能判断name instanceOf List

说到范型就不得不说说协变和逆变了,所谓协变就是使用泛型集合get到的泛型值需要使用协变的父类型来引用,这样才能类型安全,而逆变则相反,是指add集合的值必须是逆变的子类

```

static class A { 
} 
static class B extends A { 
} 
static class C extends A { 
} 
static class D extends B { 
} 
static void test() { 
List<? extends A> list1 = new ArrayList<>(); 
List<? super B> list2 = new ArrayList<>(); 
A a = new A(); 
B b = new B(); 
C c = new C(); 
D d = new D(); 
list1.add(a); // 语法错误 
list1.add(b); // 语法错误 
list1.add(c); // 语法错误 
list1.add(d); // 语法错误 
A a1 = list1.get(0); 
B b1 = (B) list1.get(0); 
C c1 = (C) list1.get(0); 
D d1 = (D) list1.get(0); 
list2.add(a); // 语法错误 
list2.add(b); 
list2.add(c); // 语法错误 
list2.add(d); 
A a2 = (A) list2.get(0); 
B b2 = (B) list2.get(0); 
C c2 = (C) list2.get(0); 
D d2 = (D) list2.get(0); 
} 

```

在这段代码中,有一处是非常出乎人意料的,就是list1在add时,居然四种类型的值都会语法错误,这就是因为list1的泛型参数是协变的,协变是用来适配get的值使用父类引用的,因此A a1 = list1.get(0)就不需要强转。而list2.add(b)和list2.add(d)没有语法错误就是逆变在起作用,由于逆变可以add任意子类,因为在get时全部都要强转,而这些都是有可能会强转异常的。

Dart的导库

Dart跟Java一样,使用import关键词来导库,但库的定位不再是package指定的包名,而是一个URI,同时,导的库也可以使用as关键词来重命令,以此来避免库名重复导致的歧义。

Dart的导库还支持部分导入,用show关键词来选择性导入,用hide关键词来屏蔽导入。

Dart异步编程

Dart使用await、async和Future类型来提供异步支持。其中的Future比较类似于JS中的Promise,创建一个Promise时需要传入一个闭包,这个闭包中要执行耗时任务。同样Future创建时也传入一个匿名函数,这个匿名函数中可以执行耗时操作,同时Future还提供了then和catchError函数,也是要传入匿名函数,分别处理正常执行结果和异常结果,但执行then和catchError函数后,并不会马上执行耗时任务的匿名函数,这就是Dart提供的异步编译框架。

```

import 'dart:async'; 
Future<String> sayHello(String name) async { 
return Future(() { 
Future.delayed(const Duration(seconds: 1)); 
print("int call..."); 
return "hello $name"; 
}); 
} 
main() async { 
print("before call..."); 
sayHello("xiaobing") 
.then((res) { 
print(res); 
}) 
.catchError((error) { 
print(error); 
}); 
print("after call..."); 
} 

在上面这个例子中,打印结果是

before call... 
after call... 
int call... 
hello xiaobing 

分析可知先执行了main函数中的before call和after call,然后才执行耗时匿名函数,成功返回后将结果打印出来。

对于await和async的使用,可以想象这样一种场景,用网络请求登录,再获取用户信息,最后将用户信息保存到本地。这个场景中三个任务都是异步的,但三个任务是有先后关系的,如果只用Future,我们会写出如下嵌套层的代码

Future<String> login() { 
return Future(() => "token"); 
} 
Future<String> getUserInfo(String token) { 
return Future(() => "xiaobing-$token"); 
} 
Future<String> saveUserInfo(String name) { 
return Future(() { 
print("save $name"); 
return name; 
}); 
} 
main() { 
login().then((token) => getUserInfo(token) 
.then((name) => saveUserInfo(name).then((name) => print("saveUserInfo $name success...")) 
)); 
} 

但若使用async + await就可以避免这种嵌套写法

Future<String> login() { 
return Future(() => "token"); 
} 
Future<String> getUserInfo(String token) { 
return Future(() => "xiaobing-$token"); 
} 
Future<String> saveUserInfo(String name) { 
return Future(() { 
print("save $name"); 
return name; 
}); 
} 
main() async { 
var token = await login(); 
var name = await getUserInfo(token); 
var saveResult = await saveUserInfo(name); 
print("saveUserInfo $saveResult success"); 
} 

await指令是等待Future的耗时任务执行完,并将结果解包成普通类型,而aysnc是在使用await的函数上必须加的,因为await表示要等待,等待则必须异步,async则表示这个函数是个异步函数。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值